Compare commits

...

124 Commits
0.9 ... 1.2

Author SHA1 Message Date
Kevin Papst
94f8159fdc prepare release 1.2 (#1059) 2019-08-27 18:37:42 +02:00
Kevin Papst
8fb0b1445a added korean translations (#1039) 2019-08-26 15:00:59 +02:00
Kevin Papst
15673555a2 added API functions to save meta fields (#1063) 2019-08-26 14:30:39 +02:00
Kevin Papst
ceeec7b8a6 improved invoice handling for fixed rates (#1058) 2019-08-24 00:02:16 +02:00
Kevin Papst
46e5650882 refactored invoice and export screens (#1046) 2019-08-22 18:56:21 +02:00
Kevin Papst
405ea0076b fix empty user language (#1054) 2019-08-21 11:16:44 +02:00
Kevin Papst
8cd7f7de3f do not delete team if linked entity is deleted (#1053) 2019-08-21 01:04:58 +02:00
Kevin Papst
7cd17154fc support creating new roles (#1050) 2019-08-20 14:02:15 +02:00
Kevin Papst
299fcb091a added documentation links to all screens (#1044) 2019-08-19 12:53:36 +02:00
Kevin Papst
025c6b0e7b added css class to target running records in timesheet tables (#1042) 2019-08-16 22:34:41 +02:00
Kevin Papst
388c3ec188 reduce field size to 150 chars to prevent index out of length (#1031) 2019-08-16 16:57:09 +02:00
Kevin Papst
b6d9bb0d2a fix team opens in modal (#1041) 2019-08-16 16:55:56 +02:00
Kevin Papst
561ec3b1e9 added team permissions (#996) 2019-08-16 01:22:33 +02:00
Kevin Papst
9611646bc6 added decimal format variable for invoice duration (#1037) 2019-08-15 21:31:13 +02:00
Kevin Papst
b5972bb680 added invoice variable to display month numerical (#1033) 2019-08-14 23:30:45 +02:00
Kevin Papst
455861ec69 copy timesheet meta fields on restart record (#1030) 2019-08-13 18:41:20 +02:00
trentks
5affa0de82 fix various minor english grammar translations (#1002) 2019-08-08 23:04:02 +02:00
Kevin Papst
e11a2a6089 upgraded FosUserBundle and Symfony 4.3 (#1005) 2019-08-02 16:52:30 +02:00
Kevin Papst
614a50ce0b added command to show plugin versions (#994) 2019-08-02 08:53:51 +02:00
Kevin Papst
66594b286a remove default role from user filter and role select (#993) 2019-08-02 08:31:45 +02:00
Kevin Papst
8e06f94bdd composer prod dependencies and commands (#986) 2019-08-01 23:21:43 +02:00
trentks
b0e03bb0b8 Fix english translation for 'info' (#995) 2019-08-01 23:01:02 +02:00
Kevin Papst
22e30b1871 fix activity import Kimai v1 (#1001) 2019-08-01 21:38:28 +02:00
Kevin Papst
8104a67e3e travis builds with mysql (#924) 2019-08-01 19:25:38 +02:00
Kevin Papst
654ffda454 respect users timezone when populating working-time dashboard widget (#988) 2019-07-30 23:57:20 +02:00
cdahinten
3f51437569 optimize DE translations (#950) 2019-07-29 13:31:00 +02:00
Kevin Papst
1bd784eef3 added calendar slot_duration config (#989) 2019-07-26 23:51:29 +02:00
yayitazale
0d11aded40 spanish update - more natural translations (#945) 2019-07-25 18:26:39 +02:00
Kevin Papst
00925d38aa fix migration for sqlite users (#985) 2019-07-25 18:00:53 +02:00
Kevin Papst
12803b3957 prepare release 1.1 (#983) 2019-07-24 19:38:46 +02:00
Kevin Papst
7583498735 added invoice calculator to sum by day (#982) 2019-07-24 18:36:06 +02:00
Kevin Papst
53eda31179 project grouped invoice calculator (#892) 2019-07-24 17:45:51 +02:00
Kevin Papst
56e276939b trigger template events in authentication layout as well (#981) 2019-07-24 15:32:46 +02:00
Kevin Papst
39abccb35d dashboard is missing in initial view (#974) 2019-07-24 14:47:07 +02:00
Toby Batch
dae8866053 Fixes regexp for search timesheets by tag (#978) 2019-07-22 15:34:40 +02:00
Kevin Papst
a0e8aa3a43 fixed xss in timesheet description (#962) 2019-07-14 13:58:10 +02:00
Kevin Papst
e68b7da1f8 added slovakian translations (#952) 2019-07-12 15:52:30 +02:00
Marcos Ellys Rocha Honorato
fcce7a531a updated Portuguese (Brazilian) translations (#942) 2019-07-11 00:18:42 +02:00
Kevin Papst
f23dcbec17 run migrations always in kimai:install command (#944) 2019-07-10 19:19:03 +02:00
Kevin Papst
8a1d28563d fix changing project for existing entries (#940) 2019-07-10 19:17:34 +02:00
Kevin Papst
c33a87a07c toolbar dropdown and visibility improvements (#933) 2019-07-09 16:34:11 +02:00
Toby Batch
b833e1ce27 pre-populate tags for new timesheet records via request params (#930) 2019-07-09 16:32:40 +02:00
Kevin Papst
6de1a55d73 updated swedish translations (#934) 2019-07-09 14:02:11 +02:00
Kevin Papst
cbf9086e10 performance improvements (#927) 2019-07-09 03:40:48 +02:00
Kevin Papst
eb4aa23577 allow non-interactive installation (#932) 2019-07-09 03:03:27 +02:00
Kevin Papst
bf9ae44775 fix LDAP edge-case initialization error (#931) 2019-07-08 20:35:41 +02:00
Kevin Papst
f0822fa2b1 prepare release 1.0.1 (#922) 2019-07-05 19:25:06 +02:00
Kevin Papst
f966d34929 updated all composer packages (#920) 2019-07-05 15:07:03 +02:00
Kevin Papst
d51136f8f4 cleanup prod log config (#921) 2019-07-05 13:48:24 +02:00
Kevin Papst
19843760e5 allow to configure default values for users (#919) 2019-07-05 02:42:50 +02:00
Kevin Papst
3457f93ed0 fix 500 happening in error pages (#914) 2019-07-04 17:00:55 +02:00
Kevin Papst
625ac2f1d7 fix default calendar config (#918) 2019-07-04 16:59:57 +02:00
Kevin Papst
061581c4ff use pre-defined icons in menu as well (#913) 2019-07-04 14:59:03 +02:00
Kevin Papst
a0116422c6 remove scrutinizer (#915) 2019-07-04 14:57:07 +02:00
yayitazale
94e49ea86f updated Spanish translations (#906) 2019-07-02 12:10:38 +02:00
heddi.nabbisen
040409eac7 Update japanese translation (#905) 2019-07-02 12:07:01 +02:00
Kevin Papst
baa2fca7ca Release 1.0 (#885) 2019-07-02 02:36:46 +02:00
infeeeee
1fd8139214 Update Hungarian translation (#902) 2019-07-02 00:14:31 +02:00
Kevin Papst
db5f9f639e fix selectbox is not resettable (#901) 2019-07-02 00:14:06 +02:00
Kevin Papst
90980f40f5 allow to disable user preferences by permission (#895) 2019-07-01 19:37:20 +02:00
Kevin Papst
94d68c8b64 fixed short invoice (#891) 2019-06-30 18:38:02 +02:00
Kevin Papst
834a0a873f fix migration for sqlite (#881) 2019-06-30 15:16:44 +02:00
Kevin Papst
d8621f0b7a support custom fields for timesheets, customers, projects and activities (#871) 2019-06-26 00:39:05 +02:00
Kevin Papst
994c671fd8 order users by username (#880) 2019-06-26 00:35:54 +02:00
Kevin Papst
7b95750dcf added LDAP config to set filter for finding user attributes (#877) 2019-06-23 12:38:11 +02:00
Kevin Papst
9fbbaf9b88 performance tuning on timesheets (#874) 2019-06-20 16:05:31 +02:00
Jacob Blanco
bb8572a364 fix LDAP hydration with multi-value email field (#862) 2019-06-20 15:35:22 +02:00
Kevin Papst
bdd05a275d use default_begin in duration only mode (#873) 2019-06-20 01:38:28 +02:00
Toby Batch
dcdbf23b6d load plugins in test env (#864) 2019-06-19 23:10:06 +02:00
Kevin Papst
ce6ad0bbb8 export tags in spreadsheets (#860) 2019-06-16 15:35:22 +02:00
Kevin Papst
59d2946b91 add "duration + fixed start time" tracking mode (#859) 2019-06-15 23:04:53 +02:00
Kevin Papst
92f3f4ee57 added config to brand Kimai with logo and company name (#856) 2019-06-13 19:06:02 +02:00
Kevin Papst
3311f17bbb users weekly stats as bar-chart in dashboard (#847) 2019-06-13 18:38:49 +02:00
yayitazale
e1f862507e Spanish translate updated (#852) 2019-06-13 13:41:26 +02:00
Thierry Pasquier
7e040ac216 Update of the French translation (#850) 2019-06-13 13:02:12 +02:00
Kevin Papst
515c19422d improved fixture data for demo (#848) 2019-06-11 14:55:25 +02:00
Kevin Papst
5702d7afa8 added hourly and money budgets to activity, project and customer (#843) 2019-06-11 13:18:57 +02:00
Kevin Papst
833968a87d fix LDAP install for systems without ldap extension (#846) 2019-06-11 01:25:49 +02:00
Kevin Papst
0c0e9c2f71 LDAP authentication support (#815) 2019-06-07 22:48:39 +02:00
Kevin Papst
bcf1ebd778 fixed pdf temp directory (#845) 2019-06-07 18:17:45 +02:00
heddi.nabbisen
4c82f9767f updated japanese translations (#837) 2019-06-05 13:57:08 +02:00
Kevin Papst
ab405f671e updated dockerfile (#840) 2019-06-05 13:56:31 +02:00
Kevin Papst
325d204332 added installation command (#838) 2019-06-04 23:51:37 +02:00
Kevin Papst
97feed0be6 updated frontend stack to latest versions (#729) 2019-06-03 01:43:49 +02:00
Kevin Papst
19636582ab use codecov for code coverage (#833) 2019-06-01 11:53:27 +02:00
Kevin Papst
393abe9e62 phpstan level 3, fixed deprecations, code cleanup (#811) 2019-05-28 12:24:21 +02:00
Kevin Papst
d9dca96a32 added punch-in punch-out / time-clock mode (#812) 2019-05-27 01:36:43 +02:00
Kevin Papst
ebff4a765a refactored permissions for simpler customizations (#808) 2019-05-25 17:23:15 +02:00
Kevin Papst
d18019159b fix tag query to show all tags of an item, even if not included in query (#810) 2019-05-25 16:27:01 +02:00
Kevin Papst
46905e9504 allow markdown for customer, project and activity comments (#807) 2019-05-25 00:29:23 +02:00
Kevin Papst
8b1c15ccb6 added github funding config (#804) 2019-05-24 13:16:25 +02:00
Kevin Papst
8873947a1b added phpstan for code analyzes (#806) 2019-05-24 01:11:03 +02:00
Kevin Papst
cdef22b4d1 added SECUTRITY.md for reporting vulnerabilities (#805) 2019-05-23 21:42:52 +02:00
Kevin Papst
29209105d3 improved html export (#802) 2019-05-23 18:19:56 +02:00
Kevin Papst
fea3495098 lock exported timesheets (#798) 2019-05-22 22:28:29 +02:00
Kevin Papst
871b2e52c7 use dropdown for entity actions on detail pages (#801) 2019-05-21 23:21:26 +02:00
Kevin Papst
ec174a38a9 timesheet controller refactoring (#796) 2019-05-19 23:52:43 +02:00
Kevin Papst
46ff78a4c4 fix phpunit annotations (#794) 2019-05-19 17:05:07 +02:00
Kevin Papst
d2ad87d09c improved calendar (#784) 2019-05-19 16:16:20 +02:00
Kevin Papst
1903d6fb77 fix column and max key length (#789) 2019-05-14 23:09:33 +02:00
Kevin Papst
bbe1f9beda restart via API allows to copy description (#782) 2019-05-13 17:32:01 +02:00
heddi.nabbisen
1f5710eaba updated japanese translations (#779) 2019-05-13 13:16:27 +02:00
Kevin Papst
392f8239f5 bugfix for API selects (#780) 2019-05-12 17:22:24 +02:00
Kevin Papst
5c227888f2 delete timesheets via api (#776) 2019-05-12 03:26:31 +02:00
Mathias
e29e183e84 added tags for timesheets (#604) 2019-05-12 01:40:04 +02:00
Kevin Papst
f31118292c translate date picker and charts (#775) 2019-05-12 00:56:00 +02:00
heddi.nabbisen
5f4a5364b0 add japanese translation (#774) 2019-05-11 22:37:36 +02:00
Kevin Papst
09da7cd242 javascript and api to stop and display active records (#772) 2019-05-10 13:45:09 +02:00
Björn Weinbrenner
e619b5fd31 added more entity actions (#757) 2019-05-10 02:10:37 +02:00
Kevin Papst
b1855447b8 recent activities via API (#761) 2019-05-07 22:24:28 +02:00
Kevin Papst
98f386dad6 refactored javascript to ES6 classes (#759) 2019-05-06 15:22:52 +02:00
Kevin Papst
80af3fb010 fix toolbar requests for empty choice (#758) 2019-05-05 08:31:13 +02:00
Kevin Papst
6e224c63ef improve cross database engine compatibility (#756) 2019-05-05 03:09:02 +02:00
Kevin Papst
aa113cc300 dynamically update title and visible durations for running records (#752) 2019-05-05 03:01:25 +02:00
Kevin Papst
65df1ff909 colors for visual grouping of customer, projects and activities (#751) 2019-05-04 01:45:52 +02:00
Kevin Papst
e92c2d168e entity action buttons re-designed (#746) 2019-05-03 21:10:16 +02:00
Kevin Papst
455d772095 moved shared entity fields and functions to trait (#750) 2019-05-02 18:03:40 +02:00
Kevin Papst
e80973c4d2 refactored column visibility modal to ES6 (#733) 2019-05-01 22:18:57 +02:00
Kevin Papst
721ecdbf3d prevent jumping icons in navbar between collapsed and open state (#739) 2019-04-30 18:50:31 +02:00
Kevin Papst
e6b3d4349e navbar block cleanup (#742) 2019-04-30 18:10:25 +02:00
Kevin Papst
b865add1b3 translate time-tracking in login screen and browser title (#738) 2019-04-30 17:52:19 +02:00
Kevin Papst
b35022d161 use bootstrap tooltips instead of own solution (#741) 2019-04-30 17:00:10 +02:00
Kevin Papst
62b091c77b always query timesheets by begin date (#740) 2019-04-30 14:31:10 +02:00
Kevin Papst
cdb112df05 theme update for swedish auth screens (#737) 2019-04-30 14:29:52 +02:00
843 changed files with 52126 additions and 19032 deletions

33
.codecov.yml Normal file
View File

@@ -0,0 +1,33 @@
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "80...100"
status:
project:
default:
threshold: 0.5%
patch:
default:
threshold: 50%
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "diff, flags, files"
behavior: default
require_changes: yes
require_base: no
require_head: yes
branches: null

View File

@@ -7,4 +7,12 @@ end_of_line = LF
[*.php]
indent_style = space
indent_size = 4
indent_size = 4
[*.yaml]
indent_style = space
indent_size = 4
[*.yml]
indent_style = space
indent_size = 4

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
#github: [kevinpapst]
custom: https://www.kimai.org/donate/

View File

@@ -7,7 +7,6 @@ A clear and concise description of what this pull request adds or changes.
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
## Checklist
- [ ] I ran `bin/console kimai:codestyle --fix` to verify the correct code style
- [ ] I have updated the [documentation](https://github.com/kimai/www.kimai.org/tree/master/_documentation) accordingly
- [ ] I have added tests to cover my changes
- [ ] I verified that my code applies to the guidelines (`composer kimai:code-check`)
- [ ] I updated the documentation (see [here](https://github.com/kimai/www.kimai.org/tree/master/_documentation))
- [ ] I agree that this code is used in Kimai and will be published under the [MIT license](https://github.com/kevinpapst/kimai2/blob/master/LICENSE)

View File

@@ -1,4 +1,5 @@
unreleased=true
future-release=0.9
future-release=1.2
exclude-labels=duplicate,question,invalid,wontfix,release
enhancement_labels=>enhancement,Enhancement,feature request
enhancement_labels=>enhancement,Enhancement,feature request,translation i18n,technical debt
issues-wo-labels=false

5
.gitignore vendored
View File

@@ -47,13 +47,14 @@ templates/invoice/renderer/.~lock*
.php_cs
.php_cs.cache
###< friendsofphp/php-cs-fixer ###
###> symfony/phpunit-bridge ###
.phpunit
/phpunit.xml
###< symfony/phpunit-bridge ###
###> symfony/webpack-encore-pack ###
###> symfony/webpack-encore-bundle ###
/node_modules/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-pack ###
###< symfony/webpack-encore-bundle ###

View File

@@ -1,46 +0,0 @@
build:
environment:
node: v8.11.2
php:
version: 7.2
ini:
date.timezone: "UTC"
nodes:
analysis:
tests:
override:
- js-scrutinizer-run
- php-scrutinizer-run
tests:
tests:
override:
-
command: 'vendor/bin/phpunit --coverage-clover=clover.xml tests/'
stop_on_failure: true
coverage:
file: 'clover.xml'
format: 'clover'
-
command: 'bin/console kimai:codestyle --checkstyle=checkstyle'
stop_on_failure: true
analysis:
file: 'checkstyle'
format: 'php-cs-checkstyle'
filter:
excluded_paths:
- '.github/'
- 'bin/'
- 'config/'
- 'public/'
- 'templates/'
- 'translations/'
- 'var/'
dependency_paths:
- 'node_modules/'
- 'vendor/'
build_failure_conditions:
- 'project.metric("scrutinizer.quality", < 9.30)'
- 'project.metric("scrutinizer.test_coverage", < 0.9)'
- 'project.metric_change("scrutinizer.test_coverage", < -0.01)'

View File

@@ -1,38 +1,78 @@
dist: xenial
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
addons:
mariadb: '10.2'
services:
- mysql
matrix:
fast_finish: true
include:
- php: 7.2
- php: 7.3
- stage: Code quality
env: CODECOVERAGE=0 DB=sqlite
php: '7.2'
script:
- composer validate --no-check-all --strict
- composer kimai:codestyle
- composer kimai:phpstan
- stage: Test
php: '7.2'
env: CODECOVERAGE=0 DB=mysql
sudo: required
services:
- mysql
- stage: Test
php: '7.3'
env: CODECOVERAGE=0 DB=mysql
sudo: required
services:
- mysql
- stage: Test
php: '7.2'
env: CODECOVERAGE=0 DB=sqlite
- stage: Test
#php: '7.4snapshot'
php: '7.3'
env: CODECOVERAGE=0 DB=sqlite
- stage: Test
php: '7.2'
env: CODECOVERAGE=0 DB=mariadb
addons:
mariadb: '10.2'
services:
- mysql
before_script:
- mysql -u root -e 'CREATE USER IF NOT EXISTS travis@localhost; GRANT ALL ON *.* TO travis@localhost;'
- stage: Test
php: '7.3'
env: CODECOVERAGE=0 DB=mariadb
addons:
mariadb: '10.2'
services:
- mysql
before_script:
- mysql -u root -e 'CREATE USER IF NOT EXISTS travis@localhost; GRANT ALL ON *.* TO travis@localhost;'
- stage: Code coverage
php: '7.3'
env: CODECOVERAGE=1 DB=sqlite
after_success:
- if [[ $CODECOVERAGE == 1 ]]; then bash <(curl -s https://codecov.io/bash); fi
before_install:
- phpenv config-rm xdebug.ini
- if [[ $CODECOVERAGE == 0 ]]; then phpenv config-rm xdebug.ini; fi;
- composer self-update
- php -i
- php -m
install:
- composer install
- composer require zendframework/zend-ldap
script:
- php -r "echo date(DATE_RFC850);"
- vendor/bin/phpunit tests/
- cp tests/.env.dist.sqlite .env
- bin/console doctrine:database:create -n
- bin/console doctrine:migrations:migrate -n
- bin/console doctrine:migrations:migrate first -n
- cp tests/.env.dist.mysql .env
- if [[ $CODECOVERAGE == 1 ]]; then vendor/bin/phpunit tests/ --coverage-clover=coverage.xml; fi;
- if [[ $CODECOVERAGE == 0 ]]; then vendor/bin/phpunit tests/; fi;
- if [[ $DB == 'sqlite' ]]; then cp tests/.env.dist.sqlite .env; fi;
- if [[ $DB == "mysql" || $DB == "mariadb" ]]; then cp tests/.env.dist.mysql .env; fi;
- bin/console doctrine:database:create -n
- bin/console doctrine:migrations:migrate -n
- bin/console doctrine:migrations:migrate first -n

View File

@@ -1,677 +1,75 @@
# Change Log
## [1.2](https://github.com/kevinpapst/kimai2/tree/1.2) (2019-08-28)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.1...1.2)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/1.2)
## [1.1](https://github.com/kevinpapst/kimai2/tree/1.1) (2019-07-24)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.0.1...1.1)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/1.1)
## [1.0.1](https://github.com/kevinpapst/kimai2/tree/1.0.1) (2019-07-05)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.0...1.0.1)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/1.0.1)
## [1.0](https://github.com/kevinpapst/kimai2/tree/1.0) (2019-07-02)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.9...1.0)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/1.0)
## [0.9](https://github.com/kevinpapst/kimai2/tree/0.9) (2019-04-29)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.8.1...0.9)
**Implemented enhancements:**
- Translation to Swedish [\#731](https://github.com/kevinpapst/kimai2/issues/731)
- Remove active flag in user preferences form for non admin user [\#694](https://github.com/kevinpapst/kimai2/issues/694)
- Add floor and ceil rounding [\#692](https://github.com/kevinpapst/kimai2/issues/692)
- POST/PUT API endpoints for projects and activities [\#683](https://github.com/kevinpapst/kimai2/issues/683)
- API is not able to handle CORS [\#646](https://github.com/kevinpapst/kimai2/issues/646)
- Recompile the frontend assets for usage in a sub-directory [\#635](https://github.com/kevinpapst/kimai2/issues/635)
- Remove database table prefix [\#633](https://github.com/kevinpapst/kimai2/issues/633)
- Autofocus on first entry of forms [\#620](https://github.com/kevinpapst/kimai2/issues/620)
- Activity admin - filter for globals [\#598](https://github.com/kevinpapst/kimai2/issues/598)
- Non visible customer/projects etc are shown in dropdowns [\#597](https://github.com/kevinpapst/kimai2/issues/597)
- remove duplicate confirm dialog of deletion [\#591](https://github.com/kevinpapst/kimai2/issues/591)
- remove mobile navbar header [\#588](https://github.com/kevinpapst/kimai2/issues/588)
- Support 12h time format [\#578](https://github.com/kevinpapst/kimai2/issues/578)
- add timerange filter to timesheet export api [\#577](https://github.com/kevinpapst/kimai2/issues/577)
- Extend rounding options with "to closest" mode [\#576](https://github.com/kevinpapst/kimai2/issues/576)
- Custom CSS [\#516](https://github.com/kevinpapst/kimai2/issues/516)
- reduce config complexity by converting php format to js format [\#734](https://github.com/kevinpapst/kimai2/pull/734) ([kevinpapst](https://github.com/kevinpapst))
- Updated authorization screens [\#727](https://github.com/kevinpapst/kimai2/pull/727) ([kevinpapst](https://github.com/kevinpapst))
- added delete timesheet API endpoint [\#726](https://github.com/kevinpapst/kimai2/pull/726) ([kevinpapst](https://github.com/kevinpapst))
- Design updates [\#723](https://github.com/kevinpapst/kimai2/pull/723) ([kevinpapst](https://github.com/kevinpapst))
- added api/activities POST and PATCH [\#717](https://github.com/kevinpapst/kimai2/pull/717) ([horlabs](https://github.com/horlabs))
- allow to overwrite config via prepend [\#715](https://github.com/kevinpapst/kimai2/pull/715) ([kevinpapst](https://github.com/kevinpapst))
- Load export type ids dynamically [\#703](https://github.com/kevinpapst/kimai2/pull/703) ([simone-gasparini](https://github.com/simone-gasparini))
- fixing default env var for cors header [\#656](https://github.com/kevinpapst/kimai2/pull/656) ([kevinpapst](https://github.com/kevinpapst))
- added cors bundle to allow external api access [\#655](https://github.com/kevinpapst/kimai2/pull/655) ([kevinpapst](https://github.com/kevinpapst))
- added route to redirect locale to configured homepage [\#654](https://github.com/kevinpapst/kimai2/pull/654) ([kevinpapst](https://github.com/kevinpapst))
- added database driven system configurations with admin screen [\#647](https://github.com/kevinpapst/kimai2/pull/647) ([kevinpapst](https://github.com/kevinpapst))
- added new rounding mode: closest [\#611](https://github.com/kevinpapst/kimai2/pull/611) ([kevinpapst](https://github.com/kevinpapst))
- order datatable items by name [\#594](https://github.com/kevinpapst/kimai2/pull/594) ([kevinpapst](https://github.com/kevinpapst))
- UI improvements for datatables [\#593](https://github.com/kevinpapst/kimai2/pull/593) ([kevinpapst](https://github.com/kevinpapst))
- Plugins support [\#592](https://github.com/kevinpapst/kimai2/pull/592) ([kevinpapst](https://github.com/kevinpapst))
**Fixed bugs:**
- 500 \(Internal Server Error\) create customer [\#711](https://github.com/kevinpapst/kimai2/issues/711)
- Export date parsing does not care about timezone [\#707](https://github.com/kevinpapst/kimai2/issues/707)
- API docs are wrong for GET entity \(ignores serialization group\) [\#701](https://github.com/kevinpapst/kimai2/issues/701)
- API date and datetime format [\#700](https://github.com/kevinpapst/kimai2/issues/700)
- totals do not match [\#691](https://github.com/kevinpapst/kimai2/issues/691)
- Validate email when creating user via cmd-line [\#663](https://github.com/kevinpapst/kimai2/issues/663)
- Datepicker "From" not working [\#651](https://github.com/kevinpapst/kimai2/issues/651)
- Error export PDF [\#648](https://github.com/kevinpapst/kimai2/issues/648)
- Error on importing v1 timeSheets [\#645](https://github.com/kevinpapst/kimai2/issues/645)
- Time selection dropdown not working in Firefox [\#618](https://github.com/kevinpapst/kimai2/issues/618)
- Revoke of start\_own\_timesheet permission doesn't prevent start active entry [\#606](https://github.com/kevinpapst/kimai2/issues/606)
- remove invalid cache header [\#722](https://github.com/kevinpapst/kimai2/pull/722) ([kevinpapst](https://github.com/kevinpapst))
- API: changed date-format, camelCase instead of snake\_case, null values, update and create for customer and project [\#718](https://github.com/kevinpapst/kimai2/pull/718) ([kevinpapst](https://github.com/kevinpapst))
- fix modal - autofocus problem, datepicker not shown on begin date [\#653](https://github.com/kevinpapst/kimai2/pull/653) ([kevinpapst](https://github.com/kevinpapst))
- fix pdf export cache directory [\#650](https://github.com/kevinpapst/kimai2/pull/650) ([kevinpapst](https://github.com/kevinpapst))
**Closed issues:**
- 403 error after installation [\#725](https://github.com/kevinpapst/kimai2/issues/725)
- AppExtension Error after bin/console cache:clear --env=prod on Line 43 [\#721](https://github.com/kevinpapst/kimai2/issues/721)
- Kimai-v1 Import Failure [\#719](https://github.com/kevinpapst/kimai2/issues/719)
- Review export lifecycle for plugin injection [\#702](https://github.com/kevinpapst/kimai2/issues/702)
- Missing database-table kimai2\_configuration while trying to run import/migration script [\#698](https://github.com/kevinpapst/kimai2/issues/698)
- Import from v1 fails if two users have the same email or blank email [\#688](https://github.com/kevinpapst/kimai2/issues/688)
- mysql limit of max amount of activities ? [\#681](https://github.com/kevinpapst/kimai2/issues/681)
- Filter by project broken [\#670](https://github.com/kevinpapst/kimai2/issues/670)
- Group of user [\#669](https://github.com/kevinpapst/kimai2/issues/669)
- Demo not loading [\#665](https://github.com/kevinpapst/kimai2/issues/665)
- Admin Panel [\#662](https://github.com/kevinpapst/kimai2/issues/662)
- mixed date formats in the api for the timesheet [\#658](https://github.com/kevinpapst/kimai2/issues/658)
- Some activites don't show up in last activities [\#609](https://github.com/kevinpapst/kimai2/issues/609)
- Wrong link in the CSS [\#605](https://github.com/kevinpapst/kimai2/issues/605)
- Failed to import users: failed to validate user [\#600](https://github.com/kevinpapst/kimai2/issues/600)
- Edit timesheet , delete user edit own timesheet [\#498](https://github.com/kevinpapst/kimai2/issues/498)
- Add demo bundle [\#442](https://github.com/kevinpapst/kimai2/issues/442)
- Add audit logs [\#62](https://github.com/kevinpapst/kimai2/issues/62)
**Merged pull requests:**
- added svenska [\#732](https://github.com/kevinpapst/kimai2/pull/732) ([kevinpapst](https://github.com/kevinpapst))
- apply users timezone to toolbar queries [\#716](https://github.com/kevinpapst/kimai2/pull/716) ([kevinpapst](https://github.com/kevinpapst))
- remove unused role ROLE\_CUSTOMER [\#712](https://github.com/kevinpapst/kimai2/pull/712) ([kevinpapst](https://github.com/kevinpapst))
- Update to Symfony 4.2 [\#710](https://github.com/kevinpapst/kimai2/pull/710) ([kevinpapst](https://github.com/kevinpapst))
- fix validation for create user command [\#709](https://github.com/kevinpapst/kimai2/pull/709) ([kevinpapst](https://github.com/kevinpapst))
- remove the total counter from all admin pages [\#708](https://github.com/kevinpapst/kimai2/pull/708) ([kevinpapst](https://github.com/kevinpapst))
- removed active flag in user preferences form for non admin user. \(\#694\) [\#696](https://github.com/kevinpapst/kimai2/pull/696) ([simone-gasparini](https://github.com/simone-gasparini))
- added floor and ceil rounding with tests \(\#692\) [\#695](https://github.com/kevinpapst/kimai2/pull/695) ([simone-gasparini](https://github.com/simone-gasparini))
- fixed activity-project assignment for kimai v1 importer [\#679](https://github.com/kevinpapst/kimai2/pull/679) ([kevinpapst](https://github.com/kevinpapst))
- added content-type to allowed headers for api [\#672](https://github.com/kevinpapst/kimai2/pull/672) ([hmr-it-jr](https://github.com/hmr-it-jr))
- added plugin screen [\#671](https://github.com/kevinpapst/kimai2/pull/671) ([kevinpapst](https://github.com/kevinpapst))
- Updated hungarian translation [\#661](https://github.com/kevinpapst/kimai2/pull/661) ([infeeeee](https://github.com/infeeeee))
- remove mailer url from about debug info [\#657](https://github.com/kevinpapst/kimai2/pull/657) ([kevinpapst](https://github.com/kevinpapst))
- delete directory with invalid case [\#643](https://github.com/kevinpapst/kimai2/pull/643) ([kevinpapst](https://github.com/kevinpapst))
- added command to create release packages \(for FTP user\) [\#642](https://github.com/kevinpapst/kimai2/pull/642) ([kevinpapst](https://github.com/kevinpapst))
- removed dynamic database prefix [\#641](https://github.com/kevinpapst/kimai2/pull/641) ([kevinpapst](https://github.com/kevinpapst))
- set autofocus attribute on form fields for better keyboard support [\#640](https://github.com/kevinpapst/kimai2/pull/640) ([kevinpapst](https://github.com/kevinpapst))
- API - added begin end and export filter for timesheets [\#639](https://github.com/kevinpapst/kimai2/pull/639) ([kevinpapst](https://github.com/kevinpapst))
- replaced delete confirm dialog with modal [\#638](https://github.com/kevinpapst/kimai2/pull/638) ([kevinpapst](https://github.com/kevinpapst))
- convert-timezone: add a default value 'y' to the asked question [\#637](https://github.com/kevinpapst/kimai2/pull/637) ([blueowl04](https://github.com/blueowl04))
- support subdirectories and subdomains for assets [\#636](https://github.com/kevinpapst/kimai2/pull/636) ([kevinpapst](https://github.com/kevinpapst))
- improved plugin support [\#634](https://github.com/kevinpapst/kimai2/pull/634) ([kevinpapst](https://github.com/kevinpapst))
- fix random order in recent activities [\#631](https://github.com/kevinpapst/kimai2/pull/631) ([kevinpapst](https://github.com/kevinpapst))
- fix for firefox datepicker focus problem in modals [\#630](https://github.com/kevinpapst/kimai2/pull/630) ([kevinpapst](https://github.com/kevinpapst))
- remove invisible entities from toolbars [\#628](https://github.com/kevinpapst/kimai2/pull/628) ([kevinpapst](https://github.com/kevinpapst))
- removed inline documentation [\#624](https://github.com/kevinpapst/kimai2/pull/624) ([kevinpapst](https://github.com/kevinpapst))
- added support for Ante meridiem and Post meridiem format [\#615](https://github.com/kevinpapst/kimai2/pull/615) ([kevinpapst](https://github.com/kevinpapst))
- added filter for global activities [\#614](https://github.com/kevinpapst/kimai2/pull/614) ([kevinpapst](https://github.com/kevinpapst))
- Improve create and start permission handling [\#613](https://github.com/kevinpapst/kimai2/pull/613) ([kevinpapst](https://github.com/kevinpapst))
- Edit and create timesheet records in modal [\#603](https://github.com/kevinpapst/kimai2/pull/603) ([kevinpapst](https://github.com/kevinpapst))
- code cleanup [\#602](https://github.com/kevinpapst/kimai2/pull/602) ([kevinpapst](https://github.com/kevinpapst))
- simplify theme settings in user profile [\#586](https://github.com/kevinpapst/kimai2/pull/586) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.9)
## [0.8.1](https://github.com/kevinpapst/kimai2/tree/0.8.1) (2019-02-22)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.8...0.8.1)
**Fixed bugs:**
- resize mobile navbar buttons [\#582](https://github.com/kevinpapst/kimai2/issues/582)
- deletion of customers leads to internal server error 500 [\#589](https://github.com/kevinpapst/kimai2/issues/589)
- Entry editor label wrong \(from/to\) [\#587](https://github.com/kevinpapst/kimai2/issues/587)
**Merged pull requests:**
- Support foreign keys with SQLite [\#590](https://github.com/kevinpapst/kimai2/pull/590) ([kevinpapst](https://github.com/kevinpapst))
- post release fixes [\#584](https://github.com/kevinpapst/kimai2/pull/584) ([kevinpapst](https://github.com/kevinpapst))
- Mobile navbar size [\#583](https://github.com/kevinpapst/kimai2/pull/583) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.8.1)
## [0.8](https://github.com/kevinpapst/kimai2/tree/0.8) (2019-02-19)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.7...0.8)
**Implemented enhancements:**
- Export other users' timesheets using the API [\#562](https://github.com/kevinpapst/kimai2/issues/562)
- Some importer bugs/improvements [\#553](https://github.com/kevinpapst/kimai2/issues/553)
- Export timesheet only exports entries on the current page [\#534](https://github.com/kevinpapst/kimai2/issues/534)
- On the last activities dropdown show tha last ten DIFFERENT activities [\#533](https://github.com/kevinpapst/kimai2/issues/533)
- Better user expierence on "My Times" [\#526](https://github.com/kevinpapst/kimai2/issues/526)
- Highlight current day in datepickers and not only the selected day [\#522](https://github.com/kevinpapst/kimai2/issues/522)
- Configuration of first page after login [\#501](https://github.com/kevinpapst/kimai2/issues/501)
- Visual Grouping of entry from same day or same week with some stats of that group [\#495](https://github.com/kevinpapst/kimai2/issues/495)
**Fixed bugs:**
- Error 404 on 'My times' page 2+ if the time range is changed to something smaller [\#558](https://github.com/kevinpapst/kimai2/issues/558)
- Timezone is not working correctly [\#554](https://github.com/kevinpapst/kimai2/issues/554)
- New time picker not working on languages without updated translation [\#546](https://github.com/kevinpapst/kimai2/issues/546)
- Export timesheet only exports entries on the current page [\#534](https://github.com/kevinpapst/kimai2/issues/534)
- Kimai2 will not allow me to enter times in the future [\#531](https://github.com/kevinpapst/kimai2/issues/531)
- No projects in filter section [\#525](https://github.com/kevinpapst/kimai2/issues/525)
- This value should be greater than or equal to zero [\#511](https://github.com/kevinpapst/kimai2/issues/511)
- Wrong time after migration kimai v1 data [\#507](https://github.com/kevinpapst/kimai2/issues/507)
- SQL-Error for getRecentActivities\(\) caused by GROUP BY [\#488](https://github.com/kevinpapst/kimai2/issues/488)
- fix sql for only\_full\_group\_by [\#581](https://github.com/kevinpapst/kimai2/pull/581) ([kevinpapst](https://github.com/kevinpapst))
**Closed issues:**
- Feedback for improving installation setup [\#541](https://github.com/kevinpapst/kimai2/issues/541)
- template entry.end\_time shows empty field [\#535](https://github.com/kevinpapst/kimai2/issues/535)
**Merged pull requests:**
- handle deleted user during import from v1 [\#569](https://github.com/kevinpapst/kimai2/pull/569) ([kevinpapst](https://github.com/kevinpapst))
- fix pagination in combination with daterange picker [\#568](https://github.com/kevinpapst/kimai2/pull/568) ([kevinpapst](https://github.com/kevinpapst))
- allow to query other users timesheets via api [\#563](https://github.com/kevinpapst/kimai2/pull/563) ([kevinpapst](https://github.com/kevinpapst))
- Updated HTML invoice templates [\#560](https://github.com/kevinpapst/kimai2/pull/560) ([kevinpapst](https://github.com/kevinpapst))
- updated composer packages [\#559](https://github.com/kevinpapst/kimai2/pull/559) ([kevinpapst](https://github.com/kevinpapst))
- fix timezone problems in timesheet forms [\#555](https://github.com/kevinpapst/kimai2/pull/555) ([kevinpapst](https://github.com/kevinpapst))
- Daily stats in timesheet [\#552](https://github.com/kevinpapst/kimai2/pull/552) ([kevinpapst](https://github.com/kevinpapst))
- Added more php requirements to documentation [\#551](https://github.com/kevinpapst/kimai2/pull/551) ([infeeeee](https://github.com/infeeeee))
- improve recent activities [\#550](https://github.com/kevinpapst/kimai2/pull/550) ([kevinpapst](https://github.com/kevinpapst))
- scss fixes: year selector width, dropdown menu width, navbar refactoring [\#549](https://github.com/kevinpapst/kimai2/pull/549) ([infeeeee](https://github.com/infeeeee))
- improved installation docs [\#548](https://github.com/kevinpapst/kimai2/pull/548) ([kevinpapst](https://github.com/kevinpapst))
- fix daterange-picker for fr, hu and ar [\#547](https://github.com/kevinpapst/kimai2/pull/547) ([kevinpapst](https://github.com/kevinpapst))
- do not limit users timesheet export page size [\#545](https://github.com/kevinpapst/kimai2/pull/545) ([kevinpapst](https://github.com/kevinpapst))
- Use Symfony formatter for currency symbol placement [\#542](https://github.com/kevinpapst/kimai2/pull/542) ([sanjitlpatel](https://github.com/sanjitlpatel))
- Localized date-inputs and daterange-picker [\#540](https://github.com/kevinpapst/kimai2/pull/540) ([kevinpapst](https://github.com/kevinpapst))
- added export module [\#538](https://github.com/kevinpapst/kimai2/pull/538) ([kevinpapst](https://github.com/kevinpapst))
- updated documentation [\#536](https://github.com/kevinpapst/kimai2/pull/536) ([kevinpapst](https://github.com/kevinpapst))
- support remote data in beta-test selectpicker [\#529](https://github.com/kevinpapst/kimai2/pull/529) ([kevinpapst](https://github.com/kevinpapst))
- Improve allowed running records [\#528](https://github.com/kevinpapst/kimai2/pull/528) ([kevinpapst](https://github.com/kevinpapst))
- Improve daterangepicker [\#527](https://github.com/kevinpapst/kimai2/pull/527) ([kevinpapst](https://github.com/kevinpapst))
- added configurable view after login [\#523](https://github.com/kevinpapst/kimai2/pull/523) ([kevinpapst](https://github.com/kevinpapst))
- convert timesheets to UTC with support for user timezone [\#372](https://github.com/kevinpapst/kimai2/pull/372) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.8)
## [0.7](https://github.com/kevinpapst/kimai2/tree/0.7) (2019-01-28)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.6.1...0.7)
**Implemented enhancements:**
- Editing from calendar view will return to my times table instead of calendar [\#515](https://github.com/kevinpapst/kimai2/issues/515)
- Timesheet Export for Admins [\#503](https://github.com/kevinpapst/kimai2/issues/503)
- Customer List Not Alphabetic [\#499](https://github.com/kevinpapst/kimai2/issues/499)
- sorting of ${entry.X} values [\#487](https://github.com/kevinpapst/kimai2/issues/487)
- ${entry.description} needed in Word-Docx [\#485](https://github.com/kevinpapst/kimai2/issues/485)
- Automatic sort for activities, customers, etc. [\#477](https://github.com/kevinpapst/kimai2/issues/477)
- User title in the timesheet invoice [\#461](https://github.com/kevinpapst/kimai2/issues/461)
- global variables for reports/invoices [\#438](https://github.com/kevinpapst/kimai2/issues/438)
- Configuration option: Only one active record for each user [\#427](https://github.com/kevinpapst/kimai2/issues/427)
- User creating activity and projects [\#393](https://github.com/kevinpapst/kimai2/issues/393)
- Configuration option to disable fixed rate and hourly rate from "edit timesheet" [\#330](https://github.com/kevinpapst/kimai2/issues/330)
- Set other users hourly rate [\#303](https://github.com/kevinpapst/kimai2/issues/303)
- Feature request - Make "Rate" hideable [\#217](https://github.com/kevinpapst/kimai2/issues/217)
- fixed null project for advanced invoice calculator [\#462](https://github.com/kevinpapst/kimai2/pull/462) ([kevinpapst](https://github.com/kevinpapst))
**Fixed bugs:**
- Timesheet Export for Admins [\#503](https://github.com/kevinpapst/kimai2/issues/503)
- admin activity: visibility "none" \(no filter\) causes sql-error [\#491](https://github.com/kevinpapst/kimai2/issues/491)
- login-screen optimizations [\#483](https://github.com/kevinpapst/kimai2/issues/483)
- Configuration of roles to add/edit customers, projects, activities... [\#479](https://github.com/kevinpapst/kimai2/issues/479)
- Line breaks for address and payment information fields [\#464](https://github.com/kevinpapst/kimai2/issues/464)
- Possible to use a decimal in hourly rate field? [\#458](https://github.com/kevinpapst/kimai2/issues/458)
- Invoice Number Generator possibly not compatible [\#454](https://github.com/kevinpapst/kimai2/issues/454)
- users can change their role to system-admin [\#440](https://github.com/kevinpapst/kimai2/issues/440)
- fix wrong include filename in user registration [\#520](https://github.com/kevinpapst/kimai2/pull/520) ([kevinpapst](https://github.com/kevinpapst))
- fix segmentation fault - rollback composer dependencies [\#463](https://github.com/kevinpapst/kimai2/pull/463) ([kevinpapst](https://github.com/kevinpapst))
- fixed null project for advanced invoice calculator [\#462](https://github.com/kevinpapst/kimai2/pull/462) ([kevinpapst](https://github.com/kevinpapst))
- fix the restart timesheet button [\#436](https://github.com/kevinpapst/kimai2/pull/436) ([kevinpapst](https://github.com/kevinpapst))
**Closed issues:**
- Redirecting when using Kimai on a subdirectory + reverse proxy [\#492](https://github.com/kevinpapst/kimai2/issues/492)
- Installation \(Cannot declare.... in use\) [\#455](https://github.com/kevinpapst/kimai2/issues/455)
- New Number Generator not recognized [\#453](https://github.com/kevinpapst/kimai2/issues/453)
- de/help/invoices returns 404 error [\#452](https://github.com/kevinpapst/kimai2/issues/452)
- Can you Add Brazilian Portuguese Translation? I can help it.. [\#444](https://github.com/kevinpapst/kimai2/issues/444)
- replace all selects with smart-selects and live-search [\#441](https://github.com/kevinpapst/kimai2/issues/441)
- use parsedown-extra for rendering markdown [\#388](https://github.com/kevinpapst/kimai2/issues/388)
- Think about a cooler name [\#133](https://github.com/kevinpapst/kimai2/issues/133)
**Merged pull requests:**
- pagination without reload while keeping filters applied [\#521](https://github.com/kevinpapst/kimai2/pull/521) ([kevinpapst](https://github.com/kevinpapst))
- go back to calendar after editing and creation of time-records [\#519](https://github.com/kevinpapst/kimai2/pull/519) ([kevinpapst](https://github.com/kevinpapst))
- fetch toolbar results without page reload [\#518](https://github.com/kevinpapst/kimai2/pull/518) ([kevinpapst](https://github.com/kevinpapst))
- Form and theme improvements [\#513](https://github.com/kevinpapst/kimai2/pull/513) ([kevinpapst](https://github.com/kevinpapst))
- validation for future and negative times [\#512](https://github.com/kevinpapst/kimai2/pull/512) ([kevinpapst](https://github.com/kevinpapst))
- alphabetical order for selectboxes [\#510](https://github.com/kevinpapst/kimai2/pull/510) ([kevinpapst](https://github.com/kevinpapst))
- Export team timesheets [\#508](https://github.com/kevinpapst/kimai2/pull/508) ([kevinpapst](https://github.com/kevinpapst))
- fix broken sql in activity admin [\#506](https://github.com/kevinpapst/kimai2/pull/506) ([kevinpapst](https://github.com/kevinpapst))
- display invoice entries in ascending order [\#505](https://github.com/kevinpapst/kimai2/pull/505) ([kevinpapst](https://github.com/kevinpapst))
- Improve DOCX template row [\#504](https://github.com/kevinpapst/kimai2/pull/504) ([kevinpapst](https://github.com/kevinpapst))
- do not display admin menu if it has no children [\#500](https://github.com/kevinpapst/kimai2/pull/500) ([kevinpapst](https://github.com/kevinpapst))
- login-screen optimizations [\#493](https://github.com/kevinpapst/kimai2/pull/493) ([lduer](https://github.com/lduer))
- preg\_replace for md-file-extensions when rendering correct link… [\#482](https://github.com/kevinpapst/kimai2/pull/482) ([lduer](https://github.com/lduer))
- Better composer install in the docker [\#481](https://github.com/kevinpapst/kimai2/pull/481) ([tobybatch](https://github.com/tobybatch))
- Update dockerfile for https in composer installer [\#478](https://github.com/kevinpapst/kimai2/pull/478) ([scolson](https://github.com/scolson))
- support line breaks in docx [\#466](https://github.com/kevinpapst/kimai2/pull/466) ([kevinpapst](https://github.com/kevinpapst))
- updated README and docu [\#465](https://github.com/kevinpapst/kimai2/pull/465) ([kevinpapst](https://github.com/kevinpapst))
- allow decimals in users hourly rate [\#460](https://github.com/kevinpapst/kimai2/pull/460) ([kevinpapst](https://github.com/kevinpapst))
- updated all composer packages [\#459](https://github.com/kevinpapst/kimai2/pull/459) ([kevinpapst](https://github.com/kevinpapst))
- moved some packages to dev requirements [\#456](https://github.com/kevinpapst/kimai2/pull/456) ([kevinpapst](https://github.com/kevinpapst))
- Added portuguese translations [\#446](https://github.com/kevinpapst/kimai2/pull/446) ([marquesmatheus](https://github.com/marquesmatheus))
- Simplify timesheet edit form if only one customer is existing [\#443](https://github.com/kevinpapst/kimai2/pull/443) ([kevinpapst](https://github.com/kevinpapst))
- added project variables for invoice templates [\#439](https://github.com/kevinpapst/kimai2/pull/439) ([kevinpapst](https://github.com/kevinpapst))
- added configurable permission system [\#424](https://github.com/kevinpapst/kimai2/pull/424) ([kevinpapst](https://github.com/kevinpapst))
- Only one running timesheet: automatically stop others [\#386](https://github.com/kevinpapst/kimai2/pull/386) ([lduer](https://github.com/lduer))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.7)
## [0.6.1](https://github.com/kevinpapst/kimai2/tree/0.6.1) (2018-11-19)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.6...0.6.1)
**Fixed bugs:**
- Error importing v1 Database [\#428](https://github.com/kevinpapst/kimai2/issues/428)
- Redo button missing? [\#426](https://github.com/kevinpapst/kimai2/issues/426)
**Closed issues:**
- Error importing v1 Database \(could not convert to string\) [\#431](https://github.com/kevinpapst/kimai2/issues/431)
**Merged pull requests:**
- fix migration for MySQL [\#430](https://github.com/kevinpapst/kimai2/pull/430) ([kevinpapst](https://github.com/kevinpapst))
- fix importer for MySQL [\#429](https://github.com/kevinpapst/kimai2/pull/429) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.6.1)
## [0.6](https://github.com/kevinpapst/kimai2/tree/0.6) (2018-11-18)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.5...0.6)
**Implemented enhancements:**
- Language setting does not work immediately [\#418](https://github.com/kevinpapst/kimai2/issues/418)
- Merge projects and/or activities [\#406](https://github.com/kevinpapst/kimai2/issues/406)
- Global activities support for Kimai 1 importer [\#400](https://github.com/kevinpapst/kimai2/issues/400)
- Invoice Grouped by Activity [\#379](https://github.com/kevinpapst/kimai2/issues/379)
- Set the order of columns \(in my times page\) [\#371](https://github.com/kevinpapst/kimai2/issues/371)
- Columns on small/narrow displays \(mobile\) on „my times“ page [\#370](https://github.com/kevinpapst/kimai2/issues/370)
- Make settings more accessible [\#365](https://github.com/kevinpapst/kimai2/issues/365)
- Data validation for register page username field. [\#360](https://github.com/kevinpapst/kimai2/issues/360)
- Option for setting default calendar view [\#359](https://github.com/kevinpapst/kimai2/issues/359)
- Activities just Duration [\#334](https://github.com/kevinpapst/kimai2/issues/334)
- API: Add filtering/sorting/limits [\#333](https://github.com/kevinpapst/kimai2/issues/333)
- API: Timesheets [\#315](https://github.com/kevinpapst/kimai2/issues/315)
- Feature request: Quick search box for activities when creating new timesheet [\#307](https://github.com/kevinpapst/kimai2/issues/307)
- Separate Customer / Project / Activity input when logging time [\#250](https://github.com/kevinpapst/kimai2/issues/250)
- Interactive help after fresh installation [\#94](https://github.com/kevinpapst/kimai2/issues/94)
**Fixed bugs:**
- Invoicing export - 500: Internal Server Error [\#390](https://github.com/kevinpapst/kimai2/issues/390)
- invalid CSRF-Token [\#344](https://github.com/kevinpapst/kimai2/issues/344)
- fix column visibility if unconfigured \(no cookie existing\) [\#423](https://github.com/kevinpapst/kimai2/pull/423) ([kevinpapst](https://github.com/kevinpapst))
- fix duration and rate for html invoices [\#414](https://github.com/kevinpapst/kimai2/pull/414) ([kevinpapst](https://github.com/kevinpapst))
- fixed broken toolbar form with empty date [\#391](https://github.com/kevinpapst/kimai2/pull/391) ([kevinpapst](https://github.com/kevinpapst))
**Closed issues:**
- After installation no graphic/background/icons shown [\#417](https://github.com/kevinpapst/kimai2/issues/417)
- Form fields should be always visible [\#408](https://github.com/kevinpapst/kimai2/issues/408)
- Active filter should stay open [\#405](https://github.com/kevinpapst/kimai2/issues/405)
- Make project field optional [\#401](https://github.com/kevinpapst/kimai2/issues/401)
- Unrecognized Options "renderer, number\_generator" under "kimai.invoice" [\#381](https://github.com/kevinpapst/kimai2/issues/381)
- 500: Internal Server Error after login \(new install\) [\#342](https://github.com/kevinpapst/kimai2/issues/342)
- Invoices export error [\#341](https://github.com/kevinpapst/kimai2/issues/341)
- add the same activity for multiple projects [\#325](https://github.com/kevinpapst/kimai2/issues/325)
- API: Endpoint for API docs needs session cookie [\#319](https://github.com/kevinpapst/kimai2/issues/319)
- docker image for kimai2 [\#284](https://github.com/kevinpapst/kimai2/issues/284)
**Merged pull requests:**
- redirect to user language after profile update [\#421](https://github.com/kevinpapst/kimai2/pull/421) ([kevinpapst](https://github.com/kevinpapst))
- added more timesheet related invoice fields [\#411](https://github.com/kevinpapst/kimai2/pull/411) ([kevinpapst](https://github.com/kevinpapst))
- toolbar improvements [\#410](https://github.com/kevinpapst/kimai2/pull/410) ([kevinpapst](https://github.com/kevinpapst))
- move entries from one entity to another upon deletion [\#409](https://github.com/kevinpapst/kimai2/pull/409) ([kevinpapst](https://github.com/kevinpapst))
- show toolbar filter if they were submitted [\#407](https://github.com/kevinpapst/kimai2/pull/407) ([kevinpapst](https://github.com/kevinpapst))
- full configurable data columns in all screen sizes [\#404](https://github.com/kevinpapst/kimai2/pull/404) ([kevinpapst](https://github.com/kevinpapst))
- added global activity support in importer [\#402](https://github.com/kevinpapst/kimai2/pull/402) ([kevinpapst](https://github.com/kevinpapst))
- added parsedown extension for header ids \(\#388\) [\#399](https://github.com/kevinpapst/kimai2/pull/399) ([lduer](https://github.com/lduer))
- Fix for \#397 [\#398](https://github.com/kevinpapst/kimai2/pull/398) ([tobybatch](https://github.com/tobybatch))
- Added links to docker hub and my docker repo [\#396](https://github.com/kevinpapst/kimai2/pull/396) ([tobybatch](https://github.com/tobybatch))
- fixed issues- & milestone links [\#395](https://github.com/kevinpapst/kimai2/pull/395) ([lduer](https://github.com/lduer))
- added links to further docker images by the community [\#394](https://github.com/kevinpapst/kimai2/pull/394) ([kevinpapst](https://github.com/kevinpapst))
- beta test: added quick search box for customers, projects and activities [\#392](https://github.com/kevinpapst/kimai2/pull/392) ([kevinpapst](https://github.com/kevinpapst))
- updated the documentation, reflecting the changes in \#306 [\#387](https://github.com/kevinpapst/kimai2/pull/387) ([lduer](https://github.com/lduer))
- Added Hungarian translation and fixed the path of kimai.yaml in translation documentation [\#385](https://github.com/kevinpapst/kimai2/pull/385) ([infeeeee](https://github.com/infeeeee))
- improved docs and cross-linked docker [\#383](https://github.com/kevinpapst/kimai2/pull/383) ([kevinpapst](https://github.com/kevinpapst))
- Doc update of install/update instructions re: username [\#382](https://github.com/kevinpapst/kimai2/pull/382) ([srdco](https://github.com/srdco))
- invoice calculator to group entries by activity [\#380](https://github.com/kevinpapst/kimai2/pull/380) ([kevinpapst](https://github.com/kevinpapst))
- basic about screen with server and debug infos [\#378](https://github.com/kevinpapst/kimai2/pull/378) ([kevinpapst](https://github.com/kevinpapst))
- Dev/demo dockerfile [\#377](https://github.com/kevinpapst/kimai2/pull/377) ([tobybatch](https://github.com/tobybatch))
- Sidebar - user-profile links and language settings [\#369](https://github.com/kevinpapst/kimai2/pull/369) ([kevinpapst](https://github.com/kevinpapst))
- added configurable initial calendar view [\#363](https://github.com/kevinpapst/kimai2/pull/363) ([kevinpapst](https://github.com/kevinpapst))
- decrease minimum username to 3 character [\#362](https://github.com/kevinpapst/kimai2/pull/362) ([kevinpapst](https://github.com/kevinpapst))
- Default country \(\#2\) [\#358](https://github.com/kevinpapst/kimai2/pull/358) ([tobybatch](https://github.com/tobybatch))
- composer: removed deprecated symfony/lt, updated others [\#354](https://github.com/kevinpapst/kimai2/pull/354) ([kevinpapst](https://github.com/kevinpapst))
- updated admin-lte theme bundle to 2.1.1 [\#350](https://github.com/kevinpapst/kimai2/pull/350) ([kevinpapst](https://github.com/kevinpapst))
- updated timesheet and email documentation [\#349](https://github.com/kevinpapst/kimai2/pull/349) ([kevinpapst](https://github.com/kevinpapst))
- updated required dependencies [\#346](https://github.com/kevinpapst/kimai2/pull/346) ([kevinpapst](https://github.com/kevinpapst))
- Fix password reset [\#345](https://github.com/kevinpapst/kimai2/pull/345) ([kevinpapst](https://github.com/kevinpapst))
- added arabic translations and locale support in datepicker [\#337](https://github.com/kevinpapst/kimai2/pull/337) ([kevinpapst](https://github.com/kevinpapst))
- API endpoint for Timesheet entries [\#332](https://github.com/kevinpapst/kimai2/pull/332) ([kevinpapst](https://github.com/kevinpapst))
- Support global activities [\#259](https://github.com/kevinpapst/kimai2/pull/259) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.6)
## [0.5](https://github.com/kevinpapst/kimai2/tree/0.5) (2018-09-27)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.4...0.5)
**Implemented enhancements:**
- API: Kimai metadata endpoint [\#320](https://github.com/kevinpapst/kimai2/issues/320)
- Support Markdown in timesheet description [\#295](https://github.com/kevinpapst/kimai2/issues/295)
- Add support for MS Office templates [\#283](https://github.com/kevinpapst/kimai2/issues/283)
- Adding rates to customers, projects and tasks [\#271](https://github.com/kevinpapst/kimai2/issues/271)
- Delete invoice templates [\#267](https://github.com/kevinpapst/kimai2/issues/267)
- Sum of hours in timesheet invoice [\#262](https://github.com/kevinpapst/kimai2/issues/262)
- Feature request - API [\#251](https://github.com/kevinpapst/kimai2/issues/251)
- Feature request - PDF Download for monthly report [\#244](https://github.com/kevinpapst/kimai2/issues/244)
- Add support for OpenOffice templates [\#223](https://github.com/kevinpapst/kimai2/issues/223)
- Feature Request - Excel Output of times [\#221](https://github.com/kevinpapst/kimai2/issues/221)
- Add hourly rate options [\#111](https://github.com/kevinpapst/kimai2/issues/111)
**Fixed bugs:**
- Excel Output not containing data [\#327](https://github.com/kevinpapst/kimai2/issues/327)
**Closed issues:**
- Creating database schema throws exceptions [\#322](https://github.com/kevinpapst/kimai2/issues/322)
- Add permission voter for invoices and templates [\#293](https://github.com/kevinpapst/kimai2/issues/293)
**Merged pull requests:**
- added copy invoice template action [\#331](https://github.com/kevinpapst/kimai2/pull/331) ([kevinpapst](https://github.com/kevinpapst))
- fix spreadsheet renderer for invoices with one entry [\#328](https://github.com/kevinpapst/kimai2/pull/328) ([kevinpapst](https://github.com/kevinpapst))
- invoice VAT as float and database key length [\#323](https://github.com/kevinpapst/kimai2/pull/323) ([kevinpapst](https://github.com/kevinpapst))
- added version command + api endpoint [\#321](https://github.com/kevinpapst/kimai2/pull/321) ([kevinpapst](https://github.com/kevinpapst))
- Timesheet export [\#317](https://github.com/kevinpapst/kimai2/pull/317) ([kevinpapst](https://github.com/kevinpapst))
- added invoice voter [\#316](https://github.com/kevinpapst/kimai2/pull/316) ([kevinpapst](https://github.com/kevinpapst))
- Refactored invoice system [\#306](https://github.com/kevinpapst/kimai2/pull/306) ([kevinpapst](https://github.com/kevinpapst))
- add stable controller tests [\#305](https://github.com/kevinpapst/kimai2/pull/305) ([kevinpapst](https://github.com/kevinpapst))
- Fixed rate and Hourly rates [\#304](https://github.com/kevinpapst/kimai2/pull/304) ([kevinpapst](https://github.com/kevinpapst))
- add hourly\_rate and fixed\_rate to timesheet entries [\#302](https://github.com/kevinpapst/kimai2/pull/302) ([kevinpapst](https://github.com/kevinpapst))
- allow to switch language on any page [\#299](https://github.com/kevinpapst/kimai2/pull/299) ([kevinpapst](https://github.com/kevinpapst))
- Fix duration\_short format for french translation [\#297](https://github.com/kevinpapst/kimai2/pull/297) ([jeau](https://github.com/jeau))
- allow markdown in timesheet descriptions [\#296](https://github.com/kevinpapst/kimai2/pull/296) ([kevinpapst](https://github.com/kevinpapst))
- added option to delete invoice template [\#294](https://github.com/kevinpapst/kimai2/pull/294) ([kevinpapst](https://github.com/kevinpapst))
- Improved invoice preview [\#254](https://github.com/kevinpapst/kimai2/pull/254) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.5)
## [0.4](https://github.com/kevinpapst/kimai2/tree/0.4) (2018-09-01)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.3...0.4)
**Implemented enhancements:**
- Delete user [\#225](https://github.com/kevinpapst/kimai2/issues/225)
**Fixed bugs:**
- Don't allow to stop an already stopped entry [\#282](https://github.com/kevinpapst/kimai2/issues/282)
- installed it, and then what??? [\#255](https://github.com/kevinpapst/kimai2/issues/255)
- fixed calendar for empty timesheet descriptions [\#265](https://github.com/kevinpapst/kimai2/pull/265) ([kevinpapst](https://github.com/kevinpapst))
- fixed .htaccess for apache users [\#260](https://github.com/kevinpapst/kimai2/pull/260) ([kevinpapst](https://github.com/kevinpapst))
**Closed issues:**
- Spanish Translation Contrib [\#248](https://github.com/kevinpapst/kimai2/issues/248)
- Administration of projects throws error if locale is "de" [\#247](https://github.com/kevinpapst/kimai2/issues/247)
- Web Installer Package for near-automated installs from browser [\#235](https://github.com/kevinpapst/kimai2/issues/235)
- Improve dashboard display [\#280](https://github.com/kevinpapst/kimai2/issues/280)
- Feature request - Monthly overview / report [\#207](https://github.com/kevinpapst/kimai2/issues/207)
**Merged pull requests:**
- fixed money display in dashboard widgets [\#291](https://github.com/kevinpapst/kimai2/pull/291) ([kevinpapst](https://github.com/kevinpapst))
- Symfony update 4.1.4 [\#290](https://github.com/kevinpapst/kimai2/pull/290) ([kevinpapst](https://github.com/kevinpapst))
- do not allow to stop already stopped timesheets [\#289](https://github.com/kevinpapst/kimai2/pull/289) ([kevinpapst](https://github.com/kevinpapst))
- added configurable formats for duration [\#287](https://github.com/kevinpapst/kimai2/pull/287) ([kevinpapst](https://github.com/kevinpapst))
- Updated russian translations [\#286](https://github.com/kevinpapst/kimai2/pull/286) ([kevinpapst](https://github.com/kevinpapst))
- Update french translation [\#279](https://github.com/kevinpapst/kimai2/pull/279) ([jeau](https://github.com/jeau))
- Fix standard date format in french [\#278](https://github.com/kevinpapst/kimai2/pull/278) ([jeau](https://github.com/jeau))
- new translations: spanish, french and italian [\#276](https://github.com/kevinpapst/kimai2/pull/276) ([kevinpapst](https://github.com/kevinpapst))
- French translation for Kimai [\#275](https://github.com/kevinpapst/kimai2/pull/275) ([jeau](https://github.com/jeau))
- updated to AdminLTE 2.4.8 [\#274](https://github.com/kevinpapst/kimai2/pull/274) ([kevinpapst](https://github.com/kevinpapst))
- fixing migration\_v1.md url [\#270](https://github.com/kevinpapst/kimai2/pull/270) ([ltsavar](https://github.com/ltsavar))
- dashboard widgets are configurable via config [\#269](https://github.com/kevinpapst/kimai2/pull/269) ([kevinpapst](https://github.com/kevinpapst))
- Spanish translation for Kimai [\#266](https://github.com/kevinpapst/kimai2/pull/266) ([cobaltos](https://github.com/cobaltos))
- moved theme settings to dynamic compiler pass [\#264](https://github.com/kevinpapst/kimai2/pull/264) ([kevinpapst](https://github.com/kevinpapst))
- Added config setting to disable password-reset and user-registration [\#261](https://github.com/kevinpapst/kimai2/pull/261) ([kevinpapst](https://github.com/kevinpapst))
- Added basic API endpoints [\#258](https://github.com/kevinpapst/kimai2/pull/258) ([kevinpapst](https://github.com/kevinpapst))
- Hotfix for Symfony Flex incompatibility with latest composer changes [\#257](https://github.com/kevinpapst/kimai2/pull/257) ([kevinpapst](https://github.com/kevinpapst))
- added italian translation [\#253](https://github.com/kevinpapst/kimai2/pull/253) ([kevinpapst](https://github.com/kevinpapst))
- Delete user [\#249](https://github.com/kevinpapst/kimai2/pull/249) ([kevinpapst](https://github.com/kevinpapst))
- dashboard widgets from event [\#246](https://github.com/kevinpapst/kimai2/pull/246) ([kevinpapst](https://github.com/kevinpapst))
- Added timesheet-calendar view [\#236](https://github.com/kevinpapst/kimai2/pull/236) ([kevinpapst](https://github.com/kevinpapst))
- Improved data fixtures [\#234](https://github.com/kevinpapst/kimai2/pull/234) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.4)
## [0.3](https://github.com/kevinpapst/kimai2/tree/0.3) (2018-07-22)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.2...0.3)
**Implemented enhancements:**
- Install Kimai as WebApp [\#203](https://github.com/kevinpapst/kimai2/issues/203)
- Replace AvanzuAdminTheme with AdminLTE bundle [\#201](https://github.com/kevinpapst/kimai2/issues/201)
- Add dynamic column filter [\#174](https://github.com/kevinpapst/kimai2/issues/174)
- Add register user function [\#164](https://github.com/kevinpapst/kimai2/issues/164)
- Add forgot password function - login screen [\#163](https://github.com/kevinpapst/kimai2/issues/163)
- Integrate FOSUserBundle \(for registration, password-reset, etc.\) [\#144](https://github.com/kevinpapst/kimai2/issues/144)
- added dynamic column filter \#174 [\#184](https://github.com/kevinpapst/kimai2/pull/184) ([kevinpapst](https://github.com/kevinpapst))
**Fixed bugs:**
- Missing assets in fresh installation [\#213](https://github.com/kevinpapst/kimai2/issues/213)
- Cannot remove payment terms from invoice template [\#188](https://github.com/kevinpapst/kimai2/issues/188)
- Set PHP locale for date format and month names [\#110](https://github.com/kevinpapst/kimai2/issues/110)
- Show revenue only for Admin \(wrong currency shown\) [\#19](https://github.com/kevinpapst/kimai2/issues/19)
**Closed issues:**
- Installation Issue of version1.3.1 [\#208](https://github.com/kevinpapst/kimai2/issues/208)
- Error on bin/console doctrine:schema:create [\#191](https://github.com/kevinpapst/kimai2/issues/191)
- Verifying email configuration [\#226](https://github.com/kevinpapst/kimai2/issues/226)
- Add favicon [\#205](https://github.com/kevinpapst/kimai2/issues/205)
- Document DB requirements in install docu [\#196](https://github.com/kevinpapst/kimai2/issues/196)
- easier creation of tasks [\#195](https://github.com/kevinpapst/kimai2/issues/195)
- Integrate lock bot [\#183](https://github.com/kevinpapst/kimai2/issues/183)
- Upgrade to FontAwesome 5 [\#179](https://github.com/kevinpapst/kimai2/issues/179)
**Merged pull requests:**
- Easier creation of Tasks/Projects/User [\#230](https://github.com/kevinpapst/kimai2/pull/230) ([kevinpapst](https://github.com/kevinpapst))
- Added email configuration docs [\#228](https://github.com/kevinpapst/kimai2/pull/228) ([kevinpapst](https://github.com/kevinpapst))
- Show revenue only for ROLE\_ADMIN [\#227](https://github.com/kevinpapst/kimai2/pull/227) ([kevinpapst](https://github.com/kevinpapst))
- hotfix responsiveness [\#220](https://github.com/kevinpapst/kimai2/pull/220) ([kevinpapst](https://github.com/kevinpapst))
- Integrated FOSUserBundle [\#216](https://github.com/kevinpapst/kimai2/pull/216) ([kevinpapst](https://github.com/kevinpapst))
- added auto-script to install assets [\#214](https://github.com/kevinpapst/kimai2/pull/214) ([kevinpapst](https://github.com/kevinpapst))
- added yunohost installation link [\#212](https://github.com/kevinpapst/kimai2/pull/212) ([kevinpapst](https://github.com/kevinpapst))
- added favicons and web app metadata [\#211](https://github.com/kevinpapst/kimai2/pull/211) ([kevinpapst](https://github.com/kevinpapst))
- added FAQ and database requirement docu \#196 [\#210](https://github.com/kevinpapst/kimai2/pull/210) ([kevinpapst](https://github.com/kevinpapst))
- Updated to AdminLTEBundle 1.0 [\#206](https://github.com/kevinpapst/kimai2/pull/206) ([kevinpapst](https://github.com/kevinpapst))
- replaced AvanzuAdminTheme with AdminLTE bundle [\#202](https://github.com/kevinpapst/kimai2/pull/202) ([kevinpapst](https://github.com/kevinpapst))
- allow empty invoice template terms \#188 [\#189](https://github.com/kevinpapst/kimai2/pull/189) ([kevinpapst](https://github.com/kevinpapst))
- improved install docs and composer [\#187](https://github.com/kevinpapst/kimai2/pull/187) ([kevinpapst](https://github.com/kevinpapst))
- added lock-bot config \#183 [\#186](https://github.com/kevinpapst/kimai2/pull/186) ([kevinpapst](https://github.com/kevinpapst))
- Composer package name [\#185](https://github.com/kevinpapst/kimai2/pull/185) ([kevinpapst](https://github.com/kevinpapst))
- updated to font-awesome 5 \#179 [\#181](https://github.com/kevinpapst/kimai2/pull/181) ([kevinpapst](https://github.com/kevinpapst))
- language specific money and date display [\#180](https://github.com/kevinpapst/kimai2/pull/180) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.3)
## [0.2](https://github.com/kevinpapst/kimai2/tree/0.2) (2018-06-23)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.1...0.2)
**Implemented enhancements:**
- Add english translation [\#8](https://github.com/kevinpapst/kimai2/issues/8)
- Add configurable system configuration [\#6](https://github.com/kevinpapst/kimai2/issues/6)
- Hide notifications after some seconds [\#169](https://github.com/kevinpapst/kimai2/issues/169)
- Setup Scrutinizer to run code sniffer [\#158](https://github.com/kevinpapst/kimai2/issues/158)
- Local config to overwrite settings [\#153](https://github.com/kevinpapst/kimai2/issues/153)
- Improve mobile view [\#151](https://github.com/kevinpapst/kimai2/issues/151)
- Add help text to form elements [\#138](https://github.com/kevinpapst/kimai2/issues/138)
- Add duration-only mode [\#131](https://github.com/kevinpapst/kimai2/issues/131)
- Add help controller [\#129](https://github.com/kevinpapst/kimai2/issues/129)
- Secure create-user command [\#123](https://github.com/kevinpapst/kimai2/issues/123)
- Add command for building a production release [\#115](https://github.com/kevinpapst/kimai2/issues/115)
- Add webpack and webpack-encore for frontend assets [\#113](https://github.com/kevinpapst/kimai2/issues/113)
- Add time rounding option [\#112](https://github.com/kevinpapst/kimai2/issues/112)
- Add field order number in Projects [\#107](https://github.com/kevinpapst/kimai2/issues/107)
- Create docu for invoice templates [\#106](https://github.com/kevinpapst/kimai2/issues/106)
- Add invoice template: timesheet [\#104](https://github.com/kevinpapst/kimai2/issues/104)
- Add importer from Kimai v1 [\#102](https://github.com/kevinpapst/kimai2/issues/102)
- Setup error pages [\#100](https://github.com/kevinpapst/kimai2/issues/100)
- Add basic HTML invoice rendering [\#97](https://github.com/kevinpapst/kimai2/issues/97)
- Filter user in timesheet admin [\#93](https://github.com/kevinpapst/kimai2/issues/93)
- Support to switch theme settings [\#89](https://github.com/kevinpapst/kimai2/issues/89)
- Roles should become a real table [\#86](https://github.com/kevinpapst/kimai2/issues/86)
- Fix Doctrine extensions config loader [\#84](https://github.com/kevinpapst/kimai2/issues/84)
- Upgrade to Symfony 4 and Flex [\#74](https://github.com/kevinpapst/kimai2/issues/74)
- Add remember me login [\#53](https://github.com/kevinpapst/kimai2/issues/53)
- Add preferences sidebar [\#10](https://github.com/kevinpapst/kimai2/issues/10)
- Add user settings [\#9](https://github.com/kevinpapst/kimai2/issues/9)
- Mobile alignments and toolbar \#151 [\#157](https://github.com/kevinpapst/kimai2/pull/157) ([kevinpapst](https://github.com/kevinpapst))
- Local config \#153 [\#156](https://github.com/kevinpapst/kimai2/pull/156) ([kevinpapst](https://github.com/kevinpapst))
- Filter in hidden toolbar - improved mobile \#151 [\#155](https://github.com/kevinpapst/kimai2/pull/155) ([kevinpapst](https://github.com/kevinpapst))
- Improved mobile view \#151 [\#150](https://github.com/kevinpapst/kimai2/pull/150) ([kevinpapst](https://github.com/kevinpapst))
**Fixed bugs:**
- Change to users language after login [\#7](https://github.com/kevinpapst/kimai2/issues/7)
- Prevent end time before start time [\#152](https://github.com/kevinpapst/kimai2/issues/152)
- Importing old data fails [\#145](https://github.com/kevinpapst/kimai2/issues/145)
- Translate LanguageType choices [\#83](https://github.com/kevinpapst/kimai2/issues/83)
- Mobile alignments and toolbar \\#151 [\#157](https://github.com/kevinpapst/kimai2/pull/157) ([kevinpapst](https://github.com/kevinpapst))
- Improved mobile view \\#151 [\#150](https://github.com/kevinpapst/kimai2/pull/150) ([kevinpapst](https://github.com/kevinpapst))
**Closed issues:**
- Add CHANGELOG.md [\#177](https://github.com/kevinpapst/kimai2/issues/177)
- Add preview blog message and info text on old website [\#162](https://github.com/kevinpapst/kimai2/issues/162)
- Upgrade to Symfony 4.1 [\#149](https://github.com/kevinpapst/kimai2/issues/149)
- Replace has\_role\(\) with is\_granted\(\) [\#148](https://github.com/kevinpapst/kimai2/issues/148)
- Duration mode: creating new timesheet records prefills duration with some seconds [\#137](https://github.com/kevinpapst/kimai2/issues/137)
- \[RFC\] Encore: configureFilenames with hash after filename instead of in the filename [\#125](https://github.com/kevinpapst/kimai2/issues/125)
- \[RFC\] Kimai license header [\#124](https://github.com/kevinpapst/kimai2/issues/124)
- Setup travis to run phpunit tests [\#120](https://github.com/kevinpapst/kimai2/issues/120)
- Add russian translation [\#95](https://github.com/kevinpapst/kimai2/issues/95)
**Merged pull requests:**
- \[WIP\] Translations [\#173](https://github.com/kevinpapst/kimai2/pull/173) ([simonschaufi](https://github.com/simonschaufi))
- various documentation updates [\#176](https://github.com/kevinpapst/kimai2/pull/176) ([kevinpapst](https://github.com/kevinpapst))
- code styles: header\_comment and ordered\_imports [\#175](https://github.com/kevinpapst/kimai2/pull/175) ([kevinpapst](https://github.com/kevinpapst))
- Code sniffer rules \#158 [\#172](https://github.com/kevinpapst/kimai2/pull/172) ([kevinpapst](https://github.com/kevinpapst))
- Set default locale for unit-tests [\#171](https://github.com/kevinpapst/kimai2/pull/171) ([kevinpapst](https://github.com/kevinpapst))
- Hide success notification after 5 seconds [\#170](https://github.com/kevinpapst/kimai2/pull/170) ([kevinpapst](https://github.com/kevinpapst))
- Added validation for end date before start date [\#167](https://github.com/kevinpapst/kimai2/pull/167) ([kevinpapst](https://github.com/kevinpapst))
- Security expression \#148 [\#166](https://github.com/kevinpapst/kimai2/pull/166) ([kevinpapst](https://github.com/kevinpapst))
- Composer install [\#165](https://github.com/kevinpapst/kimai2/pull/165) ([kevinpapst](https://github.com/kevinpapst))
- added scrutinizer config \#158 [\#161](https://github.com/kevinpapst/kimai2/pull/161) ([kevinpapst](https://github.com/kevinpapst))
- Update to Symfony 4.1 \#149 [\#160](https://github.com/kevinpapst/kimai2/pull/160) ([kevinpapst](https://github.com/kevinpapst))
- Updated README [\#159](https://github.com/kevinpapst/kimai2/pull/159) ([kevinpapst](https://github.com/kevinpapst))
- added russian translation \#95 [\#147](https://github.com/kevinpapst/kimai2/pull/147) ([kevinpapst](https://github.com/kevinpapst))
- Bugfix importer \(\#145\) [\#146](https://github.com/kevinpapst/kimai2/pull/146) ([kevinpapst](https://github.com/kevinpapst))
- help text and link for form labels \#138 [\#143](https://github.com/kevinpapst/kimai2/pull/143) ([kevinpapst](https://github.com/kevinpapst))
- bugfix for duration\_only mode \#137 [\#142](https://github.com/kevinpapst/kimai2/pull/142) ([kevinpapst](https://github.com/kevinpapst))
- Make the back link appear like a normal link instead of a button [\#139](https://github.com/kevinpapst/kimai2/pull/139) ([simonschaufi](https://github.com/simonschaufi))
- Use short array syntax [\#135](https://github.com/kevinpapst/kimai2/pull/135) ([simonschaufi](https://github.com/simonschaufi))
- Duration only mode \#131 [\#134](https://github.com/kevinpapst/kimai2/pull/134) ([kevinpapst](https://github.com/kevinpapst))
- added controller to render markdown documentation \#129 [\#132](https://github.com/kevinpapst/kimai2/pull/132) ([kevinpapst](https://github.com/kevinpapst))
- Rounding rules [\#128](https://github.com/kevinpapst/kimai2/pull/128) ([kevinpapst](https://github.com/kevinpapst))
- Secure create-user command [\#127](https://github.com/kevinpapst/kimai2/pull/127) ([simonschaufi](https://github.com/simonschaufi))
- ConfigureFilenames with hash after filename [\#126](https://github.com/kevinpapst/kimai2/pull/126) ([simonschaufi](https://github.com/simonschaufi))
- added webpack-encore for managing frontend assets \#113 [\#122](https://github.com/kevinpapst/kimai2/pull/122) ([kevinpapst](https://github.com/kevinpapst))
- Remove personal mentions in license header [\#119](https://github.com/kevinpapst/kimai2/pull/119) ([simonschaufi](https://github.com/simonschaufi))
- Add github issue and pull request template [\#118](https://github.com/kevinpapst/kimai2/pull/118) ([simonschaufi](https://github.com/simonschaufi))
- Improvements [\#114](https://github.com/kevinpapst/kimai2/pull/114) ([kevinpapst](https://github.com/kevinpapst))
- added first docu on extending kimai with bundles \#106 [\#109](https://github.com/kevinpapst/kimai2/pull/109) ([kevinpapst](https://github.com/kevinpapst))
- added order number to projects \#107 [\#108](https://github.com/kevinpapst/kimai2/pull/108) ([kevinpapst](https://github.com/kevinpapst))
- added short invoice calculator and timesheet invoice template \#104 [\#105](https://github.com/kevinpapst/kimai2/pull/105) ([kevinpapst](https://github.com/kevinpapst))
- added command to import data from kimai v1 \#102 [\#103](https://github.com/kevinpapst/kimai2/pull/103) ([kevinpapst](https://github.com/kevinpapst))
- Error pages \#100 [\#101](https://github.com/kevinpapst/kimai2/pull/101) ([kevinpapst](https://github.com/kevinpapst))
- added basic invoice rendering \#97 [\#99](https://github.com/kevinpapst/kimai2/pull/99) ([kevinpapst](https://github.com/kevinpapst))
- added remember me feature \#53 [\#98](https://github.com/kevinpapst/kimai2/pull/98) ([kevinpapst](https://github.com/kevinpapst))
- code-cleanup: removed unused classes, added role constants, phpcs fixes [\#96](https://github.com/kevinpapst/kimai2/pull/96) ([kevinpapst](https://github.com/kevinpapst))
- load doctrine extensions during kernel bootstrap \#84 [\#92](https://github.com/kevinpapst/kimai2/pull/92) ([kevinpapst](https://github.com/kevinpapst))
- support theme options via user preferences \#89 [\#91](https://github.com/kevinpapst/kimai2/pull/91) ([kevinpapst](https://github.com/kevinpapst))
- added dynamic user preferences \#7 \#9 [\#90](https://github.com/kevinpapst/kimai2/pull/90) ([kevinpapst](https://github.com/kevinpapst))
- added control sidebar \#10 [\#87](https://github.com/kevinpapst/kimai2/pull/87) ([kevinpapst](https://github.com/kevinpapst))
- added english translation \#8 [\#82](https://github.com/kevinpapst/kimai2/pull/82) ([kevinpapst](https://github.com/kevinpapst))
- upgraded to symfony 4 \#74 [\#81](https://github.com/kevinpapst/kimai2/pull/81) ([kevinpapst](https://github.com/kevinpapst))
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.2)
## [0.1](https://github.com/kevinpapst/kimai2/tree/0.1) (2018-01-10)
**Implemented enhancements:**
- Hide inactive delete user button [\#78](https://github.com/kevinpapst/kimai2/issues/78)
- Delete activity [\#71](https://github.com/kevinpapst/kimai2/issues/71)
- Delete other users timesheet entries [\#70](https://github.com/kevinpapst/kimai2/issues/70)
- Delete Project [\#69](https://github.com/kevinpapst/kimai2/issues/69)
- Delete Customer [\#68](https://github.com/kevinpapst/kimai2/issues/68)
- Delete timesheet entries [\#67](https://github.com/kevinpapst/kimai2/issues/67)
- Add installation docu [\#64](https://github.com/kevinpapst/kimai2/issues/64)
- Disallow login for inactive user [\#57](https://github.com/kevinpapst/kimai2/issues/57)
- Add toolbar to filter user screen [\#56](https://github.com/kevinpapst/kimai2/issues/56)
- Upgrade to latest Avanzu version [\#55](https://github.com/kevinpapst/kimai2/issues/55)
- AdminLTE login screen [\#52](https://github.com/kevinpapst/kimai2/issues/52)
- Add edit Timesheet entry for Teamleads [\#45](https://github.com/kevinpapst/kimai2/issues/45)
- Add Timesheet Voter [\#44](https://github.com/kevinpapst/kimai2/issues/44)
- Developer improvements [\#42](https://github.com/kevinpapst/kimai2/issues/42)
- Base class for repository and query objects [\#40](https://github.com/kevinpapst/kimai2/issues/40)
- Add console command to create user [\#36](https://github.com/kevinpapst/kimai2/issues/36)
- Add basic security Voter [\#34](https://github.com/kevinpapst/kimai2/issues/34)
- Do not show hidden Activities [\#33](https://github.com/kevinpapst/kimai2/issues/33)
- Do not show hidden Projects [\#32](https://github.com/kevinpapst/kimai2/issues/32)
- Add "create timesheet record" form [\#31](https://github.com/kevinpapst/kimai2/issues/31)
- Add filter to Customer admin [\#28](https://github.com/kevinpapst/kimai2/issues/28)
- Do not show hidden Customer [\#27](https://github.com/kevinpapst/kimai2/issues/27)
- Rename Timesheet query class [\#25](https://github.com/kevinpapst/kimai2/issues/25)
- Add "create activity" functionality [\#24](https://github.com/kevinpapst/kimai2/issues/24)
- Add "create project" functionality [\#23](https://github.com/kevinpapst/kimai2/issues/23)
- Add "create customer" functionality [\#22](https://github.com/kevinpapst/kimai2/issues/22)
- Translate dashboard widgets [\#20](https://github.com/kevinpapst/kimai2/issues/20)
- Display user avatar in navbar [\#16](https://github.com/kevinpapst/kimai2/issues/16)
- Add role SUPER\_ADMIN [\#15](https://github.com/kevinpapst/kimai2/issues/15)
- Teamlead can see all user times [\#13](https://github.com/kevinpapst/kimai2/issues/13)
- Add filter to Timesheet \(user\) [\#11](https://github.com/kevinpapst/kimai2/issues/11)
- Add "create user" functionality [\#5](https://github.com/kevinpapst/kimai2/issues/5)
- Edit users role [\#4](https://github.com/kevinpapst/kimai2/issues/4)
- Add filter to Timesheet admin [\#3](https://github.com/kevinpapst/kimai2/issues/3)
- Add filter to Activity admin [\#2](https://github.com/kevinpapst/kimai2/issues/2)
- Add filter to Project admin [\#1](https://github.com/kevinpapst/kimai2/issues/1)
**Merged pull requests:**
- hide inactive delete user button \#78 [\#79](https://github.com/kevinpapst/kimai2/pull/79) ([kevinpapst](https://github.com/kevinpapst))
- added delete customer action \#68 [\#77](https://github.com/kevinpapst/kimai2/pull/77) ([kevinpapst](https://github.com/kevinpapst))
- added delete action for projects \#69 [\#76](https://github.com/kevinpapst/kimai2/pull/76) ([kevinpapst](https://github.com/kevinpapst))
- Delete actions \#71 [\#75](https://github.com/kevinpapst/kimai2/pull/75) ([kevinpapst](https://github.com/kevinpapst))
- added delete timesheet record for admins \#70 [\#73](https://github.com/kevinpapst/kimai2/pull/73) ([kevinpapst](https://github.com/kevinpapst))
- added delete timesheet records action for users \#67 [\#72](https://github.com/kevinpapst/kimai2/pull/72) ([kevinpapst](https://github.com/kevinpapst))
- added toolbar to user screen \#56 [\#66](https://github.com/kevinpapst/kimai2/pull/66) ([kevinpapst](https://github.com/kevinpapst))
- document installation options \#64 [\#65](https://github.com/kevinpapst/kimai2/pull/65) ([kevinpapst](https://github.com/kevinpapst))
- update avanzu admin bundle \#55 [\#63](https://github.com/kevinpapst/kimai2/pull/63) ([kevinpapst](https://github.com/kevinpapst))
- prevent inactive user from login \#57 [\#61](https://github.com/kevinpapst/kimai2/pull/61) ([kevinpapst](https://github.com/kevinpapst))
- handle hidden customer \#27 [\#60](https://github.com/kevinpapst/kimai2/pull/60) ([kevinpapst](https://github.com/kevinpapst))
- handle hidden projects \#32 [\#59](https://github.com/kevinpapst/kimai2/pull/59) ([kevinpapst](https://github.com/kevinpapst))
- Handle hidden activities \#33 [\#58](https://github.com/kevinpapst/kimai2/pull/58) ([kevinpapst](https://github.com/kevinpapst))
- using the AdminLTE login screen \#52 [\#54](https://github.com/kevinpapst/kimai2/pull/54) ([kevinpapst](https://github.com/kevinpapst))
- added toolbar for activity screen \#2 [\#51](https://github.com/kevinpapst/kimai2/pull/51) ([kevinpapst](https://github.com/kevinpapst))
- Added toolbar to project screen \#1 [\#50](https://github.com/kevinpapst/kimai2/pull/50) ([kevinpapst](https://github.com/kevinpapst))
- added toolbar to customer screen \#28 [\#49](https://github.com/kevinpapst/kimai2/pull/49) ([kevinpapst](https://github.com/kevinpapst))
- added form to edit timesheet entries for all user \#45 [\#48](https://github.com/kevinpapst/kimai2/pull/48) ([kevinpapst](https://github.com/kevinpapst))
- Create timesheet for User \#31 [\#47](https://github.com/kevinpapst/kimai2/pull/47) ([kevinpapst](https://github.com/kevinpapst))
- added timesheet voter \#44 [\#46](https://github.com/kevinpapst/kimai2/pull/46) ([kevinpapst](https://github.com/kevinpapst))
- Developer cleanup \#42 [\#43](https://github.com/kevinpapst/kimai2/pull/43) ([kevinpapst](https://github.com/kevinpapst))
- Repository cleanup [\#41](https://github.com/kevinpapst/kimai2/pull/41) ([kevinpapst](https://github.com/kevinpapst))
- Added voter for customer, project and activity [\#39](https://github.com/kevinpapst/kimai2/pull/39) ([kevinpapst](https://github.com/kevinpapst))
- added create user console command [\#38](https://github.com/kevinpapst/kimai2/pull/38) ([kevinpapst](https://github.com/kevinpapst))
- added "create user" functionality [\#37](https://github.com/kevinpapst/kimai2/pull/37) ([kevinpapst](https://github.com/kevinpapst))
- Added "create activity" form [\#35](https://github.com/kevinpapst/kimai2/pull/35) ([kevinpapst](https://github.com/kevinpapst))
- Added create project functionality [\#30](https://github.com/kevinpapst/kimai2/pull/30) ([kevinpapst](https://github.com/kevinpapst))
- Rename TimesheetQuery class [\#29](https://github.com/kevinpapst/kimai2/pull/29) ([kevinpapst](https://github.com/kevinpapst))
- Add customer functionality [\#26](https://github.com/kevinpapst/kimai2/pull/26) ([kevinpapst](https://github.com/kevinpapst))
- Dashboard widgets [\#21](https://github.com/kevinpapst/kimai2/pull/21) ([kevinpapst](https://github.com/kevinpapst))
- Added form to edit user roles [\#18](https://github.com/kevinpapst/kimai2/pull/18) ([kevinpapst](https://github.com/kevinpapst))
- added role ROLE\_SUPER\_ADMIN [\#17](https://github.com/kevinpapst/kimai2/pull/17) ([kevinpapst](https://github.com/kevinpapst))
- allow TEAMLEAD to see user times [\#14](https://github.com/kevinpapst/kimai2/pull/14) ([kevinpapst](https://github.com/kevinpapst))
- Timesheet toolbar [\#12](https://github.com/kevinpapst/kimai2/pull/12) ([kevinpapst](https://github.com/kevinpapst))
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.1)

View File

@@ -1,15 +1,16 @@
# Contributing
Kimai is an open source project, contributions made by the community are welcome.
Send us your ideas, code reviews, pull requests and feature requests to help us improve this project.
Send your ideas, code reviews, pull requests and feature requests to help improving this project.
## Pull request rules
- We use PSR-2 with some additional code-style checks (see our [php-cs-fixer config](.php_cs.dist)). You can run `bin/console kimai:codestyle` to check and `bin/console kimai:codestyle --fix` to fix violations.
- Add PHPUnit tests for your changes, verify everything still works and execute our test-suites `bin/console kimai:test-unit` and `bin/console kimai:test-integration`.
- If you contribute new files, please add them with the file-header template from below (our chode-style fixer can do that for you).
- With sending in a PR, you accept that your contributions/code will be published under MIT license (see the [LICENSE](LICENSE) file as well).
- If one of the PR checks fails, please fix them before asking us for a review.
- Use the [pre-configured codesniffer](.php_cs.dist)) to check for `composer kimai:codestyle` and fix `composer kimai:codestyle-fix` violations
- Add PHPUnit tests for your changes!
- Verify everything still works with `composer kimai:tests-unit` and `composer kimai:tests-integration`
- If you contribute new files, add them with the file-header template from below (the code-style fixer can do that for you)
- When sending in a PR, you must accept that your contributions/code will be published under MIT license (see the [LICENSE](LICENSE) file as well), otherwise your PR will be closed
- If one of the PR checks/builds fails, fix it before asking for a review
Further documentation can be found in the [developer documentation](https://www.kimai.org/documentation/developers.html).

View File

@@ -2,17 +2,18 @@
#
# For the full copyright and license information, please view the LICENSE
# file that was distributed with this source code.
FROM php:7.2.9-apache-stretch
FROM php:7.2.9-apache-stretch AS tmp_kimai2_base
RUN apt update && \
apt install -y \
apt install -y --allow-unauthenticated \
git \
haveged \
libicu-dev \
libjpeg-dev \
libldap2-dev \
libldb-dev \
libpng-dev \
mysql-client \
unzip \
wget \
zip \
@@ -32,18 +33,22 @@ RUN apt update && \
zip && \
apt remove -y wget && \
apt -y autoremove && \
apt clean && \
git clone https://github.com/kevinpapst/kimai2.git /opt/kimai && \
apt clean
FROM tmp_kimai2_base
RUN git clone https://github.com/kevinpapst/kimai2.git /opt/kimai && \
sed "s/prod/dev/g" /opt/kimai/.env.dist > /opt/kimai/.env && \
composer install --working-dir=/opt/kimai --dev --optimize-autoloader && \
/opt/kimai/bin/console doctrine:database:create && \
/opt/kimai/bin/console doctrine:schema:create && \
/opt/kimai/bin/console doctrine:migrations:version --add --all && \
/opt/kimai/bin/console cache:warmup && \
chown -R www-data:www-data /opt/kimai/var
chown -R www-data:www-data /opt/kimai/var && \
chown www-data:www-data /opt/kimai/vendor/mpdf/mpdf/tmp
WORKDIR /opt/kimai
EXPOSE 8001
USER www-data
CMD /opt/kimai/bin/console server:run 0.0.0.0:8001
CMD /opt/kimai/bin/console server:run 0.0.0.0:8001

View File

@@ -1,74 +1,69 @@
# Kimai 2 - Time-tracking made easy
Kimai - the open source time-tracker application with a mobile-first approach (read more at the [official website](https://www.kimai.org)).
# Kimai 2 - online time-tracker
[![Latest Stable Version](https://poser.pugx.org/kevinpapst/kimai2/v/stable)](https://packagist.org/packages/kevinpapst/kimai2)
[![License](https://poser.pugx.org/kevinpapst/kimai2/license)](https://packagist.org/packages/kevinpapst/kimai2)
[![Travis Status](https://travis-ci.org/kevinpapst/kimai2.svg?branch=master)](https://travis-ci.org/kevinpapst/kimai2)
[![Code Quality](https://scrutinizer-ci.com/g/kevinpapst/kimai2/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/kevinpapst/kimai2/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/kevinpapst/kimai2/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/kevinpapst/kimai2/?branch=master)
[![Scrutinizer Status](https://scrutinizer-ci.com/g/kevinpapst/kimai2/badges/build.png?b=master)](https://scrutinizer-ci.com/g/kevinpapst/kimai2/build-status/master)
[![Code Coverage](https://codecov.io/gh/kevinpapst/kimai2/branch/master/graph/badge.svg)](https://codecov.io/gh/kevinpapst/kimai2)
[![Gitter](https://badges.gitter.im/kimai2/support.svg)](https://gitter.im/kimai2/support)
[![Bountysource](https://img.shields.io/bountysource/team/kimai2/activity)](https://www.bountysource.com/teams/kimai2)
Kimai is a free, open source and online time-tracking software designed for small businesses and freelancers.
It is built with modern technologies such as Symfony, Bootstrap, RESTful API, Doctrine, AdminLTE, Webpack, ES6 etc.
## Introduction
This is the _reloaded_ version of the open source timetracker Kimai.
It is built from scratch and doesn't share any source code with its [predecessor](http://www.kimai.org).
But it adapts the same ideas and a clean & simple UI for your time-tracking experience.
By now it is in an almost-stable development phase, usable and with most advanced features from Kimai 1.
You can even [import your data](https://www.kimai.org/documentation/migration-v1.html) and start testing and using it today.
Kimai is a [multi-language application](https://www.kimai.org/documentation/translations.html) and already translated to english, german, italian, french, spanish, russian, arabic, hungarian, portuguese (brazilian) and swedish.
- [Home](https://www.kimai.org) - The house of Kimai
- [Blog](https://www.kimai.org/blog/) - Get the latest news
- [Documentation](https://www.kimai.org/documentation/) - Learn how to use
- [Translations](https://www.kimai.org/documentation/translations.html) - Kimai in your language
- [Migration](https://www.kimai.org/documentation/migration-v1.html) - Import data from v1
### Requirements
- PHP 7.2 or higher (test your system compatibility with the [requirements-checker](http://symfony.com/doc/current/reference/requirements.html))
- The PHP extensions [xml](http://php.net/manual/en/book.xml.php), [mbstring](http://php.net/manual/en/book.mbstring.php), [gd](http://php.net/manual/en/book.image.php), [intl](https://php.net/manual/en/book.intl.php), [zip](https://php.net/manual/en/book.zip.php) and [PDO](https://php.net/manual/en/book.pdo.php) with either [pdo_sqlite](https://php.net/manual/en/ref.pdo-sqlite.php) or [pdo_mysql](https://php.net/manual/en/ref.pdo-mysql.php) enabled
- If you use MariaDB, make sure its at least v10.2.7 (see [FAQ](https://www.kimai.org/documentation/faq.html))
- A modern browser, Kimai v2 might be broken on old browsers like IE 10
- PHP 7.2 or higher
- Database (MySQL, MariaDB, SQLite)
- Webserver (nginx, Apache)
- A modern browser
- [Other libraries](https://www.kimai.org/download/)
## Documentation
### About
Looking for more information about Kimai 2? Check out our detailed [documentation](https://www.kimai.org/documentation/).
This is new version of the open source timetracker Kimai. It is stable and production ready, ships
with most advanced features from Kimai 1 and many new ones, including but not limited to:
### Installation
JSON API, invoicing, data exports, multi-timer and punch-in punch-out mode, tagging, multi-user and multi-timezones,
LDAP and built-in authentication, customizable role permissions, responsive and ready for your mobile device,
hourly and fixed rates, advanced filtering, money and time budgets with report, support for plugins and many more.
There are multiple ways to install Kimai, all of them described in the [installation docu](https://www.kimai.org/documentation/installation.html):
## Installation
- [Recommended installation with GIT and Composer](https://www.kimai.org/documentation/installation.html#recommended-setup)
- [Development setup](https://www.kimai.org/documentation/installation.html#development-installation)
- [Docker](https://www.kimai.org/documentation/docker.html)
- [1-click installations](https://www.kimai.org/documentation/installation.html#hosting-and-1-click-installations)
- [FTP](https://www.kimai.org/documentation/installation.html#ftp-installation)
- [Recommended setup](https://www.kimai.org/documentation/installation.html#recommended-setup) - with Git and Composer
- [Docker](https://www.kimai.org/documentation/docker.html) - containerized
- [Development](https://www.kimai.org/documentation/installation.html#development-installation) - on your local machine
- [1-click installer](https://www.kimai.org/documentation/installation.html#hosting-and-1-click-installations) - hosted environments
- [FTP](https://www.kimai.org/documentation/installation.html#ftp-installation) - unfortunately still widely used ;-)
### Updating Kimai
Read the following documentations before you start your upgrade:
- [Update Kimai](https://www.kimai.org/documentation/updates.html) - the documentation
- [UPGRADING guide](UPGRADING.md) - version specific steps
- The [update documentation](https://www.kimai.org/documentation/updates.html)
- The version specific [UPGRADING guide](UPGRADING.md)
- The [release information](https://github.com/kevinpapst/kimai2/releases)
### Plugins
- [Plugin marketplace](https://www.kimai.org/store/) - find existing plugins here
- [Developer documentation](https://www.kimai.org/documentation/developers.html) - how to create a plugin
## Roadmap and releases
You can see a rough development roadmap in the [Milestones](https://github.com/kevinpapst/kimai2/milestones) sections.
It is open for changes and input from the community, your [ideas and questions](https://github.com/kevinpapst/kimai2/issues) are welcome!
It is open for changes and input from the community, your [ideas and questions](https://github.com/kevinpapst/kimai2/issues) are welcome.
> Kimai 2 uses a rolling release concept for delivering updates.
> You can upgrade Kimai at any time, you don't need to wait for the next official release. Read the [upgrade docs](UPGRADING.md) first!
> You can upgrade Kimai at any time, you don't need to wait for the next official release.
Release versions will be created on a regular base (approx. 1 per month) and you can use these tags if you are familiar with Git,
but we cannot provide support for any specific version (especially older ones).
Release versions will be created on a regular base (approx. every second month) and you can use these tags if you are familiar with git.
Every code change, whether it's a new feature or a bug fix, will be done on the master branch.
I have to do it this way, as I develop Kimai in my free time and want to put most
effort into the software instead of backporting changes for old versions.
## Plugins for Kimai 2
Kimai 2 was built on top of the Symfony framework and can be extended in various ways:
- Users looking for existing plugins go to our [plugin marketplace](https://www.kimai.org/store/)
- Developer start with the [developer documentation](https://www.kimai.org/documentation/developers.html)
I have to do it this way, as I develop Kimai in my free time and want to put my effort into the software instead of backporting changes for old versions.
## Credits

23
SECURITY.md Normal file
View File

@@ -0,0 +1,23 @@
# Security Policy
## Supported Versions
As announced in the [README](README.md) I only support the latest available release and master.
| Version | Supported |
| ------- | ------------------ |
| master | :white_check_mark: |
| 1.2 | :white_check_mark: |
| < 1.2 | :x: |
## Reporting a Vulnerability
Please report any security related vulnerability in the [advisories section at GitHub](https://github.com/kevinpapst/kimai2/security/advisories) or via email to info@keleo.de or kpapst@gmx.net.
I will work as fast as I can to fix the problem and publish a bugfix release / security update.
Depending on the size of the required fixes, this might take a couple of hours or a couple of days.
You can expect that your message will be answered ASAP, but please take into account that I am living in the timezone Europe/Berlin.
If your issue is valid and I verified and fixed it, you will be mentioned in the release notes within the Security section at the top of the release.
I am grateful for any (discrete) disclosure of vulnerabilities!

View File

@@ -1,27 +1,64 @@
# Upgrading Kimai 2
_Database upgrades are currently ONLY provided for MySQL/MariaDB and SQLite._
_Make sure to create a backup before you start!_
Upgrading to the latest available version can be achieved with these commands:
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.
```bash
git fetch --tags
git checkout 0.8
sudo -u www-data composer install --no-dev --optimize-autoloader
sudo -u www-data bin/console cache:clear --env=prod
sudo -u www-data bin/console cache:warmup --env=prod
bin/console doctrine:migrations:migrate
```
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.
There might be more steps required which are version specific and need to be executed after following the above list of commands.
Read and follow each version info below, otherwise you risk data inconsistency or a broken installation!
## [1.2](https://github.com/kevinpapst/kimai2/releases/tag/1.2)
And make sure to **create a backup before you start**.
### Possible BC breaks
- Deleted timezone conversion command. If you are still using 0.7 or below, you need to upgrade to 1.1 before upgrading to this version.
- Minimum password length raised from 5 to 8 character (applies only for password changes and new users)
- Maximum customer name length lowered to 150 character
- Maximum project name length lowered to 150 character
- Maximum activity name length lowered to 150 character
- Added new permission: `manage_invoice_template`
- Removed permissions: `view_invoice_template`, `create_invoice_template`, `edit_invoice_template`, `delete_invoice_template`
- Removed permission: `view_invoice` (using `create_export` only)
- Custom export renderer need to check for usage of `Timesheet::getEnd()` as running entries can now be exported as well
## [1.1](https://github.com/kevinpapst/kimai2/releases/tag/1.1)
[Update as usual](https://www.kimai.org/documentation/updates.html), nothing special for this release if you upgrade from 1.0 / 1.0.1.
## [1.0.1](https://github.com/kevinpapst/kimai2/releases/tag/1.0.1)
[Update as usual](https://www.kimai.org/documentation/updates.html), nothing special for this release if you upgrade from 1.0.
## [1.0](https://github.com/kevinpapst/kimai2/releases/tag/1.0)
This release contains several changes, as I still have the goal to stabilize the code base to prevent
such "challenges" after 1.0 for a while.
### Changes for your local.yaml
New permissions are available. You have to add them to your `local.yaml` ONLY if you use a custom permission structure,
otherwise you can't use the new features:
- `view_tag` - view all tags
- `delete_tag` - delete tags
- `edit_exported_timesheet` - allows to edit records which were exported
- `role_permissions` - view calculated permissions for user roles
- `budget_activity` - view and edit budgets for activities
- `budget_project` - view and edit budgets for projects
- `budget_customer` - view and edit budgets for customers
Removed permission:
- `system_actions` - removed experimental feature to flush app cache from the about screen
### BC BREAKS
- API: Format for queries including a datetime object fixed to use HTML5 format (previously `2019-03-02 14:23` - now `2019-03-02T14:23:00`)
- **Permission config**: the `permissions` definition in your `local.yaml` needs to be verified/changed, as the internal structure was highly optimized to simplify the definition.
Thanks to the new structure, you should be able to remove almost everything from your `local.yaml` (tip: start over from scratch!). Please read [the updated permission docu](https://www.kimai.org/documentation/permissions.html).
- default widgets were removed from `kimai.yaml`, that shouldn't cause any issues ... but if something is odd: [look here for help](https://www.kimai.org/documentation/dashboard.html)
## [0.9](https://github.com/kevinpapst/kimai2/releases/tag/0.9)
Follow the normal update and database migration process (see above).
Remember to execute the necessary timezone conversion script, if you haven't updated to 0.8 before (see below)!
### BC BREAKS

View File

@@ -1,29 +1,32 @@
// ------------------- INLINED ADMIN-LTE DEFINITIONS -------------------
// this was done to save around 300kb by:
// require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/admin-lte');
// this was replaced to save around 300kb by:
// - removing moment locales which are not used
// - removing fullcalendar locales which are not used
// - removing icheck which is not used
//require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/admin-lte');
// - removing jquery-ui which is not used
const $ = require('jquery');
global.$ = global.jQuery = $;
require('jquery-ui');
require('bootstrap-sass');
require('jquery-slimscroll');
require('bootstrap-select');
const Moment = require('moment');
global.moment = Moment;
require('moment/locale/de');
require('moment/locale/it');
require('moment/locale/fr');
require('moment/locale/es');
require('moment/locale/ru');
require('moment/locale/ar');
require('moment/locale/de');
require('moment/locale/es');
require('moment/locale/fr');
require('moment/locale/hu');
require('moment/locale/it');
require('moment/locale/ja');
require('moment/locale/ko');
require('moment/locale/pt-br');
require('moment/locale/ru');
require('moment/locale/sk');
require('moment/locale/sv');
require('daterangepicker');
@@ -36,32 +39,13 @@ require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/admin-lte-extensi
global.$.AdminLTE = {};
global.$.AdminLTE.options = {};
require('admin-lte/dist/js/adminlte.min');
require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/default_avatar.png');
// ------------------- INLINED ADMIN-LTE DEFINITIONS -------------------
// ---------------------------------------------------------------------
require('./sass/app.scss');
require('fullcalendar');
require('fullcalendar/dist/gcal.min');
require('fullcalendar/dist/locale/de');
require('fullcalendar/dist/locale/it');
require('fullcalendar/dist/locale/fr');
require('fullcalendar/dist/locale/es');
require('fullcalendar/dist/locale/ru');
require('fullcalendar/dist/locale/ar');
require('fullcalendar/dist/locale/hu');
require('fullcalendar/dist/locale/pt-br');
require('fullcalendar/dist/fullcalendar.min.css');
// ------ for charts ------
require('chart.js/dist/Chart.min');
// ------ Kimai itself ------
require('./js/kimai.js');
require('./js/datatable.js');
require('./js/toolbar.js');
require('./images/default_avatar.png');
require('./images/signature.png');
require('./js/KimaiWebLoader.js');
// ------ Autocomplete for tags only ------
require('jquery-ui/ui/widgets/autocomplete');

17
assets/calendar.js Normal file
View File

@@ -0,0 +1,17 @@
require('fullcalendar');
require('fullcalendar/dist/gcal.min');
require('fullcalendar/dist/locale/de');
require('fullcalendar/dist/locale/it');
require('fullcalendar/dist/locale/fr');
require('fullcalendar/dist/locale/es');
require('fullcalendar/dist/locale/ru');
require('fullcalendar/dist/locale/ar');
require('fullcalendar/dist/locale/hu');
require('fullcalendar/dist/locale/pt-br');
require('fullcalendar/dist/locale/sv');
require('fullcalendar/dist/locale/ja');
require('fullcalendar/dist/locale/sk');
require('fullcalendar/dist/fullcalendar.min.css');

2
assets/chart.js Normal file
View File

@@ -0,0 +1,2 @@
require('chart.js/dist/Chart.min');

View File

@@ -0,0 +1,26 @@
/*
* 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] KimaiConfiguration: handling all configuration and runtime settings
*/
export default class KimaiConfiguration {
constructor(configurations) {
this._configurations = configurations;
}
get(name) {
return this._configurations[name];
}
has(name) {
return name in this._configurations;
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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] KimaiContainer
*
* ServiceContainer for Kimai
*/
import KimaiConfiguration from './KimaiConfiguration';
import KimaiTranslation from './KimaiTranslation';
import KimaiPlugin from './KimaiPlugin';
export default class KimaiContainer {
/**
* Create a new Container with the given configurations and translations.
*
* @param {Object} configuration
* @param {Object} translation
*/
constructor(configuration, translation) {
if (!(configuration instanceof KimaiConfiguration)) {
throw new Error('Configuration needs to a KimaiConfiguration instance');
}
this._configuration = configuration;
if (!(translation instanceof KimaiTranslation)) {
throw new Error('Configuration needs to a KimaiTranslation instance');
}
this._translation = translation;
this._plugins = [];
}
/**
* Register a new Plugin.
*
* @param {KimaiPlugin} plugin
* @returns {KimaiPlugin}
*/
registerPlugin(plugin) {
if (!(plugin instanceof KimaiPlugin)) {
throw new Error('Invalid plugin given, needs to be a KimaiPlugin instance');
}
plugin.setContainer(this);
this._plugins.push(plugin);
return plugin;
}
/**
* @param {string} name
* @returns {KimaiPlugin}
*/
getPlugin(name) {
for (let plugin of this._plugins) {
if (plugin.getId() !== null && plugin.getId() === name) {
return plugin;
}
}
throw new Error('Unknown plugin: ' + name);
}
/**
* @returns {Array<KimaiPlugin>}
*/
getPlugins() {
return this._plugins;
}
/**
* @returns {KimaiTranslation}
*/
getTranslation() {
return this._translation;
}
/**
* @returns {KimaiConfiguration}
*/
getConfiguration() {
return this._configuration;
}
}

84
assets/js/KimaiLoader.js Normal file
View File

@@ -0,0 +1,84 @@
/*
* 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] KimaiLoader: bootstrap the application and all plugins
*/
import moment from 'moment';
import KimaiTranslation from "./KimaiTranslation";
import KimaiConfiguration from "./KimaiConfiguration";
import KimaiContainer from "./KimaiContainer";
import KimaiActiveRecordsDuration from './plugins/KimaiActiveRecordsDuration.js';
import KimaiDatatableColumnView from './plugins/KimaiDatatableColumnView.js';
import KimaiThemeInitializer from "./plugins/KimaiThemeInitializer";
import KimaiJqueryPluginInitializer from "./plugins/KimaiJqueryPluginInitializer";
import KimaiDateRangePicker from "./plugins/KimaiDateRangePicker";
import KimaiDatatable from "./plugins/KimaiDatatable";
import KimaiToolbar from "./plugins/KimaiToolbar";
import KimaiAPI from "./plugins/KimaiAPI";
import KimaiSelectDataAPI from "./plugins/KimaiSelectDataAPI";
import KimaiDateTimePicker from "./plugins/KimaiDateTimePicker";
import KimaiAlternativeLinks from "./plugins/KimaiAlternativeLinks";
import KimaiAjaxModalForm from "./plugins/KimaiAjaxModalForm";
import KimaiActiveRecords from "./plugins/KimaiActiveRecords";
import KimaiRecentActivities from "./plugins/KimaiRecentActivities";
import KimaiEvent from "./plugins/KimaiEvent";
import KimaiAPILink from "./plugins/KimaiAPILink";
import KimaiAlert from "./plugins/KimaiAlert";
import KimaiAutocomplete from "./plugins/KimaiAutocomplete";
import KimaiToolbarAction from "./plugins/KimaiToolbarAction";
export default class KimaiLoader {
constructor(configurations, translations) {
// set the current locale for all javascript components
moment.locale(configurations['locale']);
const kimai = new KimaiContainer(
new KimaiConfiguration(configurations),
new KimaiTranslation(translations)
);
kimai.registerPlugin(new KimaiEvent());
kimai.registerPlugin(new KimaiAPI());
kimai.registerPlugin(new KimaiAlert());
kimai.registerPlugin(new KimaiActiveRecordsDuration('[data-since]'));
kimai.registerPlugin(new KimaiDatatableColumnView('data-column-visibility'));
kimai.registerPlugin(new KimaiThemeInitializer());
kimai.registerPlugin(new KimaiJqueryPluginInitializer());
kimai.registerPlugin(new KimaiDateRangePicker('.content-wrapper'));
kimai.registerPlugin(new KimaiDateTimePicker('.content-wrapper'));
kimai.registerPlugin(new KimaiDatatable('table.dataTable'));
kimai.registerPlugin(new KimaiToolbar());
kimai.registerPlugin(new KimaiSelectDataAPI('select[data-related-select]'));
kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link'));
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form'));
kimai.registerPlugin(new KimaiRecentActivities('li.notifications-menu'));
kimai.registerPlugin(new KimaiActiveRecords('li.messages-menu', 'li.messages-menu-empty'));
kimai.registerPlugin(new KimaiAPILink('api-link'));
kimai.registerPlugin(new KimaiAutocomplete('.js-autocomplete'));
kimai.registerPlugin(new KimaiToolbarAction('toolbar-action'));
//kimai.registerPlugin(new KimaiPauseRecord('li.messages-menu ul.menu li'));
// notify all listeners that Kimai plugins can now be registered
kimai.getPlugin('event').trigger('kimai.pluginRegister', {'kimai': kimai});
// initialize all plugins
kimai.getPlugins().map(plugin => { plugin.init(); });
// notify all listeners that Kimai is now ready to be used
kimai.getPlugin('event').trigger('kimai.initialized', {'kimai': kimai});
this.kimai = kimai;
}
getKimai() {
return this.kimai;
}
}

53
assets/js/KimaiPlugin.js Normal file
View File

@@ -0,0 +1,53 @@
/*
* 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] KimaiPlugin: base class for all plugins
*/
import KimaiContainer from "./KimaiContainer";
export default class KimaiPlugin {
/**
* Overwrite this method to initialize your plugin.
*
* It is called AFTER setContainer() and AFTER DOMContentLoaded was fired.
* You don't have access to the container before this method!
*/
init() {
}
/**
* If you return an ID, you indicate that your plugin can be used by other plugins.
*
* @returns {string|null}
*/
getId() {
return null;
}
/**
* @param {KimaiContainer} core
*/
setContainer(core) {
if (!(core instanceof KimaiContainer)) {
throw new Error('Plugin was given an invalid KimaiContainer');
}
this._core = core;
}
/**
* This function returns null, if xou call it BEFORE init().
*
* @returns {KimaiContainer}
*/
getContainer() {
return this._core;
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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] KimaiTranslation: handling translation strings
*/
export default class KimaiTranslation {
constructor(translations) {
this._translations = translations;
}
get(name) {
return this._translations[name];
}
has(name) {
return name in this._translations;
}
}

View File

@@ -0,0 +1,33 @@
/*
* This file is part of the Kimai time-tracking app.
*
* Main JS application file for Kimai 2. This file should be included in all pages.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] Wrapper class for loading Kimai app in browser script scope
*/
import KimaiLoader from "./KimaiLoader";
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], function () {
return (root.KimaiWebLoader = factory());
});
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.KimaiWebLoader = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
class KimaiWebLoader extends KimaiLoader {
}
return KimaiWebLoader;
}));

View File

@@ -1,80 +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.
*/
/** global: Cookies */
global.Cookies = require('js-cookie');
if (typeof jQuery === 'undefined') {
throw new Error('Kimai requires jQuery');
}
/* datatable
*
* @type Object
* @description $.datatable is the main object for views with data-tables.
* It's used for implementing functions and options related
* to the datatables.
*/
$.datatable = {};
$(function() {
"use strict";
$.datatable = {
saveVisibility: function (modalSelector) {
$(modalSelector).find('form').each(
function() {
var settings = {};
var cookieName = $(this).attr('name');
$(this).find('input:checkbox').each(
function () {
settings[$(this).attr('name')] = $(this).is(':checked');
}
);
if (jQuery.isEmptyObject(settings)) {
Cookies.remove(cookieName);
} else {
Cookies.set(cookieName, JSON.stringify(settings), {expires: 365});
}
}
);
$(modalSelector).modal('toggle');
$.kimai.reloadDatatableWithToolbarFilter();
},
resetVisibility: function (modalSelector) {
$(modalSelector).find('form').each(
function() {
var cookieName = $(this).attr('name');
Cookies.remove(cookieName);
}
);
$(modalSelector).modal('toggle');
$.kimai.reloadDatatableWithToolbarFilter();
},
changeVisibility: function (column) {
var tbl = $('table.dataTable');
var amount = tbl.find('th').length -1;
if (column < 0 || column >= amount) {
return;
}
var header = $(tbl.find('th').get(column));
if (header.css("display") === "none") {
header.show('ease');
tbl.find('tr').each(function(){
$($(this).find('td').get(column)).show('ease');
});
} else {
header.hide('ease');
tbl.find('tr').each(function(){
$($(this).find('td').get(column)).hide('ease');
});
}
}
};
});

View File

@@ -1,344 +0,0 @@
/*!
* This file is part of the Kimai time-tracking app.
*
* Main JS application file for Kimai 2. This file should be included in all pages.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/** global: jQuery */
/** global: moment */
if (typeof jQuery === 'undefined') {
throw new Error('Kimai requires jQuery');
}
if (typeof moment === 'undefined') {
throw new Error('Kimai requires moment.js');
}
/* kimai
*
* @type Object
* @description $.kimai is the main object for the template's app.
* It's used for implementing functions and options related
* to the template. Keeping everything wrapped in an object
* prevents conflict with other plugins and is a better
* way to organize our code.
*/
$.kimai = {};
$(function() {
"use strict";
$.kimai = {
init: function(options) {
if (typeof options !== 'undefined') {
$.kimai.settings = $.extend({}, $.kimai.defaults, options);
}
// set the current locale for all javascript components
moment.locale($.kimai.settings['locale']);
// activate the dropdown functionality
$('.dropdown-toggle').dropdown();
// auto hide success message after x seconds, as they are just meant as quick feedback and
// not as a permanent source of information
setTimeout(
function() {
$('div.alert-success').alert('close');
},
5000
);
// compound field in toolbar
this.activateDateRangePicker('.content-wrapper');
// single select boxes in toolbars
this.activateDatePicker('.content-wrapper');
// edit timesheet - date with time
this.activateDateTimePicker('.content-wrapper');
// some actions can be performed in a modal for a better UX
this.activateAjaxFormInModal('a.modal-ajax-form');
$('body').on('change', 'select[data-related-select]', function(event) {
var apiUrl = $(this).attr('data-api-url').replace('-s-', $(this).val());
var targetSelect = $(this).attr('data-related-select');
$.ajax({
url: apiUrl,
headers: {
'X-AUTH-SESSION': true,
'Content-Type':'application/json'
},
method: 'GET',
dataType: 'json',
success: function(data){
var selectName = '#' + targetSelect;
var $select = $(selectName);
var $emptyOption = $(selectName + ' option[value=""]');
$select.find('option').remove().end().find('optgroup').remove().end();
if ($emptyOption.length !== 0) {
$select.append('<option value="">' + $emptyOption.text() + '</option>');
}
$.each(data, function(i, obj) {
$select.append('<option value="' + obj.id + '">' + obj.name + '</option>');
});
// if we don't trigger the change, the other selects won't be resetted
$select.trigger('change');
// if the beta test kimai.theme.select_type is active, this will tell the selects to refresh
$('.selectpicker').selectpicker('refresh');
}
});
});
},
reloadDatatableWithToolbarFilter: function() {
// TODO check if toolbar form is present, if not, reload current URL
var $form = $('.toolbar form');
var loading = '<div class="overlay"><i class="fas fa-sync fa-spin"></i></div>';
$('section.content').append(loading);
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: $form.serialize(),
success: function(html) {
$('section.content').replaceWith(
$(html).find('section.content')
);
},
error: function(xhr, err) {
$form.submit();
}
});
},
pauseRecord: function(selector) {
$(selector + ' .pull-left i').hover(function () {
var link = $(this).parents('a');
link.attr('href', link.attr('href').replace('/stop', '/pause'));
$(this).removeClass('fa-stop-circle').addClass('fa-pause-circle').addClass('text-orange');
},function () {
var link = $(this).parents('a');
link.attr('href', link.attr('href').replace('/pause', '/stop'));
$(this).removeClass('fa-pause-circle').removeClass('text-orange').addClass('fa-stop-circle');
});
},
activateDatePicker: function(selector) {
$(selector + ' input[data-datepickerenable="on"]').each(function(index) {
var localeFormat = $(this).data('format');
$(this).daterangepicker({
singleDatePicker: true,
showDropdowns: true,
autoUpdateInput: false,
locale: {
format: localeFormat,
firstDay: 1,
applyLabel: $.kimai.settings['apply'],
cancelLabel: $.kimai.settings['cancel'],
customRangeLabel: $.kimai.settings['customRange']
}
});
$(this).on('apply.daterangepicker', function(ev, picker) {
$(this).val(picker.startDate.format(localeFormat));
$(this).trigger("change");
});
});
},
activateDateTimePicker: function(selector) {
$(selector + ' input[data-datetimepicker="on"]').each(function(index) {
var localeFormat = $(this).data('format');
$(this).daterangepicker({
singleDatePicker: true,
timePicker: true,
timePicker24Hour: $.kimai.settings['twentyFourHours'],
showDropdowns: true,
autoUpdateInput: false,
locale: {
format: localeFormat,
firstDay: 1,
applyLabel: $.kimai.settings['apply'],
cancelLabel: $.kimai.settings['cancel'],
customRangeLabel: $.kimai.settings['customRange']
}
});
$(this).on('apply.daterangepicker', function(ev, picker) {
$(this).val(picker.startDate.format(localeFormat));
$(this).trigger("change");
});
});
},
activateDateRangePicker: function(selector) {
$(selector + ' input[data-daterangepickerenable="on"]').each(function(index) {
var localeFormat = $(this).data('format');
var separator = $(this).data('separator');
var rangesList = {};
rangesList[$.kimai.settings['today']] = [moment(), moment()];
rangesList[$.kimai.settings['yesterday']] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
rangesList[$.kimai.settings['thisWeek']] = [moment().startOf('week'), moment().endOf('week')];
rangesList[$.kimai.settings['lastWeek']] = [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')];
rangesList[$.kimai.settings['thisMonth']] = [moment().startOf('month'), moment().endOf('month')];
rangesList[$.kimai.settings['lastMonth']] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
rangesList[$.kimai.settings['thisYear']] = [moment().startOf('year'), moment().endOf('year')];
rangesList[$.kimai.settings['lastYear']] = [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')];
$(this).daterangepicker({
showDropdowns: true,
autoUpdateInput: false,
autoApply: false,
linkedCalendars: false,
locale: {
separator: separator,
format: localeFormat,
firstDay: 1,
applyLabel: $.kimai.settings['apply'],
cancelLabel: $.kimai.settings['cancel'],
customRangeLabel: $.kimai.settings['customRange']
},
ranges: rangesList,
alwaysShowCalendars: true
});
$(this).on('apply.daterangepicker', function(ev, picker) {
$(this).val(picker.startDate.format(localeFormat) + ' - ' + picker.endDate.format(localeFormat));
$(this).trigger("change");
});
});
},
ajaxFormInModal: function(html) {
// the modal that we use to render the form in
var formIdentifier = '#remote_form_modal .modal-content form';
var flashErrorIdentifier = 'div.alert-error';
var $form = $(formIdentifier);
var $modal = $('#remote_form_modal');
// will be (re-)activated later
$form.off('submit');
// load new form from given content
if ($(html).find('#form_modal .modal-content').length > 0 ) {
// switch classes, in case the modal type changed
$modal.on('hidden.bs.modal', function () {
if ($modal.hasClass('modal-danger')) {
$modal.removeClass('modal-danger');
}
});
if ($(html).find('#form_modal').hasClass('modal-danger')) {
$modal.addClass('modal-danger');
}
// TODO cleanup widgets before replacing the content?
$('#remote_form_modal .modal-content').replaceWith(
$(html).find('#form_modal .modal-content')
);
// activate new loaded widgets
$.kimai.activateDateTimePicker(formIdentifier);
}
// show error flash messages
if ($(html).find(flashErrorIdentifier).length > 0) {
$('#remote_form_modal .modal-body').prepend(
$(html).find(flashErrorIdentifier)
);
}
// -----------------------------------------------------------------------
// a fix for firefox focus problems with datepicker in modal
// see https://github.com/kevinpapst/kimai2/issues/618
var enforceModalFocusFn = $.fn.modal.Constructor.prototype.enforceFocus;
$.fn.modal.Constructor.prototype.enforceFocus = function() {};
$modal.on('hidden.bs.modal', function () {
$.fn.modal.Constructor.prototype.enforceFocus = enforceModalFocusFn;
});
// -----------------------------------------------------------------------
// workaround for autofocus attribute, as the modal "steals" it
$modal.on('shown.bs.modal', function () {
$(this).find('input[type=text],textarea,select').filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
});
$modal.modal('show');
// the new form that was loaded via ajax
$form = $(formIdentifier);
// click handler for modal save button, to send forms via ajax
$form.on('submit', function(event){
var btn = $(formIdentifier + ' button[type=submit]').button('loading');
event.preventDefault();
event.stopPropagation();
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: $form.serialize(),
success: function(html) {
btn.button('reset');
var hasFieldError = $(html).find('#form_modal .modal-content .has-error').length > 0;
var hasFormError = $(html).find('#form_modal .modal-content ul.list-unstyled li.text-danger').length > 0;
var hasFlashError = $(html).find(flashErrorIdentifier).length > 0;
if (hasFieldError || hasFormError || hasFlashError) {
$.kimai.ajaxFormInModal(html);
} else {
$.kimai.reloadDatatableWithToolbarFilter();
$modal.modal('hide');
}
return false;
},
error: function(xhr, err) {
// FIXME problem in google and 500 error, keeps on submitting...
// what else could we do? submitting again at least gives us the opportunity to see errors,
// which maybe would be hidden otherwise... this one is totally up for discussion!
$form.submit();
}
});
});
},
activateAjaxFormInModal: function(selector) {
$('body').on('click', selector, function(event) {
event.preventDefault();
event.stopPropagation();
$.ajax({
url: $(this).attr('href'),
success: function(html) {
$.kimai.ajaxFormInModal(html);
},
error: function(xhr, err) {
window.location = $(this).attr('href');
}
});
});
}
};
// default values
$.kimai.defaults = {
locale: 'en',
today: 'Today',
yesterday: 'Yesterday',
apply: 'Apply',
cancel: 'Cancel',
thisWeek: 'This week',
lastWeek: 'Last week',
thisMonth: 'This month',
lastMonth: 'Last month',
thisYear: 'This year',
lastYear: 'Last year',
customRange: 'Custom range',
twentyFourHours: true
};
// once initialized, here are all values
$.kimai.settings = {};
});

View File

@@ -0,0 +1,65 @@
/*
* 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] KimaiAPI: easy access to API methods
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiAPI extends KimaiPlugin {
getId() {
return 'api';
}
get(url, data, callbackSuccess, callbackError) {
jQuery.ajax({
url: url,
headers: {
'X-AUTH-SESSION': true,
'Content-Type':'application/json'
},
method: 'GET',
data: data,
dataType: 'json',
success: callbackSuccess,
error: callbackError
});
}
patch(url, data, callbackSuccess, callbackError) {
jQuery.ajax({
url: url,
headers: {
'X-AUTH-SESSION': true,
'Content-Type':'application/json'
},
method: 'PATCH',
data: data,
dataType: 'json',
success: callbackSuccess,
error: callbackError
});
}
delete(url, callbackSuccess, callbackError) {
jQuery.ajax({
url: url,
headers: {
'X-AUTH-SESSION': true,
'Content-Type':'application/json'
},
method: 'DELETE',
dataType: 'json',
success: callbackSuccess,
error: callbackError
});
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.
*/
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
/**
* Needs to be initialized with a class name.
*
* A link like <a href=# class=remoteLink> can be activated with:
* new KimaiAPILink('remoteLink')
*
* Allows to assign the given selector to any element, which then is used as click-handler
* calling an API method and trigger the event from data-event attribute afterwards.
*
* @param selector
*/
export default class KimaiAPILink extends KimaiClickHandlerReducedInTableRow {
constructor(selector) {
super();
this.selector = selector;
}
init() {
const self = this;
document.addEventListener('click', function(event) {
let target = event.target;
while (target !== null && !target.matches('body')) {
if (target.classList.contains(self.selector)) {
const attributes = target.dataset;
let url = attributes['href'];
if (!url) {
url = target.getAttribute('href');
}
if (attributes.question !== undefined) {
self.getContainer().getPlugin('alert').question(attributes.question, function(value) {
if (value) {
self._callApi(url, attributes);
}
});
} else {
self._callApi(url, attributes);
}
event.preventDefault();
event.stopPropagation();
}
target = target.parentNode;
}
});
}
_callApi(url, attributes)
{
const method = attributes['method'];
const eventName = attributes['event'];
const API = this.getContainer().getPlugin('api');
const eventing = this.getContainer().getPlugin('event');
const alert = this.getContainer().getPlugin('alert');
const successHandle = function(result) {
eventing.trigger(eventName);
if (attributes.msgSuccess) {
alert.success(attributes.msgSuccess);
}
};
const errorHandle = function(xhr, err) {
let message = 'action.update.error';
if (attributes.msgError) {
message = attributes.msgError;
}
if (xhr.responseJSON && xhr.responseJSON.message) {
err = xhr.responseJSON.message;
}
alert.error(message, err);
};
if (method === 'PATCH') {
let data = {};
if (attributes.payload) {
data = attributes.payload;
}
API.patch(url, data, successHandle, errorHandle);
} else if (method === 'DELETE') {
API.delete(url, successHandle, errorHandle);
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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] KimaiActiveRecords: responsible to display the users active records
*/
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiActiveRecords extends KimaiPlugin {
constructor(selector, selectorEmpty) {
super();
this.selector = selector;
this.selectorEmpty = selectorEmpty;
}
getId() {
return 'active-records';
}
init() {
const menu = document.querySelector(this.selector);
// the menu can be hidden if user has no permissions to see it
if (menu === null) {
return;
}
const dropdown = menu.querySelector('ul.dropdown-menu');
this.attributes = dropdown.dataset;
this.itemList = dropdown.querySelector('li > ul.menu');
this.label = menu.querySelector('a > span.label');
const self = this;
const handle = function() { self.reloadActiveRecords(); };
document.addEventListener('kimai.timesheetUpdate', handle);
document.addEventListener('kimai.activityUpdate', handle);
document.addEventListener('kimai.projectUpdate', handle);
document.addEventListener('kimai.customerUpdate', handle);
}
emptyList() {
this.itemList.innerHTML = '';
}
_toggleMenu(hasEntries) {
const menu = document.querySelector(this.selector);
const menuEmpty = document.querySelector(this.selectorEmpty);
menu.style.display = hasEntries ? 'inline-block' : 'none';
if (menuEmpty !== null) {
menuEmpty.style.display = !hasEntries ? 'inline-block' : 'none';
}
}
setEntries(entries) {
this._toggleMenu(entries.length > 0);
if (entries.length === 0) {
this.label.innerText = '';
this.emptyList();
return;
}
let htmlToInsert = '';
const durations = this.getContainer().getPlugin('timesheet-duration');
for (let timesheet of entries) {
htmlToInsert +=
`<li>` +
`<a href="${ this.attributes['href'].replace('000', timesheet.id) }" data-event="kimai.timesheetStop kimai.timesheetUpdate" class="api-link" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">` +
`<div class="pull-left">` +
`<i class="${ this.attributes['icon'] } fa-2x"></i>` +
`</div>` +
`<h4>` +
`<span>${ timesheet.activity.name }</span>` +
`<small>` +
`<span data-title="true" data-since="${ timesheet.begin }" data-format="${ this.attributes['format'] }">${ durations.formatDuration(timesheet.duration, this.attributes['format']) }</span>` +
`</small>` +
`</h4>` +
`<p>${ timesheet.project.name } (${ timesheet.project.customer.name })</p>` +
`</a>` +
`</li>`;
}
if (this.label.dataset.warning < entries.length) {
this.label.classList = 'label label-danger';
} else {
this.label.classList = 'label label-warning';
}
this.label.innerText = entries.length;
this.itemList.innerHTML = htmlToInsert;
durations.updateRecords();
}
reloadActiveRecords() {
const self = this;
const API= this.getContainer().getPlugin('api');
API.get(this.attributes['api'], {}, function(result) {
self.setEntries(result);
});
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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] KimaiActiveRecordsDuration: activate the updates for all active timesheet records on this page
*/
import moment from 'moment';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiActiveRecordsDuration extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'timesheet-duration';
}
init() {
this.updateRecords();
const self = this;
const handle = function() { self.updateRecords(); };
this._updatesHandler = setInterval(handle, 10000);
// this will probably not work as expected, as other event-handler might need longer to update the DOM
document.addEventListener('kimai.timesheetUpdate', handle);
}
unregisterUpdates() {
clearInterval(this._updatesHandler);
}
updateRecords() {
let durations = [];
const activeRecords = document.querySelectorAll(this.selector);
if (activeRecords.length === 0) {
document.title = document.querySelector('body').dataset['title'];
return;
}
for(let record of activeRecords) {
const since = record.getAttribute('data-since');
const format = record.getAttribute('data-format');
const duration = this.formatDuration(since, format);
if (record.getAttribute('data-title') !== null) {
durations.push(duration);
}
record.textContent = duration;
}
if (durations.length === 0) {
return;
}
let title = durations.shift();
let prefix = ' | ';
for (let duration of durations.slice(0, 2)) {
title += prefix + duration;
}
document.title = title;
}
formatDuration(since, format) {
const duration = moment.duration(moment(new Date()).diff(moment(since)));
let hours = parseInt(duration.asHours()).toString();
let minutes = duration.minutes();
let seconds = duration.seconds();
// special case for hours, as they can overflow the 24h barrier - Kimai does not support days as duration unit
if (hours.length === 1) {
hours = '0' + hours;
}
return format.replace('%h', hours).replace('%m', ('0'+minutes).substr(-2)).replace('%s', ('0'+seconds).substr(-2));
}
}

View File

@@ -0,0 +1,189 @@
/*
* 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] KimaiAjaxModalForm
*
* allows to assign the given selector to any element, which then is used as click-handler:
* opening a modal with the content from the URL given in the elements 'data-href' or 'href' attribute
*/
import jQuery from 'jquery';
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableRow {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'modal';
}
init() {
const self = this;
this._addClickHandlerReducedInTableRow(this.selector, function(href) {
self.openUrlInModal(href);
});
}
openUrlInModal(url, errorHandler) {
const self = this;
if (errorHandler === undefined) {
errorHandler = function(xhr, err) {
if (xhr.status !== undefined && xhr.status === 403) {
const alert = self.getContainer().getPlugin('alert');
alert.error(xhr.statusText);
return;
}
window.location = url;
};
}
jQuery.ajax({
url: url,
success: function(html) {
self._openFormInModal(html);
},
error: errorHandler
});
}
_openFormInModal(html) {
const self = this;
// the modal that we use to render the form in
let formIdentifier = '#remote_form_modal .modal-content form';
// if any of these is found in a response, the form will be re-displayed
let flashErrorIdentifier = 'div.alert-error';
// messages to show above the form
let flashMessageIdentifier = 'div.alert';
let form = jQuery(formIdentifier);
let remoteModal = jQuery('#remote_form_modal');
// will be (re-)activated later
form.off('submit');
// load new form from given content
if (jQuery(html).find('#form_modal .modal-content').length > 0 ) {
// switch classes, in case the modal type changed
remoteModal.on('hidden.bs.modal', function () {
if (remoteModal.hasClass('modal-danger')) {
remoteModal.removeClass('modal-danger');
}
});
if (jQuery(html).find('#form_modal').hasClass('modal-danger')) {
remoteModal.addClass('modal-danger');
}
jQuery('#remote_form_modal .modal-content').replaceWith(
jQuery(html).find('#form_modal .modal-content')
);
// activate new loaded widgets
self.getContainer().getPlugin('date-time-picker').activateDateTimePicker(formIdentifier);
self.getContainer().getPlugin('autocomplete').activateAutocomplete(formIdentifier + " .js-autocomplete");
// activate selectpicker if beta test is active
jQuery('.selectpicker').selectpicker('refresh');
}
// show error flash messages
let flashMessages = jQuery(html).find(flashMessageIdentifier);
if (flashMessages.length > 0) {
jQuery('#remote_form_modal .modal-body').prepend(flashMessages);
}
// -----------------------------------------------------------------------
// a fix for firefox focus problems with datepicker in modal
// see https://github.com/kevinpapst/kimai2/issues/618
let enforceModalFocusFn = jQuery.fn.modal.Constructor.prototype.enforceFocus;
jQuery.fn.modal.Constructor.prototype.enforceFocus = function() {};
remoteModal.on('hidden.bs.modal', function () {
jQuery.fn.modal.Constructor.prototype.enforceFocus = enforceModalFocusFn;
});
// -----------------------------------------------------------------------
// workaround for autofocus attribute, as the modal "steals" it
remoteModal.on('shown.bs.modal', function () {
jQuery(this).find('input[type=text],textarea,select').filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
});
remoteModal.modal('show');
// the new form that was loaded via ajax
form = jQuery(formIdentifier);
// click handler for modal save button, to send forms via ajax
form.on('submit', function(event){
const btn = jQuery(formIdentifier + ' button[type=submit]').button('loading');
const eventName = form.attr('data-form-event');
const events = self.getContainer().getPlugin('event');
const alert = self.getContainer().getPlugin('alert');
event.preventDefault();
event.stopPropagation();
jQuery.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
success: function(html) {
btn.button('reset');
let hasFieldError = jQuery(html).find('#form_modal .modal-content .has-error').length > 0;
let hasFormError = jQuery(html).find('#form_modal .modal-content ul.list-unstyled li.text-danger').length > 0;
let hasFlashError = jQuery(html).find(flashErrorIdentifier).length > 0;
if (hasFieldError || hasFormError || hasFlashError) {
self._openFormInModal(html);
} else {
events.trigger(eventName);
// try to find form defined messages first ...
let msg = form.attr('data-msg-success');
if (msg === null || msg === undefined) {
// ... but if none was available, check the response to find server rendered flash-message
let flashMessage = jQuery(html).find('section.content div.row div.alert.alert-success');
if (flashMessage.length > 0) {
let flashContent = flashMessage.contents();
if (flashContent.length === 3) {
msg = flashContent[2].textContent;
}
}
}
// ... and if even that is not available, we use a generic fallback message
if (msg === null || msg === undefined) {
msg = 'action.update.success';
}
remoteModal.modal('hide');
alert.success(msg);
}
return false;
},
error: function(xhr, err) {
let message = form.attr('data-msg-error');
if (message === null || message === undefined) {
message = 'action.update.error';
}
if (xhr.responseJSON && xhr.responseJSON.message) {
err = xhr.responseJSON.message;
} else if (xhr.status && xhr.statusText) {
err = '[' + xhr.status +'] ' + xhr.statusText;
}
alert.error(message, err);
}
});
});
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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] KimaiAlert: notifications for Kimai
*/
import Swal from 'sweetalert2'
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiAlert extends KimaiPlugin {
getId() {
return 'alert';
}
error(title, message) {
const translation = this.getContainer().getTranslation();
if (translation.has(title)) {
title = translation.get(title);
}
if (translation.has(message)) {
message = translation.get(message);
}
Swal.fire({
type: 'error',
title: title.replace('%reason%', ''),
text: message,
});
}
success(message) {
const translation = this.getContainer().getTranslation();
if (translation.has(message)) {
message = translation.get(message);
}
Swal.fire({
timer: 2000,
toast: true,
position: 'top-end',
showConfirmButton: false,
type: 'success',
title: message,
});
}
/**
* Callback receives a value and needs to decide what should happen with it
*
* @param message
* @param callback
*/
question(message, callback) {
const translation = this.getContainer().getTranslation();
if (translation.has(message)) {
message = translation.get(message);
}
Swal.fire({
title: message,
type: 'question',
showCancelButton: true,
confirmButtonText: translation.get('confirm'),
cancelButtonText: translation.get('cancel')
}).then((result) => {
callback(result.value);
});
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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] KimaiAlternativeLinks
*
* allows to assign the given selector to any element, which then is used as click-handler
* redirecting to the URL given in the elements 'data-href' or 'href' attribute
*/
import jQuery from 'jquery';
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
export default class KimaiAlternativeLinks extends KimaiClickHandlerReducedInTableRow {
constructor(selector) {
super();
this.selector = selector;
}
init() {
this._addClickHandlerReducedInTableRow(this.selector, function(href) {
window.location = href;
});
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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.
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
/**
* Supporting auto-complete fields via API.
* Currently used for timesheet tagging in toolbar and edit dialogs.
*/
export default class KimaiAutocomplete extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
init() {
this.activateAutocomplete(this.selector);
}
getId() {
return 'autocomplete';
}
splitTagList(val) {
return val.split(/,\s*/);
}
extractLastTag(term) {
return this.splitTagList(term).pop();
}
activateAutocomplete(selector)
{
const apiUrl = jQuery(selector).attr('data-autocomplete-url');
const self = this;
const API = self.getContainer().getPlugin('api');
jQuery(selector)
// don't navigate away from the field on tab when selecting an item
.on("keydown", function (event) {
if (event.keyCode === jQuery.ui.keyCode.TAB &&
jQuery(this).autocomplete("instance").menu.active) {
event.preventDefault();
}
})
.autocomplete({
source: function (request, response) {
const lastEntry = self.extractLastTag(request.term);
API.get(apiUrl, {'name': lastEntry}, function(data){
response(data);
});
},
search: function () {
// custom minLength
var term = self.extractLastTag(this.value);
if (term.length < 2) {
return false;
}
},
focus: function () {
// prevent value inserted on focus
return false;
},
select: function (event, ui) {
var terms = self.splitTagList(this.value);
// remove the current input
terms.pop();
// check if selected tag is already in list
if (!terms.includes(ui.item.value)) {
// add the selected item
terms.push(ui.item.value);
}
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join(", ");
$(this).trigger('change');
return false;
}
}
);
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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] KimaiClickHandlerReducedInTableRow: abstract class
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiClickHandlerReducedInTableRow extends KimaiPlugin {
_addClickHandlerReducedInTableRow(selector, callback)  {
jQuery('body').on('click', selector, function(event) {
// just in case an inner element is editable, than this should not be triggered
if (event.target.parentNode.isContentEditable || event.target.isContentEditable) {
return;
}
// handles the "click" on table rows to open an entry for editing: when a button within a row is clicked,
// we don't want the table row event to be processed - so we intercept it
let target = event.target;
if (event.currentTarget.matches('tr')) {
while (target !== null && !target.matches('body')) {
if (target.matches('a') || target.matches ('button')) {
return;
}
target = target.parentNode;
}
}
event.preventDefault();
event.stopPropagation();
let href = jQuery(this).attr('data-href');
if (!href) {
href = jQuery(this).attr('href');
}
callback(href);
});
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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] KimaiDatatable: handles functionality for the datatable
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiDatatable extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'datatable';
}
init() {
const dataTable = document.querySelector(this.selector);
// not every page contains a dataTable
if (dataTable === null) {
return;
}
const attributes = dataTable.dataset;
const events = attributes['reloadEvent'];
if (events === undefined) {
return;
}
const self = this;
const handle = function() { self.reloadDatatable(); };
for (let eventName of events.split(' ')) {
document.addEventListener(eventName, handle);
}
}
reloadDatatable() {
const durations = this.getContainer().getPlugin('timesheet-duration');
const form = jQuery('.toolbar form');
let loading = '<div class="overlay"><i class="fas fa-sync fa-spin"></i></div>';
jQuery('section.content').append(loading);
// remove the empty fields to prevent errors
let formData = jQuery('.toolbar form :input')
.filter(function(index, element) {
return jQuery(element).val() != '';
})
.serialize();
jQuery.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: formData,
success: function(html) {
jQuery('section.content').replaceWith(
jQuery(html).find('section.content')
);
durations.updateRecords();
},
error: function(xhr, err) {
form.submit();
}
});
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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] KimaiDatatableColumnView: manages the visibility of data-table columns in cookies
*/
import Cookies from 'js-cookie';
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiDatatableColumnView extends KimaiPlugin {
constructor(dataAttribute) {
super();
this.dataAttribute = dataAttribute;
}
getId() {
return 'datatable-column-visibility';
}
init() {
let dataTable = document.querySelector('[' + this.dataAttribute + ']');
if (dataTable === null) {
return;
}
this.id = dataTable.getAttribute(this.dataAttribute);
this.modal = document.getElementById('modal_' + this.id);
this.bindButtons();
}
bindButtons() {
let self = this;
this.modal.querySelector('button[data-type=save]').addEventListener('click', function() {
self.saveVisibility();
});
this.modal.querySelector('button[data-type=reset]').addEventListener('click', function() {
self.resetVisibility();
});
for (let checkbox of this.modal.querySelectorAll('form input[type=checkbox]')) {
checkbox.addEventListener('click', function () {
self.changeVisibility(checkbox.getAttribute('name'));
});
}
}
saveVisibility() {
const form = this.modal.getElementsByTagName('form')[0];
let settings = {};
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
settings[checkbox.getAttribute('name')] = checkbox.checked;
}
Cookies.set(form.getAttribute('name'), JSON.stringify(settings), {expires: 365});
jQuery(this.modal).modal('toggle');
}
resetVisibility() {
const form = this.modal.getElementsByTagName('form')[0];
Cookies.remove(form.getAttribute('name'));
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
if (!checkbox.checked) {
checkbox.click();
}
}
jQuery(this.modal).modal('toggle');
}
changeVisibility(columnName) {
const table = document.getElementById('datatable_' + this.id).getElementsByClassName('dataTable')[0];
let column = 0;
let foundColumn = false;
for (let columnElement of table.getElementsByTagName('th')) {
if (columnElement.getAttribute('data-field') === columnName) {
foundColumn = true;
break;
}
column++;
}
if (!foundColumn) {
return;
}
for (let rowElement of table.getElementsByTagName('tr')) {
rowElement.children[column].classList.toggle('hidden');
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
import moment from 'moment';
export default class KimaiDatePicker extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'date-picker';
}
init() {
this.activateDatePicker(this.selector);
}
activateDatePicker(selector) {
let translator = this.getContainer().getTranslation();
jQuery(selector + ' input[data-datepickerenable="on"]').each(function(index) {
let localeFormat = jQuery(this).data('format');
jQuery(this).daterangepicker({
singleDatePicker: true,
showDropdowns: true,
autoUpdateInput: false,
locale: {
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('confirm'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange'),
daysOfWeek: moment.weekdaysShort(),
monthNames: moment.months(),
}
});
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
jQuery(this).val(picker.startDate.format(localeFormat));
jQuery(this).trigger("change");
});
});
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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] KimaiDateRangePicker: activate the (daterange picker) compound field in toolbar
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
import moment from 'moment';
export default class KimaiDateRangePicker extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'date-range-picker';
}
init() {
this.activateDateRangePicker(this.selector);
}
activateDateRangePicker(selector) {
let translator = this.getContainer().getTranslation();
jQuery(selector + ' input[data-daterangepickerenable="on"]').each(function(index) {
let localeFormat = jQuery(this).data('format');
let separator = jQuery(this).data('separator');
let rangesList = {};
rangesList[translator.get('today')] = [moment(), moment()];
rangesList[translator.get('yesterday')] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
rangesList[translator.get('thisWeek')] = [moment().startOf('week'), moment().endOf('week')];
rangesList[translator.get('lastWeek')] = [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')];
rangesList[translator.get('thisMonth')] = [moment().startOf('month'), moment().endOf('month')];
rangesList[translator.get('lastMonth')] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
rangesList[translator.get('thisYear')] = [moment().startOf('year'), moment().endOf('year')];
rangesList[translator.get('lastYear')] = [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')];
jQuery(this).daterangepicker({
showDropdowns: true,
autoUpdateInput: false,
autoApply: false,
linkedCalendars: false,
locale: {
separator: separator,
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('confirm'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange'),
daysOfWeek: moment.weekdaysShort(),
monthNames: moment.months(),
},
ranges: rangesList,
alwaysShowCalendars: true
});
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
jQuery(this).val(picker.startDate.format(localeFormat) + ' - ' + picker.endDate.format(localeFormat));
jQuery(this).trigger("change");
});
});
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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] KimaiDateTimePicker: activate the (datetime picker) field in timesheet edit dialog
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
import moment from 'moment';
export default class KimaiDateTimePicker extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'date-time-picker';
}
init() {
this.activateDateTimePicker(this.selector);
}
activateDateTimePicker(selector) {
let translator = this.getContainer().getTranslation();
let configuration = this.getContainer().getConfiguration();
jQuery(selector + ' input[data-datetimepicker="on"]').each(function(index) {
let localeFormat = jQuery(this).data('format');
jQuery(this).daterangepicker({
singleDatePicker: true,
timePicker: true,
timePicker24Hour: configuration.get('twentyFourHours'),
showDropdowns: true,
autoUpdateInput: false,
locale: {
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('confirm'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange'),
daysOfWeek: moment.weekdaysShort(),
monthNames: moment.months(),
}
});
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
jQuery(this).val(picker.startDate.format(localeFormat));
jQuery(this).trigger("change");
});
});
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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] KimaiEvent: helper to trigger events
*/
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiEvent extends KimaiPlugin {
getId() {
return 'event';
}
trigger(name, details) {
if (name === null || name === undefined) {
return;
}
for(let event of name.split(' ')) {
let triggerEvent = new Event(event);
if (details !== undefined) {
triggerEvent = new CustomEvent(name, {detail: details});
}
document.dispatchEvent(triggerEvent);
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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] KimaiJqueryPluginInitializer: initialize jQuery plugins
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiJqueryPluginInitializer extends KimaiPlugin {
init() {
// activate the dropdown functionality
jQuery('.dropdown-toggle').dropdown();
// activate the tooltip functionality
jQuery('[data-toggle="tooltip"]').tooltip();
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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] KimaiPauseRecord
*
* allows to pause records
* THIS IS JUST A DRAFT FOR THE DOM, IT IS NOT SUPPORTED IN KIMAI ITSELF!
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiPauseRecord extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
init() {
this.activate(this.selector);
}
activate(selector) {
jQuery(selector + ' .pull-left i').hover(function () {
let link = jQuery(this).parents('a');
link.attr('href', link.attr('href').replace('/stop', '/pause'));
jQuery(this).removeClass('fa-stop-circle').addClass('fa-pause-circle').addClass('text-orange');
},function () {
let link = jQuery(this).parents('a');
link.attr('href', link.attr('href').replace('/pause', '/stop'));
jQuery(this).removeClass('fa-pause-circle').removeClass('text-orange').addClass('fa-stop-circle');
});
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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] KimaiRecentActivities: responsible to reload the users recent activities
*/
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiRecentActivities extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'recent-activities';
}
init() {
const menu = document.querySelector(this.selector);
// the menu can be hidden if user has no permissions to see it
if (menu === null) {
return;
}
const dropdown = menu.querySelector('ul.dropdown-menu');
this.attributes = dropdown.dataset;
this.itemList = dropdown.querySelector('li > ul.menu');
const self = this;
const handle = function() { self.reloadRecentActivities(); };
// don't block initial browser rendering
setTimeout(handle, 500);
document.addEventListener('kimai.timesheetStop', handle);
document.addEventListener('kimai.activityUpdate', handle);
document.addEventListener('kimai.projectUpdate', handle);
document.addEventListener('kimai.customerUpdate', handle);
}
emptyList() {
this.itemList.innerHTML = '';
}
setEntries(entries) {
if (entries.length === 0) {
this.emptyList();
return;
}
let htmlToInsert = '';
for (let timesheet of entries) {
let label = this.attributes['template']
.replace('%customer%', timesheet.project.customer.name)
.replace('%project%', timesheet.project.name)
.replace('%activity%', timesheet.activity.name);
htmlToInsert +=
`<li>` +
`<a href="${ this.attributes['href'].replace('000', timesheet.id) }" data-event="kimai.timesheetStart kimai.timesheetUpdate" class="api-link" data-method="PATCH" data-msg-error="timesheet.start.error" data-msg-success="timesheet.start.success">` +
`<i class="${ this.attributes['icon'] }"></i> ${ label }` +
`</a>` +
`</li>`;
}
this.itemList.innerHTML = htmlToInsert;
}
reloadRecentActivities() {
const self = this;
const API = this.getContainer().getPlugin('api');
API.get(this.attributes['api'], {}, function(result) {
self.setEntries(result);
});
}
}

View File

@@ -0,0 +1,115 @@
/*
* 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] KimaiSelectDataAPI: <select> boxes with dynamic data from API
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiSelectDataAPI extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'select-data-api';
}
init() {
this.activateApiSelects(this.selector);
}
activateApiSelects(selector) {
const self = this;
const API = this.getContainer().getPlugin('api');
jQuery('body').on('change', selector, function(event) {
let apiUrl = jQuery(this).attr('data-api-url').replace('-s-', jQuery(this).val());
const targetSelect = '#' + jQuery(this).attr('data-related-select');
// if the related target select does not exist, we do not need to load the related data
if (jQuery(targetSelect).length === 0) {
return;
}
if (jQuery(this).val() === '') {
if (jQuery(this).attr('data-empty-url') === undefined) {
self._updateSelect(targetSelect, {});
jQuery(targetSelect).attr('disabled', 'disabled');
return;
}
apiUrl = jQuery(this).attr('data-empty-url').replace('-s-', jQuery(this).val());
}
jQuery(targetSelect).removeAttr('disabled');
API.get(apiUrl, {}, function(data){
self._updateSelect(targetSelect, data);
});
});
}
_updateSelect(selectName, data) {
let select = jQuery(selectName);
let emptyOption = jQuery(selectName + ' option[value=""]');
select.find('option').remove().end().find('optgroup').remove().end();
if (emptyOption.length !== 0) {
select.append('<option value="">' + emptyOption.text() + '</option>');
}
const options = {};
for (const apiData of data) {
let title = apiData.parentTitle;
if (title === null) {
title = '__empty__';
}
if (!options.hasOwnProperty(title)) {
options[title] = [];
}
options[title].push(apiData);
}
const ordered = {};
Object.keys(options).sort().forEach(function(key) {
ordered[key] = options[key];
});
let htmlOptions = '';
let emptyOptions = '';
for (const [key, value] of Object.entries(ordered)) {
if (key === '__empty__') {
for (const entity of value) {
emptyOptions += '<option value="' + entity.id + '">' + entity.name + '</option>';
}
continue;
}
htmlOptions += '<optgroup label="' + key + '">';
for (const entity of value) {
htmlOptions += '<option value="' + entity.id + '">' + entity.name + '</option>';
}
htmlOptions += '</optgroup>';
}
select.append(htmlOptions);
select.append(emptyOptions);
// if we don't trigger the change, the other selects won't be resetted
select.trigger('change');
// if the beta test kimai.theme.select_type is active, this will tell the selects to refresh
jQuery('.selectpicker').selectpicker('refresh');
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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] KimaiThemeInitializer: initialize theme functionality
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiThemeInitializer extends KimaiPlugin {
init() {
this.registerAutomaticAlertRemove('div.alert-success', 5000);
}
/**
* auto hide success messages, as they are just meant as user feedback and not as a permanent information
*
* @param {string} selector
* @param {integer} interval
*/
registerAutomaticAlertRemove(selector, interval) {
const self = this;
this._alertRemoveHandler = setInterval(
function() {
self.hideAlert(selector);
},
interval
);
}
unregisterAutomaticAlertRemove() {
clearInterval(this._alertRemoveHandler);
}
/**
* @param {string} selector
*/
hideAlert(selector) {
jQuery(selector).alert('close');
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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] KimaiToolbar: some event listener to handle the toolbar/data-table filter, toolbar and navigation
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiToolbar extends KimaiPlugin {
init() {
const datatable = this.getContainer().getPlugin('datatable');
// This catches all clicks on the pagination and prevents the default action, as we want to relad the page via JS
jQuery('body').on('click', 'div.navigation ul.pagination li a', function(event) {
let pager = jQuery(".toolbar form input[name='page']");
if (pager.length === 0) {
return;
}
event.preventDefault();
event.stopPropagation();
let urlParts = jQuery(this).attr('href').split('/');
let page = urlParts[urlParts.length-1];
pager.val(page);
pager.trigger('change');
return false;
});
// Reset the page if any other value is changed, otherwise we might end up with a limited set
// of data which does not support the given page - and it would be just wrong to stay in the same page
jQuery('.toolbar form input').change(function (event) {
switch (event.target.id) {
case 'page':
break;
default:
jQuery('.toolbar form input#page').val(1);
}
datatable.reloadDatatable();
});
jQuery('.toolbar form select').change(function (event) {
let reload = true;
switch (event.target.id) {
case 'customer':
if (jQuery('.toolbar form select#project').length > 0) {
reload = false;
}
break;
case 'project':
if (jQuery('.toolbar form select#activity').length > 0) {
reload = false;
}
break;
}
jQuery('.toolbar form input#page').val(1);
if (reload) {
datatable.reloadDatatable();
}
});
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.
*/
import KimaiPlugin from '../KimaiPlugin';
/**
* Needs to be initialized with a class name.
*
* A link like <a href=# class=remoteLink> can be activated with:
* new KimaiToolbarAction('remoteLink')
*
* @param selector
*/
export default class KimaiToolbarAction extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
init() {
const self = this;
document.addEventListener('click', function(event) {
let target = event.target;
while (target !== null && !target.matches('body')) {
if (target.classList.contains(self.selector)) {
const form = document.querySelector('div.toolbar form.navbar-form');
if (form === null) {
return;
}
const prevAction = form.action;
const prevMethod = form.method;
form.target = '_blank';
form.action = target.href;
if (target.dataset.method !== undefined) {
form.method = target.dataset.method;
}
form.submit();
form.target = '';
form.action = prevAction;
form.method = prevMethod;
event.preventDefault();
event.stopPropagation();
}
target = target.parentNode;
}
});
}
}

View File

@@ -1,55 +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.
*/
$(document).ready(function () {
/* Submit the pagination including the toolbar filters */
$('body').on('click', 'div.navigation ul.pagination li a', function(event) {
var $pager = $(".toolbar form input[name='page']");
if ($pager.length === 0) {
return;
}
event.preventDefault();
event.stopPropagation();
var $urlParts = $(this).attr('href').split('/');
var page = $urlParts[$urlParts.length-1];
$pager.val(page);
$pager.trigger('change');
return false;
});
$('.toolbar form input').change(function (event) {
switch (event.target.id) {
case 'page':
break;
default:
$('.toolbar form input#page').val(1);
}
$.kimai.reloadDatatableWithToolbarFilter();
});
$('.toolbar form select').change(function (event) {
var reload = true;
switch (event.target.id) {
case 'customer':
if ($('.toolbar form select#project').length > 0) {
reload = false;
}
break;
case 'project':
if ($('.toolbar form select#activity').length > 0) {
reload = false;
}
break;
}
$('.toolbar form input#page').val(1);
if (reload) {
$.kimai.reloadDatatableWithToolbarFilter();
}
});
});

View File

@@ -6,37 +6,20 @@
*/
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
.error-page {
margin-bottom: 50px;
}
@media print{
.sf-toolbar, .control-sidebar {display: none !important;}
}
@import 'variables';
@import 'error-page';
@import 'print';
@import 'content';
@import 'toolbar';
/* ================================ SIDEBAR ================================ */
/* Used for the Sidebar UserPanel (which is deactivated right now) */
.user-panel>.info {
left: 80px;
}
/* ================================ FOOTER ================================ */
footer.main-footer {
padding: 5px;
font-size: 80%;
}
@import 'sidebar';
@import 'footer';
@import 'invoice';
@import 'datatable';
@import 'datatable-extensions';
@import 'calendar';
@import 'dashboard';
@import 'navbar';
@import 'daterangepicker';
@import 'tooltip';
@import 'skins';
@import 'sweetalert';
@import 'autocomplete';

View File

@@ -0,0 +1,76 @@
/*
* 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.
*/
.ui-widget {
font-size: 1em/*{fsDefault}*/;
}
.ui-widget.ui-widget-content {
border: 1px solid #c5c5c5/*{borderColorDefault}*/;
}
.ui-widget-content {
border: 1px solid #dddddd/*{borderColorContent}*/;
background: #ffffff/*{bgColorContent}*/ /*{bgImgUrlContent}*/ /*{bgContentXPos}*/ /*{bgContentYPos}*/ /*{bgContentRepeat}*/;
color: #333333/*{fcContent}*/;
}
.ui-menu {
list-style: none;
padding: 0;
margin: 0;
display: block;
outline: 0;
}
.ui-menu .ui-menu-item {
margin: 0;
cursor: pointer;
}
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
}
.ui-front {
z-index: 1200;
}
.ui-menu .ui-menu-item-wrapper {
position: relative;
padding: 3px 1em 3px .4em;
}
.ui-menu .ui-state-focus,
.ui-menu .ui-state-active {
margin: -1px;
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active,
a.ui-button:active,
.ui-button:active,
.ui-button.ui-state-active:hover {
border: 1px solid #003eff/*{borderColorActive}*/;
background: #007fff/*{bgColorActive}*/ /*{bgImgUrlActive}*/ /*{bgActiveXPos}*/ /*{bgActiveYPos}*/ /*{bgActiveRepeat}*/;
font-weight: normal/*{fwDefault}*/;
color: #ffffff/*{fcActive}*/;
}
.ui-icon-background,
.ui-state-active .ui-icon-background {
border: #003eff/*{borderColorActive}*/;
background-color: #ffffff/*{fcActive}*/;
}
.ui-state-active a,
.ui-state-active a:link,
.ui-state-active a:visited {
color: #ffffff/*{fcActive}*/;
text-decoration: none;
}

View File

@@ -1,7 +1,8 @@
.popover-content {
.calendar-entry {
ul {
padding: 0 0 0 10px;
padding: 0;
list-style-type: none;
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.
*/
span.label-activity,
span.label-project,
span.label-customer {
display: inline-block;
.dot {
height: 10px;
width: 10px;
background-color: $gray-lte;
border-radius: 50%;
display: inline-block;
}
}
table.dataTable {
/* action column */
.actions {
width: 90px;
li.delete a {
color: #dd4b39;
}
}
tr {
/* summary row - provided in the users own timesheet table */
&.summary td {
font-weight: bold;
border-bottom: 1px solid #ccc;
}
&.open-edit {
cursor: pointer;
}
}
td {
/*
Make sure that the action buttons do not line-break if another column takes all available space
See https://github.com/twbs/bootstrap/issues/9939#issuecomment-37682952
*/
.btn-group {
display: flex;
}
.label {
font-size: 85%;
font-weight: 500;
padding: .3em .6em .3em;
}
span.label-activity,
span.label-project,
span.label-customer {
a {
color: unset;
padding-bottom: 2px;
border-bottom: 1px dotted $gray-lte;
&:hover {
border-bottom: 1px dotted darken($gray-lte, 20);
}
}
}
span.label-invisible {
opacity: 0.8;
}
}
}

View File

@@ -34,7 +34,7 @@ section.content .overlay,
.overlay-wrapper .overlay {
z-index: 50;
background: rgba(255, 255, 255, 0.7);
border-radius: 3px;
border-radius: $box-border-radius;
}
.box .overlay.dark,
section.content .overlay.dark,
@@ -264,25 +264,3 @@ div.dataTables_scrollHead table.table-bordered {
.table-striped.dataTable tbody tr.active:nth-child(odd) th {
background-color: #017ebc;
}
table.dataTable {
/* summary row, like the timesheet table provides */
tr.summary td {
font-weight: bold;
border-bottom: 1px solid #ccc;
}
td {
/*
Make sure that the action buttons do not line-break if another column takes all available space
See https://github.com/twbs/bootstrap/issues/9939#issuecomment-37682952
*/
.btn-group {
display: flex;
}
.label {
font-size: 85%;
font-weight: 500;
padding: .3em .6em .3em;
}
}
}

View File

@@ -0,0 +1,10 @@
/*
* 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.
*/
.error-page {
margin-bottom: 50px;
}

11
assets/sass/footer.scss Normal file
View File

@@ -0,0 +1,11 @@
/*
* 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.
*/
footer.main-footer {
padding: 5px;
font-size: 80%;
}

View File

@@ -7,18 +7,23 @@
.navbar-nav {
li>a.ddt-large {
padding: 16px 12px 11px 12px;
padding: 15px 12px 11px 10px;
font-size: 10px;
}
li.messages-menu ul.menu li .pull-left i {
color: #dd4b39;
li.messages-menu ul.menu li {
a:hover .pull-left i {
color: #dd4b39;
}
a .pull-left i {
color: #444;
}
}
.messages-menu>.dropdown-menu>li .menu>li>a>p {
overflow: hidden;
text-overflow: ellipsis;
}
.start_record {
text-align: center;
.messages-menu>.dropdown-menu>li .menu>li>a>h4>span {
margin-right: 55px;
}
.dropdown-menu {
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
@@ -30,6 +35,12 @@
ul {
li {
a {
&:hover {
background-color: #f4f4f4;
i {
color: $blue;
}
}
color: #444;
i {
padding-right: 10px;
@@ -41,6 +52,9 @@
}
}
}
.notifications-menu>.dropdown-menu>li .menu>li>a:hover i {
color: $green;
}
}
@media (max-width: $screen-sm-min) {
@@ -75,4 +89,4 @@ li.open .ticktac i.running{
left: 40px;
padding-top: 0px;
}
*/
*/

10
assets/sass/print.scss Normal file
View File

@@ -0,0 +1,10 @@
/*
* 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.
*/
@media print{
.sf-toolbar, .control-sidebar {display: none !important;}
}

41
assets/sass/sidebar.scss Normal file
View File

@@ -0,0 +1,41 @@
/*
* 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.
*/
/* UserPanel is deactivated right now */
/*
.user-panel>.info {
left: 80px;
}
*/
/* Right sidebar with additional tabs for personal settings and "About Kimai" section */
/*
.control-sidebar {
select {
color: $input-color;
background-color: $input-bg;
}
div.image {
img.img-circle {
max-width: 34px;
}
}
}
*/
@media (min-width: $screen-sm-min) {
.sidebar-mini.sidebar-collapse .sidebar-menu li.header {
border-top-width: 1px;
border-top-style: solid;
display: block !important;
margin-top: 16px;
opacity: 0.4;
span {
display: none !important;
}
}
}

View File

@@ -0,0 +1,3 @@
.swal2-popup {
font-size: unset;
}

View File

@@ -12,19 +12,6 @@
background: transparent;
}
/* Right sidebar with additional tabs for personal settings and "About Kimai" section */
.control-sidebar {
select {
color: $input-color;
background-color: $input-bg;
}
div.image {
img.img-circle {
max-width: 34px;
}
}
}
.toolbar-pad {
padding: 10px;
}

View File

@@ -1,36 +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.
*/
/*
* These styles are not yet used and need some love before actively promoted in the UI.
* They could be used in: templates/macros/widgets.html.twig::label
*/
[data-tooltip] {
position: relative;
overflow: visible;
}
[data-tooltip]:after {
transition: all .2s ease;
content: attr(data-tooltip);
position: absolute;
top: -24px;
left: 50%;
transform: translateX(-50%);
background: $gray;
color: #fff;
padding: 2px 12px;
pointer-events: none;
white-space: nowrap;
font-size: 11px;
line-height: 18px;
border-radius: $border-radius-small;
z-index: 2;
}
[data-tooltip]:not(:hover):after {
opacity: 0;
}

126
assets/sass/variables.scss Normal file
View File

@@ -0,0 +1,126 @@
/*
* 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.
*/
/**
* A simple one to one clone of /node_modules/admin-lte/build/less/variables.less
*/
// COLORS
// --------------------------------------------------------
// Primary
//$light-blue: #3c8dbc;
// Danger
$red: #dd4b39;
// Success
$green: #00a65a;
// Info
//$aqua: #00c0ef;
// Warning
//$yellow: #f39c12;
$blue: #0073b7;
//$navy: #001F3F;
//$teal: #39CCCC;
//$olive: #3D9970;
//$lime: #01FF70;
//$orange: #FF851B;
//$fuchsia: #F012BE;
//$purple: #605ca8;
//$maroon: #D81B60;
//$black: #111;
$gray-lte: #d2d6de;
// LAYOUT
// --------------------------------------------------------
//
// Side bar and logo width
//$sidebar-width: 230px;
//
// Link colors (Aka: <a> tags)
//$link-color: $light-blue;
//$link-hover-color: lighten($link-color, 15%);
//
// Body background (Affects main content background only)
//$body-bg: #ecf0f5; // DEPRECATED
//$content-bg: #ecf0f5;
//
// SIDEBAR SKINS
// --------------------------------------------------------
//
// Dark sidebar
//$sidebar-dark-bg: #222d32;
//$sidebar-dark-hover-bg: darken($sidebar-dark-bg, 2%);
//$sidebar-dark-color: lighten($sidebar-dark-bg, 60%);
//$sidebar-dark-hover-color: #fff;
//$sidebar-dark-submenu-bg: lighten($sidebar-dark-bg, 5%);
//$sidebar-dark-submenu-color: lighten($sidebar-dark-submenu-bg, 40%);
//$sidebar-dark-submenu-hover-color: #fff;
//
// Light sidebar
//$sidebar-ligh//t-bg: #f9fafc;
//$sidebar-light-hover-bg: lighten(#f0f0f1, 1.5%);
//$sidebar-light-color: #444;
//$sidebar-light-hover-color: #000;
//$sidebar-light-submenu-bg: $sidebar-light-hover-bg;
//$sidebar-light-submenu-color: #777;
//$sidebar-light-submenu-hover-color: #000;
//
// sidebar-expanded-on-hover
//$sidebar-expanded-shadow: 3px 0 8px rgba(0,0,0,.125);
//
// CONTROL SIDEBAR
// --------------------------------------------------------
//$control-sidebar-width: $sidebar-width;
//
// BOXES
// --------------------------------------------------------
//$box-border-color: #f4f4f4;
$box-border-radius: 3px;
//$box-footer-bg: #fff;
//$box-boxshadow: 0 1px 1px rgba(0, 0, 0, .1);
//$box-padding: 10px;
//
// Box variants
//$box-default-border-top-color: #d2d6de;
//
// BUTTONS
// --------------------------------------------------------
//$btn-boxshadow: none;
//
// PROGRESS BARS
// --------------------------------------------------------
//$progress-bar-border-radius: 1px;
//$progress-bar-sm-border-radius: 1px;
//$progress-bar-xs-border-radius: 1px;
//
// FORMS
// --------------------------------------------------------
//$input-radius: 0;
//
// BUTTONS
// --------------------------------------------------------
//
// Border radius for non flat buttons
//$btn-border-radius: 3px;
//
// DIRECT CHAT
// --------------------------------------------------------
//$direct-chat-height: 250px;
//$direct-chat-default-msg-bg: $gray-lte;
//$direct-chat-default-font-color: #444;
//$direct-chat-default-msg-border-color: $gray-lte;
//
// CHAT WIDGET
// --------------------------------------------------------
//$attachment-border-radius: 3px;
//
// TRANSITIONS SETTINGS
// --------------------------------------------------------
//
// Transition global options
//$transition-speed: .3s;
//$transition-fn: ease-in-out;
//

View File

@@ -19,7 +19,7 @@ if (!isset($_SERVER['APP_ENV'])) {
if (!class_exists(Dotenv::class)) {
throw new \RuntimeException('APP_ENV environment variable is not defined. You need to define environment variables for configuration or add "symfony/dotenv" as a Composer dependency to load variables from a .env file.');
}
(new Dotenv())->load(__DIR__.'/../.env');
(new Dotenv(true))->load(__DIR__.'/../.env');
}
$input = new ArgvInput();

View File

@@ -13,18 +13,16 @@
"php": "^7.2",
"ext-gd": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pdo": "*",
"ext-zip": "*",
"beberlei/doctrineextensions": "^1.2",
"doctrine/doctrine-fixtures-bundle": "^3.0",
"erusev/parsedown": "^1.6",
"friendsofsymfony/rest-bundle": "^2.3",
"friendsofsymfony/user-bundle": "~2.0",
"fzaninotto/faker": "^1.8",
"friendsofsymfony/rest-bundle": "^2.5",
"friendsofsymfony/user-bundle": "dev-master",
"gedmo/doctrine-extensions": "^2.4",
"jms/metadata": "^2.0",
"jms/serializer": "^2.3",
"jms/serializer-bundle": "^3.2",
"kevinpapst/adminlte-bundle": "~2.6",
"kimai/kimai2-composer": "^0.1",
@@ -34,6 +32,7 @@
"ocramius/proxy-manager": "2.1.1",
"phpoffice/phpspreadsheet": "^1.4",
"phpoffice/phpword": "^0.15.0",
"psr/log": "^1.1",
"sensio/framework-extra-bundle": "^5.2",
"symfony/asset": "^4.0",
"symfony/console": "^4.0",
@@ -44,33 +43,42 @@
"symfony/form": "^4.0",
"symfony/framework-bundle": "^4.0",
"symfony/intl": "^4.0",
"symfony/maker-bundle": "^1.0",
"symfony/monolog-bundle": "^3.1",
"symfony/monolog-bundle": "^3.4",
"symfony/orm-pack": "^1.0",
"symfony/profiler-pack": "^1.0",
"symfony/security-bundle": "^4.0",
"symfony/security-csrf": "^4.0",
"symfony/serializer": "^4.0",
"symfony/swiftmailer-bundle": "^3.1",
"symfony/swiftmailer-bundle": "^3.2",
"symfony/translation": "^4.0",
"symfony/twig-bundle": "^4.0",
"symfony/validator": "^4.0",
"symfony/web-server-bundle": "^4.0",
"symfony/webpack-encore-bundle": "^1.5",
"symfony/yaml": "^4.0",
"twig/extensions": "^1.5",
"white-october/pagerfanta-bundle": "^1.1"
},
"require-dev": {
"dama/doctrine-test-bundle": "^5.0",
"doctrine/doctrine-fixtures-bundle": "^3.2",
"friendsofphp/php-cs-fixer": "^2.10",
"fzaninotto/faker": "^1.8",
"phpstan/phpstan": "^0.11.7",
"phpstan/phpstan-doctrine": "^0.11.4",
"phpstan/phpstan-phpunit": "^0.11.2",
"phpstan/phpstan-symfony": "^0.11.6",
"phpunit/phpunit": "^7.0",
"symfony/browser-kit": "^4.0",
"symfony/css-selector": "^4.0",
"symfony/maker-bundle": "^1.12",
"symfony/phpunit-bridge": "^4.0",
"symfony/profiler-pack": "^1.0",
"symfony/thanks": "^1.0",
"symfony/webpack-encore-pack": "^1.0"
"symfony/web-server-bundle": "~4.2.0"
},
"config": {
"platform": {
"php": "7.2"
},
"preferred-install": {
"*": "dist"
},
@@ -94,7 +102,8 @@
"symfony/polyfill-php56": "*"
},
"suggest": {
"ext-mbstring": "If ext-mbstring is not available you MUST install symfony/polyfill-mbstring"
"ext-mbstring": "If ext-mbstring is not available you MUST install symfony/polyfill-mbstring",
"zendframework/zend-ldap": "For LDAP authentication with Kimai"
},
"scripts": {
"auto-scripts": {
@@ -106,7 +115,21 @@
],
"post-update-cmd": [
"@auto-scripts"
]
],
"kimai:code-check": [
"@kimai:codestyle",
"@kimai:phpstan",
"@kimai:tests"
],
"kimai:tests": "vendor/bin/phpunit tests/",
"kimai:tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
"kimai:tests-integration": "vendor/bin/phpunit --group integration tests/",
"kimai:phpstan": [
"vendor/bin/phpstan analyse src -c phpstan.neon --level=3",
"vendor/bin/phpstan analyse tests -c tests/phpstan.neon --level=4"
],
"kimai:codestyle": "vendor/bin/php-cs-fixer fix --dry-run --verbose --show-progress=none",
"kimai:codestyle-fix": "vendor/bin/php-cs-fixer fix"
},
"conflict": {
"symfony/symfony": "*"
@@ -115,7 +138,7 @@
"symfony": {
"id": "01C3FWRDJJEX9K6Y3A4XDFXPBR",
"allow-contrib": true,
"require": "~4.2.0"
"require": "4.3.*"
}
}
}

3089
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,4 +23,5 @@ return [
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
];

View File

@@ -20,6 +20,8 @@ admin_lte:
theme:
widget:
use_footer: false
type: secondary
solid: false
routes:
adminlte_profile: user_profile

View File

@@ -0,0 +1,3 @@
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'

View File

@@ -1,4 +1,4 @@
# See https://symfony.com/doc/current/email/dev_environment.html
swiftmailer:
# send all emails to a specific address
#delivery_addresses: ['me@example.com']
# delivery_addresses: ['me@example.com']

View File

@@ -12,7 +12,9 @@ doctrine:
default:
url: '%env(resolve:DATABASE_URL)%'
driver: 'pdo_mysql'
server_version: '5.7'
# this setting prevents automatic database detection and finds a lot of false-negatives on doctrine:migrations:diff
# for null columns with MariaDB. Each migration tries to convert EVERY nullable column.
# server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4

View File

@@ -13,5 +13,12 @@ jms_serializer:
App:
namespace_prefix: "App"
path: "%kernel.root_dir%/../config/serializer/App"
warmup:
paths:
included:
- "%kernel.root_dir%/Entity/"
- "%kernel.root_dir%/API/Model/"
- "%kernel.root_dir%/../vendor/friendsofsymfony/user-bundle/Model"
excluded: []
property_naming:
id: 'jms_serializer.identical_property_naming_strategy'

View File

@@ -4,30 +4,40 @@
#
# Be aware that this file is YAML format and the indentation is important.
# Each config level needs to be indented with 4 additional spaces.
#
# ---------------------------------------------------------------------------------------------
kimai:
# --------------------------------------------------------------------------------
# You can disable the user management functions in the authentication screens (default: true).
#user:
# registration: false
# password_reset: false
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# AUTHENTICATION
# You can disable some user management functions in the authentication screens.
# Both settings default to "true", see https://www.kimai.org/documentation/users.html
# --------------------------------------------------------------------------------
# user:
# registration: false
# password_reset: false
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# All configs related to timesheet and record management
# --------------------------------------------------------------------------------
# TIME-TRACKING
# All configs related to time-tracking, timesheets and record management
# --------------------------------------------------------------------------------
timesheet:
# render timesheet descriptions with markdown
# renders timesheet descriptions with markdown
markdown_content: false
# The time-tracking mode that should be used (allowed values: default, duration_only)
# The time-tracking mode that should be used.
# See https://www.kimai.org/documentation/timesheet.html#tracking-modes
# mode: default
# The default time to pre-fill the "create timesheet" form (in some cases).
# This setting is only respected by some timetracking modes and not in all situations.
#
# default: display start and end time columns in timesheet view and form
# duration_only: display start time and duration, https://www.kimai.org/documentation/timesheet.html#duration-only-mode
mode: default
# Accepted formats, see
# - https://www.php.net/manual/en/datetime.formats.php
# - https://www.php.net/manual/en/datetime.formats.time.php
# default_begin: now
# Rounding rules are used to round the begin & end dates and the duration for timesheet records.
# The "default" rule will round "begin" down and "end" up to the full minute, the "duration" will not be rounded.
@@ -40,186 +50,136 @@ kimai:
duration: 0
mode: default
# If you want to apply different hourly rates for specific weekdays, you can uncomment the "rates" configuration.
# The "weekend" rule will add 50% to each timesheet entry that will be recorded on "saturdays" or "sundays".
# See documentation about rate rules: https://www.kimai.org/documentation/timesheet.html#rate-calculation
# rates:
# weekend:
# days: ['saturday','sunday']
# factor: 1.5
# If you want to limit the max. active entries per user, you can do it here.
#
# The soft_limit is used as theme setting and displays a warning color if the user has at least X active recordings
#
# The hard_limit is used to detect how many active records are allowed per user:
# By default a user can only have one active time-record. The first one is stopped when the new one starts.
# When hard_limit is > 1: as soon as the limit is reached and warning is shown and the user has to stop one active entry.
active_entries:
soft_limit: 1
hard_limit: 3
# Rules that define the Timesheet validation and behaviour
rules:
# whether records in the future can be created
allow_future_times: true
# # If you want to apply different hourly rates for specific weekdays, you can uncomment the "rates" configuration.
# # The "weekend" rule will add 50% to each timesheet entry that will be recorded on "saturdays" or "sundays".
# # See documentation at: https://www.kimai.org/documentation/timesheet.html#rate-calculation
# rates:
# weekend:
# days: ['saturday','sunday']
# factor: 1.5
#
# # If you want to limit the max. active entries per user, you can do it here.
# # The soft_limit is used as theme setting and displays a warning color if the user has reached X active recordings
# # The hard_limit is used to detect how many active records are allowed per user:
# # - by default a user can only have one active time-record: it is automatically stopped when a new one is started
# # - when hard_limit is > 1 and the user is trying to start a new entry after reaching the limit, a warning is shown
# # and the user has to stop an active entry first
# active_entries:
# soft_limit: 1
# hard_limit: 3
#
# # Rules that define timesheet validation and behaviour
# rules:
# # whether records in the future can be created
# allow_future_times: true
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# Invoice management
#invoice:
# # all files in these directories will be used as invoice documents (if supported by a renderer)
# documents:
# - 'var/invoices/'
# - 'templates/invoice/renderer/'
# --------------------------------------------------------------------------------
# Default settings used to populate forms
#defaults:
# customer:
# timezone: Europe/Berlin
# country: DE
# currency: EUR
# --------------------------------------------------------------------------------
# Find out more about the configurable permission system at https://www.kimai.org/documentation/permissions.html
# --------------------------------------------------------------------------------
# PERMISSIONS
# Find out more about the configurable permission system at:
# https://www.kimai.org/documentation/permissions.html
# --------------------------------------------------------------------------------
permissions:
# mapping complex rule sets of single permissions to named "sets" ("set name" = [array of "permissions and sets"])
sets:
# mapping complex rulesets of single permissions to named "sets" ("set name" = [array of "permissions"])
ACTIVITIES: [view_activity,create_activity,edit_activity,delete_activity]
PROJECTS: [view_project,create_project,edit_project,delete_project]
CUSTOMERS: [view_customer,create_customer,edit_customer,delete_customer]
INVOICE: [view_invoice,create_invoice]
INVOICE_TEMPLATE: [view_invoice_template,create_invoice_template,edit_invoice_template,delete_invoice_template]
TIMESHEET: [view_own_timesheet,start_own_timesheet,stop_own_timesheet,create_own_timesheet,edit_own_timesheet,export_own_timesheet,delete_own_timesheet]
TIMESHEET_OTHER: [view_other_timesheet,start_other_timesheet,stop_other_timesheet,create_other_timesheet,edit_other_timesheet,export_other_timesheet,delete_other_timesheet]
PROFILE: [view_own_profile,edit_own_profile,password_own_profile,preferences_own_profile,api-token_own_profile]
PROFILE_OTHER: [view_other_profile,edit_other_profile,delete_other_profile,password_other_profile,roles_other_profile,preferences_other_profile,api-token_other_profile]
USER: [view_user,create_user,delete_user]
RATE: [view_rate_own_timesheet,edit_rate_own_timesheet]
RATE_OTHER: [view_rate_other_timesheet,edit_rate_other_timesheet]
EXPORT: [view_export,create_export,edit_export_own_timesheet,edit_export_other_timesheet]
ACTIVITIES: ['view_activity','create_activity','edit_activity','budget_activity','delete_activity']
ACTIVITIES_TEAM: ['view_activity','create_activity','edit_teamlead_activity','budget_teamlead_activity']
PROJECTS: ['view_project','create_project','edit_project','budget_project','delete_project','permissions_project']
PROJECTS_TEAM: ['view_project','edit_teamlead_project','budget_teamlead_project','permissions_teamlead_project']
CUSTOMERS: ['view_customer','create_customer','edit_customer','budget_customer','delete_customer','permissions_customer']
CUSTOMERS_TEAM: ['view_customer','edit_teamlead_customer','budget_teamlead_customer']
INVOICE: ['view_invoice','create_invoice']
INVOICE_TEMPLATE: ['manage_invoice_template']
TIMESHEET: ['view_own_timesheet','start_own_timesheet','stop_own_timesheet','create_own_timesheet','edit_own_timesheet','export_own_timesheet','delete_own_timesheet']
TIMESHEET_OTHER: ['view_other_timesheet','start_other_timesheet','stop_other_timesheet','create_other_timesheet','edit_other_timesheet','export_other_timesheet','delete_other_timesheet']
PROFILE: ['view_own_profile','edit_own_profile','password_own_profile','preferences_own_profile','api-token_own_profile']
PROFILE_OTHER: ['view_other_profile','edit_other_profile','delete_other_profile','password_other_profile','roles_other_profile','preferences_other_profile','api-token_other_profile','teams_other_profile']
TAGS: ['view_tag','delete_tag']
USER: ['view_user','create_user','delete_user','role_permissions']
RATE: ['view_rate_own_timesheet','edit_rate_own_timesheet']
RATE_OTHER: ['view_rate_other_timesheet','edit_rate_other_timesheet']
EXPORT: ['create_export','edit_export_own_timesheet','edit_export_other_timesheet']
TEAMS: ['view_team','create_team','edit_team','delete_team']
# some single default definitions for roles
SINGLE_USER: []
SINGLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile']
SINGLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile']
SINGLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','delete_own_profile','roles_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile']
# link above sets to one set for each user role
ROLE_USER: ['@TIMESHEET','@PROFILE']
ROLE_TEAMLEAD: ['@ACTIVITIES_TEAM','@PROJECTS_TEAM','@CUSTOMERS_TEAM','@TIMESHEET_OTHER','@INVOICE','@TIMESHEET','@PROFILE','@EXPORT','@TAGS','@SINGLE_TEAMLEAD']
ROLE_ADMIN: ['@ACTIVITIES','@PROJECTS','@CUSTOMERS','@INVOICE','@INVOICE_TEMPLATE','@TIMESHEET','@TIMESHEET_OTHER','@PROFILE','@TEAMS','@RATE','@RATE_OTHER','@EXPORT','@TAGS','@SINGLE_ADMIN']
ROLE_SUPER_ADMIN: ['@ACTIVITIES','@PROJECTS','@CUSTOMERS','@INVOICE','@INVOICE_TEMPLATE','@TIMESHEET','@TIMESHEET_OTHER','@PROFILE','@PROFILE_OTHER','@USER','@TEAMS','@RATE','@RATE_OTHER','@EXPORT','@TAGS','@SINGLE_SUPER_ADMIN']
# mapping "sets" or permissions to user roles ("role name" = [array of "set names"])
maps:
# mapping "sets" or permissions to user roles ("role name" = [array of "set names"])
ROLE_USER: [TIMESHEET,PROFILE]
ROLE_TEAMLEAD: [INVOICE,TIMESHEET,TIMESHEET_OTHER,PROFILE,EXPORT]
ROLE_ADMIN: [ACTIVITIES,PROJECTS,CUSTOMERS,INVOICE,INVOICE_TEMPLATE,TIMESHEET,TIMESHEET_OTHER,PROFILE,RATE,RATE_OTHER,EXPORT]
ROLE_SUPER_ADMIN: [ACTIVITIES,PROJECTS,CUSTOMERS,INVOICE,INVOICE_TEMPLATE,TIMESHEET,TIMESHEET_OTHER,PROFILE,PROFILE_OTHER,USER,RATE,RATE_OTHER,EXPORT]
ROLE_USER: ['ROLE_USER']
ROLE_TEAMLEAD: ['ROLE_TEAMLEAD']
ROLE_ADMIN: ['ROLE_ADMIN']
ROLE_SUPER_ADMIN: ['ROLE_SUPER_ADMIN']
# add or remove single permissions
roles:
# adding single permissions to user roles, extending the definition from "sets" ("role name" = [array of "permissions"])
ROLE_USER: []
ROLE_TEAMLEAD: [view_invoice_template,create_invoice_template,edit_invoice_template,view_rate_own_timesheet,view_rate_other_timesheet,hourly-rate_own_profile]
ROLE_ADMIN: [hourly-rate_own_profile]
ROLE_SUPER_ADMIN: [hourly-rate_own_profile,hourly-rate_other_profile,delete_own_profile,roles_own_profile,system_information,system_actions,system_configuration,plugins]
ROLE_TEAMLEAD: []
ROLE_ADMIN: []
ROLE_SUPER_ADMIN: []
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# Language specific settings, like the date formats
languages:
de:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy'
date: 'd.m.Y'
date_time: 'd.m. H:i'
duration: '%%h:%%m h'
en:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd'
date: 'Y-m-d'
date_time: 'm-d H:i'
duration: '%%h:%%m h'
pt_BR:
date_time_type: 'dd-MM-yyyy HH:mm'
date_type: 'dd-MM-yyyy'
date: 'd-m-Y'
date_time: 'd-m H:i'
duration: '%%h:%%m h'
it:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy'
date: 'd.m.Y'
date_time: 'd.m. H:i'
duration: '%%h:%%m h'
fr:
date_time_type: 'dd/MM/yyyy HH:mm'
date_type: 'dd/MM/yyyy'
date: 'd/m/Y'
date_time: 'd/m H:i'
duration: '%%h h %%m'
es:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy'
date: 'd.m.Y'
date_time: 'd.m. H:i'
duration: '%%h:%%m h'
ru:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy'
date: 'd.m.Y'
date_time: 'd.m. H:i'
duration: '%%h:%%m h'
ar:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd'
date: 'Y-m-d'
date_time: 'm-d H:i'
duration: '%%h:%%m h'
hu:
date_time_type: 'yyyy.MM.dd. HH:mm'
date_type: 'yyyy.MM.dd.'
date: 'Y.m.d.'
date_time: 'm.d. H:i'
duration: '%%h:%%m h'
sv:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd'
date: 'Y-m-d'
date_time: 'm-d H:i'
duration: '%%h:%%m tim'
# --------------------------------------------------------------------------------
# timesheet calendar configuration
calendar:
week_numbers: true
day_limit: 4
businessHours:
days: [1, 2, 3, 4, 5]
begin: '08:00'
end: '20:00'
# --------------------------------------------------------------------------------
# CALENDAR Configuration
# See documentation at https://www.kimai.org/documentation/calendar.html
# --------------------------------------------------------------------------------
# calendar:
# week_numbers: true
# weekends: true
# day_limit: 4
# slot_duration: '00:30:00'
# businessHours:
# days: [1, 2, 3, 4, 5]
# begin: '08:00'
# end: '18:00'
# visibleHours:
# begin: '00:00'
# end: '24:00'
#
# # You can configure unlimited google calendars to display events for your company (e.g. holidays)
# google:
# api_key: 'your-restricted-google-api-key'
# sources:
# holidays:
# id: 'de.german#holiday@group.v.calendar.google.com'
# color: '#ccc'
# --------------------------------------------------------------------------------
# You can configure unlimited google calendars to display events for your company (e.g. holidays)
# Find out more about calendar integration at https://www.kimai.org/documentation/calendar.html
# google:
# api_key: 'your-restricted-google-api-key'
# sources:
# holidays:
# id: 'de.german#holiday@group.v.calendar.google.com'
# color: '#ccc'
# --------------------------------------------------------------------------------
# Theme related settings, will be available as twig globals at "kimai_context.*".
# See documentation at https://www.kimai.org/documentation/theme.html
theme:
# BETA test: If you set this to 'selectpicker' the customer/project/activity select boxes will be transformed
# into a searchable and javascript enhanced input type
select_type: ~
# fallback color for all widgets that don't have a dedicated color
# possible options: blue, black, purple, yellow, red, green
box_color: 'green'
# --------------------------------------------------------------------------------
# INVOICES
# --------------------------------------------------------------------------------
# invoice:
# # all files in these directories will be used as invoice documents (if supported by a renderer)
# documents:
# - 'var/invoices/'
# - 'templates/invoice/renderer/'
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# Dashboard widget sections.
# See documentation at https://www.kimai.org/documentation/dashboard.html
# --------------------------------------------------------------------------------
# DASHBOARD
# Using the configurable widget sections.
# See documentation at https://www.kimai.org/documentation/dashboard.html
# --------------------------------------------------------------------------------
dashboard:
user_duration:
title: dashboard.you
order: 10
permission: ROLE_USER
widgets: [userDurationToday, userDurationWeek, userDurationMonth, userDurationYear]
type: 'compoundChart'
widgets: [DailyWorkingTimeChart, userDurationToday, userDurationWeek, userDurationMonth, userDurationYear]
user_rates:
title: ~
order: 20
permission: ROLE_USER
permission: view_rate_own_timesheet
widgets: [userAmountToday, userAmountWeek, userAmountMonth, userAmountYear]
duration:
title: dashboard.all
@@ -234,40 +194,101 @@ kimai:
rates:
title: ~
order: 50
permission: ROLE_ADMIN
permission: view_rate_other_timesheet
widgets: [amountToday, amountWeek, amountMonth, amountYear]
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# Find out more about dashboard widgets at https://www.kimai.org/documentation/dashboard.html
widgets:
userDurationToday: { title: stats.durationToday, query: duration, user: true, begin: '00:00:00', end: '23:59:59', icon: duration, color: green }
userDurationWeek: { title: stats.durationWeek, query: duration, user: true, begin: 'monday this week 00:00:00', end: 'sunday this week 23:59:59', icon: duration, color: blue }
userDurationMonth: { title: stats.durationMonth, query: duration, user: true, begin: 'first day of this month 00:00:00', end: 'last day of this month 23:59:59', icon: duration, color: purple }
userDurationYear: { title: stats.durationYear, query: duration, user: true, begin: '01 january this year 00:00:00', end: '31 december this year 23:59:59', icon: duration, color: yellow }
userDurationTotal: { title: stats.durationTotal, query: duration, user: true, icon: duration, color: red }
userAmountToday: { title: stats.amountToday, query: rate, user: true, begin: '00:00:00', end: '23:59:59', icon: money, color: green }
userAmountWeek: { title: stats.amountWeek, query: rate, user: true, begin: 'monday this week 00:00:00', end: 'sunday this week 23:59:59', icon: money, color: blue }
userAmountMonth: { title: stats.amountMonth, query: rate, user: true, begin: 'first day of this month 00:00:00', end: 'last day of this month 23:59:59', icon: money, color: purple }
userAmountYear: { title: stats.amountYear, query: rate, user: true, begin: '01 january this year 00:00:00', end: '31 december this year 23:59:59', icon: money, color: yellow }
userAmountTotal: { title: stats.amountTotal, query: rate, user: true, icon: money, color: red }
durationToday: { title: stats.durationToday, query: duration, begin: '00:00:00', end: '23:59:59', icon: duration, color: green }
durationWeek: { title: stats.durationWeek, query: duration, begin: 'monday this week 00:00:00', end: 'sunday this week 23:59:59', icon: duration, color: blue }
durationMonth: { title: stats.durationMonth, query: duration, begin: 'first day of this month 00:00:00', end: 'last day of this month 23:59:59', icon: duration, color: purple }
durationYear: { title: stats.durationYear, query: duration, begin: '01 january this year 00:00:00', end: '31 december this year 23:59:59', icon: duration, color: yellow }
durationTotal: { title: stats.durationTotal, query: duration, icon: duration, color: red }
amountToday: { title: stats.amountToday, query: rate, begin: '00:00:00', end: '23:59:59', icon: money, color: green }
amountWeek: { title: stats.amountWeek, query: rate, begin: 'monday this week 00:00:00', end: 'sunday this week 23:59:59', icon: money, color: blue }
amountMonth: { title: stats.amountMonth, query: rate, begin: 'first day of this month 00:00:00', end: 'last day of this month 23:59:59', icon: money, color: purple }
amountYear: { title: stats.amountYear, query: rate, begin: '01 january this year 00:00:00', end: '31 december this year 23:59:59', icon: money, color: yellow }
amountTotal: { title: stats.amountTotal, query: rate, icon: money, color: red }
activeUsersToday: { title: stats.userActiveToday, query: users, begin: '00:00:00', end: '23:59:59', icon: user, color: green }
activeUsersWeek: { title: stats.userActiveWeek, query: users, begin: 'monday this week 00:00:00', end: 'sunday this week 23:59:59', icon: user, color: blue }
activeUsersMonth: { title: stats.userActiveMonth, query: users, begin: 'first day of this month 00:00:00', end: 'last day of this month 23:59:59', icon: user, color: purple }
activeUsersYear: { title: stats.userActiveYear, query: users, begin: '01 january this year 00:00:00', end: '31 december this year 23:59:59', icon: user, color: yellow }
activeUsersTotal: { title: stats.userActiveTotal, query: users, icon: user, color: red }
activeRecordings: { title: stats.activeRecordings, query: active, icon: duration, color: red }
# --------------------------------------------------------------------------------
# Storage directories
# --------------------------------------------------------------------------------
# LANGUAGES
# Locale specific settings, like date formats
# --------------------------------------------------------------------------------
languages:
ar:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd'
date: 'Y-m-d'
date_time: 'm-d H:i'
duration: '%%h:%%m h'
de:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy'
date: 'd.m.Y'
date_time: 'd.m. H:i'
duration: '%%h:%%m h'
en:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd'
date: 'Y-m-d'
date_time: 'm-d H:i'
duration: '%%h:%%m h'
es:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy'
date: 'd.m.Y'
date_time: 'd.m. H:i'
duration: '%%h:%%m h'
fr:
date_time_type: 'dd/MM/yyyy HH:mm'
date_type: 'dd/MM/yyyy'
date: 'd/m/Y'
date_time: 'd/m H:i'
duration: '%%h h %%m'
hu:
date_time_type: 'yyyy.MM.dd. HH:mm'
date_type: 'yyyy.MM.dd.'
date: 'Y.m.d.'
date_time: 'm.d. H:i'
duration: '%%h:%%m h'
it:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy'
date: 'd.m.Y'
date_time: 'd.m. H:i'
duration: '%%h:%%m h'
ja:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd'
date: 'Y-m-d'
date_time: 'm-d H:i'
duration: '%%h:%%m tim'
ko:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd'
date: 'Y-m-d'
date_time: 'm-d H:i'
duration: '%%h:%%m h'
pt_BR:
date_time_type: 'dd-MM-yyyy HH:mm'
date_type: 'dd-MM-yyyy'
date: 'd-m-Y'
date_time: 'd-m H:i'
duration: '%%h:%%m h'
ru:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy'
date: 'd.m.Y'
date_time: 'd.m. H:i'
duration: '%%h:%%m h'
sk:
date_time_type: 'dd. MM. yyyy HH:mm'
date_type: 'dd. MM. yyyy'
date: 'd. m. Y'
date_time: 'd. m. H:i'
duration: '%%h:%%m h'
sv:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd'
date: 'Y-m-d'
date_time: 'm-d H:i'
duration: '%%h:%%m tim'
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# STORAGE
# If you want to use directories outside the Kimai directory, change these
# --------------------------------------------------------------------------------
data_dir: '%kernel.project_dir%/var/data'
plugin_dir: '%kernel.project_dir%/var/plugins'
# --------------------------------------------------------------------------------

View File

@@ -2,48 +2,49 @@ nelmio_api_doc:
models:
use_jms: true
names:
- { alias: CustomerEditForm, type: App\Form\CustomerEditForm, groups: [Default, Entity, Customer] }
- { alias: CustomerEntity, type: App\Entity\Customer, groups: [Default, Entity, Customer] }
- { alias: CustomerCollection, type: App\Entity\Customer, groups: [Default, Collection, Customer] }
- { alias: ProjectEditForm, type: App\Form\ProjectEditForm, groups: [Default, Entity, Project] }
- { alias: ProjectEntity, type: App\Entity\Project, groups: [Default, Entity, Project] }
- { alias: ProjectCollection, type: App\Entity\Project, groups: [Default, Collection, Project] }
- { alias: ActivityEditForm, type: App\Form\ActivityEditForm, groups: [Default, Entity, Activity] }
- { alias: ActivityEntity, type: App\Entity\Activity, groups: [Default, Entity, Activity] }
- { alias: ActivityCollection, type: App\Entity\Activity, groups: [Default, Collection, Activity] }
- { alias: TimesheetEditForm, type: App\Form\TimesheetEditForm, groups: [Default, Entity, Timesheet] }
- { alias: TimesheetEntity, type: App\Entity\Timesheet, groups: [Default, Entity, Timesheet] }
- { alias: TimesheetCollection, type: App\Entity\Timesheet, groups: [Default, Collection, Timesheet] }
- { alias: UserEntity, type: App\Entity\User, groups: [Default, Entity, User] }
- { alias: UserCollection, type: App\Entity\User, groups: [Default, Collection, User] }
- { alias: I18nConfig, type: App\API\Model\I18n, groups: [Default] }
- { alias: CustomerEditForm, type: App\Form\API\CustomerApiEditForm, groups: [Default, Entity, Customer] }
- { alias: CustomerEntity, type: App\Entity\Customer, groups: [Default, Entity, Customer] }
- { alias: CustomerMetaField, type: App\Entity\CustomerMeta, groups: [Default, Customer] }
- { alias: CustomerCollection, type: App\Entity\Customer, groups: [Default, Collection, Customer] }
- { alias: ProjectEditForm, type: App\Form\API\ProjectApiEditForm, groups: [Default, Entity, Project] }
- { alias: ProjectEntity, type: App\Entity\Project, groups: [Default, Entity, Project] }
- { alias: ProjectMetaField, type: App\Entity\ProjectMeta, groups: [Default, Project] }
- { alias: ProjectCollection, type: App\Entity\Project, groups: [Default, Collection, Project] }
- { alias: ActivityEditForm, type: App\Form\API\ActivityApiEditForm, groups: [Default, Entity, Activity] }
- { alias: ActivityEntity, type: App\Entity\Activity, groups: [Default, Entity, Activity] }
- { alias: ActivityMetaField, type: App\Entity\ActivityMeta, groups: [Default, Activity] }
- { alias: ActivityCollection, type: App\Entity\Activity, groups: [Default, Collection, Activity] }
- { alias: TimesheetEditForm, type: App\Form\API\TimesheetApiEditForm, groups: [Default, Entity, Timesheet] }
- { alias: TimesheetEntity, type: App\Entity\Timesheet, groups: [Default, Entity, Timesheet] }
- { alias: TimesheetMeta, type: App\Entity\TimesheetMeta, groups: [Default, Timesheet] }
- { alias: TimesheetCollection, type: App\Entity\Timesheet, groups: [Default, Collection, Timesheet] }
- { alias: TimesheetSubCollection, type: App\Entity\Timesheet, groups: [Default, Subresource, Timesheet] }
- { alias: UserEntity, type: App\Entity\User, groups: [Default, Entity, User] }
- { alias: UserCollection, type: App\Entity\User, groups: [Default, Collection, User] }
- { alias: TeamEntity, type: App\Entity\Team, groups: [Default, Entity, Team] }
- { alias: TeamCollection, type: App\Entity\Team, groups: [Default, Collection, Team] }
- { alias: I18nConfig, type: App\API\Model\I18n, groups: [Default] }
areas:
path_patterns:
- ^/api(?!/doc)
documentation:
# host: '{{hostname}}'
schemes: [http, https]
#host: '{{hostname}}'
host: '%router.request_context.host%'
basePath: '%router.request_context.base_url%'
schemes: ['%router.request_context.scheme%']
info:
title: Kimai 2 - API Docs
description: |
REST API for the Kimai 2 time-tracking software. It is not yet considered stable and BC breaks might happen, even though I try to avoid them as much as possible.
- Collections return less data than explicit entity calls
- DateTime formats are explained in detail at https://www.kimai.org/documentation/rest-api.html
REST API for the Kimai 2 time-tracking software. Read more about its usage in the [API documentation](https://www.kimai.org/documentation/rest-api.html) and then download a [Swagger file](doc.json) for import e.g. in Postman.
Be aware: it is not yet considered stable and BC breaks might happen, but we try to avoid them.
version: 0.3
# parameters:
# hostname:
# name: hostname
# in: url
# description: the hostname where your api runs
# required: true
# type: string
securityDefinitions:
X-AUTH-USER:
apiUser:
type: apiKey
description: 'Value: {Username}'
name: X-AUTH-USER
in: header
X-AUTH-TOKEN:
apiToken:
type: apiKey
description: 'Value: {API Token}'
name: X-AUTH-TOKEN

View File

@@ -10,8 +10,8 @@ monolog:
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
console:
type: console
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]

View File

@@ -0,0 +1,4 @@
webpack_encore:
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# Available in version 1.2
cache: true

View File

@@ -1,8 +1,13 @@
security:
encoders:
App\Entity\User: bcrypt
App\Entity\User: auto
providers:
chain_provider:
chain:
providers: [fos_userbundle]
kimai_ldap:
id: App\Ldap\LdapUserProvider
fos_userbundle:
id: fos_user.user_provider.username_email
@@ -26,8 +31,10 @@ security:
lifetime: 604800
path: /
# activate all configured user provider
provider: chain_provider
form_login:
provider: fos_userbundle
check_path: fos_user_security_check
login_path: fos_user_security_login
csrf_token_generator: security.csrf.token_manager

View File

@@ -4,7 +4,7 @@ security:
encoders:
# to make tests much faster, BCrypt cost is changed to its minimum allowed value (4)
# See https://symfony.com/doc/current/reference/configuration/security.html#using-the-bcrypt-password-encoder
App\Entity\User: { algorithm: bcrypt, cost: 4 }
App\Entity\User: { algorithm: auto }
firewalls:
secured_area:

View File

@@ -0,0 +1,13 @@
webpack_encore:
# The path where Encore is building the assets.
# This should match Encore.setOutputPath() in webpack.config.js.
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%'

View File

@@ -1,11 +1,3 @@
app.test.activity_recent:
path: /{_locale}/activities/recent
controller: App\Controller\NavbarController::recentActivitiesAction
requirements:
_locale: '%app_locales%'
defaults:
_locale: '%locale%'
app.test.sidebar_settings:
path: /{_locale}/sidebar/settings
controller: App\Controller\SidebarController::settingsAction

View File

@@ -1,6 +1,6 @@
App\Entity\Activity:
exclusion_policy: All
custom_accessor_order: [id, name, comment, visible, project, fixedRate, hourlyRate]
custom_accessor_order: [id, name, comment, visible, project, fixedRate, hourlyRate, color, budget, timeBudget, metaFields]
properties:
id:
include: true
@@ -14,19 +14,39 @@ App\Entity\Activity:
visible:
include: true
groups: [Default]
budget:
include: true
groups: [Entity]
timeBudget:
include: true
groups: [Entity]
fixedRate:
include: true
groups: [Default]
groups: [Activity]
hourlyRate:
include: true
groups: [Default]
groups: [Activity]
project:
include: false
exclude: true
groups: [Default]
color:
include: true
metaFields:
exclude: true
virtual_properties:
parentTitle:
serialized_name: parentTitle
include: true
exp: "object.getProject() === null ? null : object.getProject().getName()"
groups: [Default]
getProject:
serialized_name: project
exp: "object.getProject() === null ? null : object.getProject().getId()"
type: integer
groups: [Default]
getMetaFields:
serialized_name: metaFields
exp: "object.getVisibleMetaFields()"
type: array<App\Entity\ActivityMeta>
groups: [Default]

View File

@@ -0,0 +1,15 @@
App\Entity\ActivityMeta:
exclusion_policy: All
custom_accessor_order: [name, value]
properties:
name:
include: false
value:
include: false
virtual_properties:
getName:
serialized_name: name
exp: "object.isVisible() ? object.getName() : null"
getValue:
serialized_name: value
exp: "object.isVisible() ? object.getValue() : null"

View File

@@ -1,13 +1,11 @@
App\Entity\Customer:
exclusion_policy: All
custom_accessor_order: [id, name, number, comment, visible, company, contact, address, country, currency, phone, fax, mobile, email, homepage, timezone, fixedRate, hourlyRate]
custom_accessor_order: [id, name, number, comment, visible, company, contact, address, country, currency, phone, fax, mobile, email, homepage, timezone, fixedRate, hourlyRate, color, budget, timeBudget, metaFields]
properties:
id:
include: true
groups: [Default]
name:
include: true
groups: [Default]
number:
include: true
groups: [Entity]
@@ -16,7 +14,12 @@ App\Entity\Customer:
groups: [Entity]
visible:
include: true
groups: [Default]
budget:
include: true
groups: [Entity]
timeBudget:
include: true
groups: [Entity]
company:
include: true
groups: [Entity]
@@ -52,7 +55,17 @@ App\Entity\Customer:
groups: [Entity]
fixedRate:
include: true
groups: [Default]
groups: [Customer]
hourlyRate:
include: true
groups: [Customer]
color:
include: true
metaFields:
exclude: true
virtual_properties:
getMetaFields:
serialized_name: metaFields
exp: "object.getVisibleMetaFields()"
type: array<App\Entity\CustomerMeta>
groups: [Default]

View File

@@ -0,0 +1,15 @@
App\Entity\CustomerMeta:
exclusion_policy: All
custom_accessor_order: [name, value]
properties:
name:
include: false
value:
include: false
virtual_properties:
getName:
serialized_name: name
exp: "object.isVisible() ? object.getName() : null"
getValue:
serialized_name: value
exp: "object.isVisible() ? object.getValue() : null"

View File

@@ -1,38 +1,50 @@
App\Entity\Project:
exclusion_policy: All
custom_accessor_order: [id, name, comment, visible, budget, orderNumber, customer, fixedRate, hourlyRate]
custom_accessor_order: [id, name, comment, visible, orderNumber, customer, fixedRate, hourlyRate, color, budget, timeBudget, metaFields]
properties:
id:
include: true
groups: [Default]
name:
include: true
groups: [Default]
comment:
include: true
groups: [Entity]
visible:
include: true
groups: [Default]
budget:
include: true
groups: [Entity]
timeBudget:
include: true
groups: [Entity]
orderNumber:
include: true
groups: [Entity]
fixedRate:
include: true
groups: [Default]
groups: [Project]
hourlyRate:
include: true
groups: [Default]
groups: [Project]
customer:
include: false
groups: [Subresource]
color:
include: true
metaFields:
exclude: true
groups: [Default]
virtual_properties:
parentTitle:
serialized_name: parentTitle
include: true
exp: "object.getCustomer() === null ? null : object.getCustomer().getName()"
groups: [Default]
getCustomer:
serialized_name: customer
exp: "object.getCustomer() === null ? null : object.getCustomer().getId()"
type: integer
groups: [Entity, Collection]
getMetaFields:
serialized_name: metaFields
exp: "object.getVisibleMetaFields()"
type: array<App\Entity\ProjectMeta>
groups: [Default]

View File

@@ -0,0 +1,15 @@
App\Entity\ProjectMeta:
exclusion_policy: All
custom_accessor_order: [name, value]
properties:
name:
include: false
value:
include: false
virtual_properties:
getName:
serialized_name: name
exp: "object.isVisible() ? object.getName() : null"
getValue:
serialized_name: value
exp: "object.isVisible() ? object.getValue() : null"

View File

@@ -0,0 +1,14 @@
App\Entity\Team:
exclusion_policy: All
custom_accessor_order: [id, name, users]
properties:
id:
include: true
name:
include: true
users:
exclude: true
customers:
exclude: true
projects:
exclude: true

View File

@@ -1,23 +1,19 @@
App\Entity\Timesheet:
exclusion_policy: All
custom_accessor_order: [id, begin, end, duration, rate, activity, project, user, description, fixedRate, hourlyRate, exported]
custom_accessor_order: [id, begin, end, duration, rate, activity, project, user, description, fixedRate, hourlyRate, tags, exported, metaFields]
properties:
id:
include: true
groups: [Default]
begin:
exclude: true
end:
exclude: true
duration:
include: true
groups: [Default]
description:
include: true
groups: [Default]
rate:
include: true
groups: [Default]
fixedRate:
include: true
groups: [Entity]
@@ -28,34 +24,42 @@ App\Entity\Timesheet:
include: true
groups: [Entity]
activity:
exclude: true
groups: [Subresource]
project:
exclude: true
groups: [Subresource]
user:
exclude: true
metaFields:
exclude: true
virtual_properties:
getBegin:
serialized_name: begin
exp: "object.getBegin() === null ? null : object.getBegin()"
type: DateTime
groups: [Default]
getEnd:
serialized_name: end
exp: "object.getEnd() === null ? null : object.getEnd()"
type: DateTime
groups: [Default]
getActivity:
serialized_name: activity
exp: "object.getActivity() === null ? null : object.getActivity().getId()"
type: integer
groups: [Default]
groups: [Entity, Collection]
getProject:
serialized_name: project
exp: "object.getProject() === null ? null : object.getProject().getId()"
type: integer
groups: [Default]
groups: [Entity, Collection]
getUser:
serialized_name: user
exp: "object.getUser().getId()"
type: integer
getTags:
serialized_name: tags
exp: "object.getTagsAsArray()"
type: array<string>
getMetaFields:
serialized_name: metaFields
exp: "object.getVisibleMetaFields()"
type: array<App\Entity\TimesheetMeta>
groups: [Default]

View File

@@ -0,0 +1,15 @@
App\Entity\TimesheetMeta:
exclusion_policy: All
custom_accessor_order: [name, value]
properties:
name:
include: false
value:
include: false
virtual_properties:
getName:
serialized_name: name
exp: "object.isVisible() ? object.getName() : null"
getValue:
serialized_name: value
exp: "object.isVisible() ? object.getValue() : null"

View File

@@ -2,7 +2,7 @@
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: en
app_locales: en|de|it|fr|es|ru|ar|hu|pt_BR|sv
app_locales: ar|de|en|es|fr|hu|it|ja|ko|pt_BR|ru|sk|sv
services:
# default configuration for services in *this* file
@@ -13,7 +13,7 @@ services:
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
bind:
$projectDirectory: "%kernel.project_dir%"
$projectDirectory: '%kernel.project_dir%'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
@@ -34,32 +34,39 @@ services:
App\EventSubscriber\RedirectToLocaleSubscriber:
arguments: ['@router', '%app_locales%', '%locale%']
App\Calendar\Service:
arguments:
$config: "%kimai.calendar%"
App\Repository\WidgetRepository:
arguments:
$widgets: "%kimai.widgets%"
$widgets: '%kimai.widgets%'
App\Controller\DashboardController:
arguments:
$dashboard: "%kimai.dashboard%"
$dashboard: '%kimai.dashboard%'
App\Configuration\LanguageFormattings:
arguments: ["%kimai.languages%"]
arguments: ['%kimai.languages%']
App\Configuration\TimesheetConfiguration:
arguments:
$settings: "%kimai.timesheet%"
$settings: '%kimai.timesheet%'
App\Configuration\CalendarConfiguration:
arguments:
$settings: '%kimai.calendar%'
App\Configuration\FormConfiguration:
arguments:
$settings: "%kimai.defaults%"
$settings: '%kimai.defaults%'
App\Configuration\SystemConfiguration:
arguments:
$settings: "%kimai.config%"
$settings: '%kimai.config%'
App\Configuration\ThemeConfiguration:
arguments:
$settings: '%kimai.theme%'
App\Configuration\LdapConfiguration:
arguments: ['%kimai.ldap%']
App\Utils\MPdfConverter:
arguments: ['%kernel.cache_dir%']
@@ -70,6 +77,10 @@ services:
App\Export\ServiceExport:
arguments: [!tagged export.renderer]
App\Widget\WidgetService:
arguments:
$renderer: !tagged widget.renderer
# ================================================================================
# DATABASE
# ================================================================================
@@ -91,24 +102,8 @@ services:
# FORMS
# ================================================================================
App\Form\Type\UserRoleType:
arguments: ["%security.role_hierarchy.roles%"]
App\Form\Type\LanguageType:
arguments: ["%app_locales%"]
App\Form\Extension\DocumentationLinkExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }
App\Form\Extension\EnhancedChoiceTypeExtension:
arguments: ["%kimai.theme.select_type%"]
tags:
- { name: form.type_extension, extended_type: Symfony\Bridge\Doctrine\Form\Type\EntityType }
App\Form\Extension\SelectWithApiDataExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Bridge\Doctrine\Form\Type\EntityType }
arguments: ['%app_locales%']
# ================================================================================
# THEME
@@ -122,17 +117,29 @@ services:
# ================================================================================
App\Timesheet\Calculator\DurationCalculator:
arguments: ["%kimai.timesheet.rounding%"]
arguments: ['%kimai.timesheet.rounding%']
App\Timesheet\Calculator\RateCalculator:
arguments: ["%kimai.timesheet.rates%"]
arguments: ['%kimai.timesheet.rates%']
# ================================================================================
# SECURITY & VOTER
# ================================================================================
App\Security\RoleService:
arguments: ['%security.role_hierarchy.roles%']
App\Security\RolePermissionManager:
arguments: ["%kimai.permissions%"]
arguments:
$permissions: '%kimai.permissions%'
# ================================================================================
# LDAP
# ================================================================================
kimai_ldap.security.authentication.provider:
class: App\Ldap\LdapAuthenticationProvider
arguments: ['@App\Security\UserChecker', '', '', '', '@App\Configuration\LdapConfiguration', '%security.authentication.hide_user_not_found%']
# ================================================================================
# REPOSITORIES
@@ -148,6 +155,11 @@ services:
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\User']
App\Repository\TeamRepository:
class: Doctrine\ORM\EntityRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\Team']
App\Repository\ActivityRepository:
class: Doctrine\ORM\EntityRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
@@ -158,6 +170,11 @@ services:
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\Project']
App\Repository\TagRepository:
class: Doctrine\ORM\EntityRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\Tag']
App\Repository\CustomerRepository:
class: Doctrine\ORM\EntityRepository
factory: ['@doctrine.orm.entity_manager', getRepository]

View File

@@ -4,6 +4,11 @@ services:
test.PasswordEncoder: "@security.encoder_factory"
# added, so we are able to overwrite the service for the CalendarControllerTest
App\Configuration\CalendarConfiguration:
arguments: ['@App\Repository\ConfigurationRepository', "%kimai.calendar%"]
public: true
App\Configuration\SystemConfiguration:
arguments: ['@App\Repository\ConfigurationRepository', "%kimai.config%"]

View File

@@ -9,5 +9,5 @@ App\Entity\User:
- NotBlank: ~
- Email: ~
plainPassword:
- NotBlank: { groups: [registration, passwordUpdate] }
- Length: { min: 5, max: 60, groups: [registration, passwordUpdate] }
- NotBlank: { groups: [Registration, PasswordUpdate] }
- Length: { min: 8, max: 60, groups: [Registration, PasswordUpdate] }

View File

@@ -1,4 +1,18 @@
{
"homepage": "https://github.com/kevinpapst/kimai2",
"license": "MIT",
"maintainers": [
{
"name": "Kevin Papst",
"email": "kpapst@gmx.net",
"url": "https://www.kevinpapst.de"
}
],
"name": "kimai2",
"repository": {
"type": "git",
"url": "git+https://github.com/kevinpapst/kimai2.git"
},
"scripts": {
"dev-server": "./node_modules/.bin/encore dev-server",
"dev": "./node_modules/.bin/encore dev",
@@ -7,12 +21,13 @@
"prod": "node_modules/.bin/encore production"
},
"devDependencies": {
"@fortawesome/fontawesome-free": "~5.1.1",
"@symfony/webpack-encore": "^0.20.1",
"@fortawesome/fontawesome-free": "~5.8.2",
"@symfony/webpack-encore": "^0.27.0",
"admin-lte": "~2.4.8",
"bootstrap-sass": "^3.3.7",
"bootstrap-select": "^1.13.1",
"chart.js": "~2.7.2",
"chart.js": "~2.8.0",
"core-js": "^3.1.3",
"daterangepicker": "^3.0.3",
"fullcalendar": "^3.9.0",
"icheck": "^1.0.2",
@@ -20,8 +35,10 @@
"jquery-slimscroll": "^1.3.8",
"jquery-ui": "^1.12.1",
"js-cookie": "^2.2.0",
"moment": "^2.24.0",
"node-sass": "^4.9.0",
"sass-loader": "^7.0.2",
"sweetalert2": "^8.11.7",
"webpack-notifier": "^1.5.1"
}
}

23
phpstan.neon Normal file
View File

@@ -0,0 +1,23 @@
includes:
- vendor/phpstan/phpstan-symfony/extension.neon
- vendor/phpstan/phpstan-doctrine/extension.neon
parameters:
tmpDir: %rootDir%/../../../var/cache/phpstan
autoload_directories:
- %rootDir%/../../../src/Migrations
# symfony:
# container_xml_path: '%rootDir%/../../../var/cache/dev/srcApp_KernelDevDebugContainer.xml'
ignoreErrors:
- '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface::scalarNode\(\).#'
- '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children\(\).#'
- '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::useAttributeAsKey\(\).#'
- '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::addDefaultsIfNotSet\(\).#'
- '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::requiresAtLeastOneElement\(\).#'
- '#Access to an undefined property Faker\\Generator::\$stateAbbr.#'
- '#Access to an undefined property Faker\\Generator::\$catchPhrase.#'
- '#Access to an undefined property Faker\\Generator::\$bs.#'
- '#Method Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface::dispatch\(\) invoked with 2 parameters, 1 required.#'
excludes_analyse:
- %rootDir%/../../../src/Command/KimaiImporterCommand.php
- %rootDir%/../../../src/Ldap/LdapDriver.php

View File

@@ -19,7 +19,7 @@
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
<!-- ###+ doctrine/doctrine-bundle ### -->
<env name="DATABASE_URL" value="sqlite:///var/data/kimai_test.sqlite" force="true"/>
<env name="DATABASE_URL" value="sqlite:///%kernel.project_dir%/var/data/kimai_test.sqlite" force="true"/>
<!-- ###- doctrine/doctrine-bundle ### -->
<!-- ###+ symfony/swiftmailer-bundle ### -->

View File

@@ -5,6 +5,15 @@
# to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
DirectoryIndex index.php
# Do NOT use Kimai with http, this is a huge security risk!
# Activate these lines if you configured SSL and certificates:
#<IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{SERVER_PORT} 80
# RewriteRule ^(.*)$ https://kimai.example.com/$1 [R,L]
#</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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