Compare commits

...

88 Commits

Author SHA1 Message Date
Kevin Papst
233c0e792e merge main 2024-09-22 20:26:03 +02:00
Kevin Papst
896e8b42c1 merge main 2024-09-22 20:11:37 +02:00
Kevin Papst
bf288bf115 Merge branch 'refs/heads/main' into api-pagination
# Conflicts:
#	src/API/BaseApiController.php
2024-09-22 17:34:24 +02:00
Kevin Papst
13649e146c Improve pagination support in API (#5073) 2024-09-22 17:26:02 +02:00
Kevin Papst
0f9b964b11 Merge remote-tracking branch 'refs/remotes/origin/api-enhancements' into api-pagination
# Conflicts:
#	src/API/ActivityController.php
#	src/API/CustomerController.php
#	src/API/ProjectController.php
#	src/API/TimesheetController.php
#	src/API/UserController.php
#	src/Repository/ProjectRepository.php
2024-09-22 16:34:24 +02:00
Kevin Papst
4076e1c3d3 Support for changeable work contract types (#5069) 2024-09-22 16:18:21 +02:00
Kevin Papst
8de54e1fa7 Added API endpoints to fetch invoices (#5070)
* added serializer attributes for API usage
* new setters to fill invoice data from fixture
* re-usable fixture helper methods
* added API endpoints to fetch invoices
* adjust tests
2024-09-22 16:17:45 +02:00
Weblate (bot)
1965c35b43 Translated using Weblate (#5045)
Co-authored-by: Doctorredits_here <alkaf.alkaf2018@tutamail.com>
Co-authored-by: Hien <hienly@yandex.com>
Co-authored-by: Jose Delvani <jsdelvani@users.noreply.hosted.weblate.org>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: albanobattistella <albano_battistella@hotmail.com>
Co-authored-by: enly sure <enlysure@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2024-09-22 15:38:51 +02:00
Kevin Papst
3fd770047e allow Authorization header in CORS request 2024-09-20 23:26:29 +02:00
Kevin Papst
036b297a76 fix hydration issues in favorites (#5068) 2024-09-20 22:43:16 +02:00
Kevin Papst
537c120ad9 Release 2.22.0 (#5043) 2024-09-20 14:30:11 +02:00
Weblate (bot)
3e0dadc0c8 Translated using Weblate (#5037)
Co-authored-by: Jose Delvani <jsdelvani@users.noreply.hosted.weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
2024-09-01 14:58:47 +02:00
Kevin Papst
004ac5a6b9 re-phrase 2024-09-01 14:57:07 +02:00
Kevin Papst
3204dcb03e bump PHPSpreadsheet major version (#5040) 2024-08-31 23:42:41 +02:00
Kevin Papst
b1903ba183 Release 2.21.0 (#5014) 2024-08-28 17:16:37 +02:00
Weblate (bot)
75fa7a20b8 Translated using Weblate (#5012)
Co-authored-by: BeckeBauer <berko@gmx.de>
Co-authored-by: Biscuittttt <biscuitwithtea.tall310@passinbox.com>
Co-authored-by: Héctor Borrás Aleixandre <hborrasaleixandre@gmail.com>
Co-authored-by: John Titor <utkin2007@gmail.com>
Co-authored-by: Léane GRASSER <leane.grasser@proton.me>
Co-authored-by: Prefill add-on <noreply-addon-prefill@weblate.org>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Uporabnik Alen <obutimacek556@gmail.com>
Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Co-authored-by: tygyh <jonis9898@hotmail.com>
2024-08-28 17:15:57 +02:00
Kevin Papst
a19426147a added event to send email to user (#5034) 2024-08-28 14:04:36 +02:00
Kevin Papst
9d933f62c0 refactored repositories and DB queries (#5026)
* removed unused teams from export order
* added new paginator for query instead of querybuilder
* added field hydrate enums
* hide PARTIAL deprecation
* never log deprecations in production
* replaced InvoiceLoader with native Doctrine feature
* prevent excessive permission queries
* support loading customers of team
* improved findByIds
* internalized API
* fix null string deprecations
2024-08-27 10:11:19 +02:00
Kevin Papst
9e3d243b4b use enabled_locales logic to handle locales (#5017)
* allow to skip locales (here: catalan)
* use enabled_locales and replace app_locales with kimai_locales
* added test to call all reports once for super_admin
2024-08-11 17:43:20 +02:00
Kevin Papst
62047c7c01 link security guidelines on the website 2024-08-05 18:44:38 +02:00
Kevin Papst
0bcd45babe clarify test exclusions 2024-08-05 13:23:53 +02:00
Kevin Papst
2a72815481 remove invalid label 2024-08-04 19:38:47 +02:00
Kevin Papst
ff6a3e8262 Release 2.20.0 (#4987) 2024-08-04 18:22:27 +02:00
Kevin Papst
0947b88d36 added OCI docker labels (#4996) 2024-08-04 16:43:46 +02:00
Kevin Papst
5de5b4fef5 BC: Cleanup Docker tags (#4995) 2024-08-04 16:37:55 +02:00
robjuz
cde856a9c1 BC: Docker support for encoded characters in DATABASE_URL (#5000) 2024-08-04 16:02:08 +02:00
Kevin Papst
6f8877a740 new export of project overview list (#5009) 2024-08-03 00:43:05 +02:00
Kevin Papst
e7d71bff64 remove comments from XLIFF files 2024-08-02 22:43:36 +02:00
Weblate (bot)
cca0657291 Update translation files (#5010) 2024-08-02 21:49:13 +02:00
Weblate (bot)
a4904d3335 Update translation files (#5007)
Co-authored-by: Héctor Borrás <hborras@cyberclick.net>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Naoko Takano <naokomc@gmail.com>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: stuntworks <stuntworks@saber.games>
2024-08-02 21:43:29 +02:00
Kevin Papst
b9c1036b21 Revert Docker credentials urldecode (#4998) 2024-07-28 18:04:18 +02:00
Weblate (bot)
818c080d28 Translated using Weblate (#4990)
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: Wouter Meeusen <wouter.meeusen@outlook.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2024-07-27 17:37:10 +02:00
Weblate (bot)
a5a6ae065b Translated using Weblate (#4984)
Co-authored-by: Adrien N <adriennathaniel1999@gmail.com>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
2024-07-23 10:03:56 +02:00
Kevin Papst
8b06ca9d10 unify CSRF token usage in comment sections (#4986) 2024-07-23 08:33:02 +02:00
Kevin Papst
7e1025d61d fix financial year issue, fix test, improve DateTimeFactory (#4985) 2024-07-23 07:20:11 +02:00
Kevin Papst
8788311faf Release 2.19 (#4922) 2024-07-22 17:51:03 +02:00
Weblate (bot)
ce22520d7f Translated using Weblate (#4946)
Co-authored-by: Ahmad Dakhlallah <ahmad@linuxarabia.co>
Co-authored-by: Bent Haar <BentHaar@users.noreply.hosted.weblate.org>
Co-authored-by: Carlos Carreras <mytriponlinux@gmail.com>
Co-authored-by: David Bauer <dbs23.dbs23@gmail.com>
Co-authored-by: Jose Delvani <delvani.eletricista@gmail.com>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: Mads R. Andersen <madsdk_789@hotmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Mikkel Ricky <rimi@aarhus.dk>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Quiwy <github@quiwy.ninja>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: geekom13 <geekom13@proton.me>
Co-authored-by: lilosti <lilosti@aarhus.dk>
Co-authored-by: ssantos <ssantos@web.de>
2024-07-20 17:36:02 +02:00
robjuz
f5d76cfd9a fix database URLs with special characters for docker (#4976) 2024-07-20 17:20:23 +02:00
Chinmay Purav
27f7f14539 fixed case of AS keyword in Dockerfile (#4943) 2024-06-29 21:45:14 +02:00
Kevin Papst
f7c32e633a bump docker action versions to fix GH deprecations 2024-06-17 11:05:49 +02:00
Kevin Papst
6d9055826f bump docker action versions to fix GH deprecations 2024-06-17 10:07:27 +02:00
Toby Batch
4627a9047e feat: stop multiarch images being lost on re tagging docker images (#4856) 2024-06-17 10:03:11 +02:00
Kevin Papst
987b46bf8f Release 2.18 (#4878) 2024-06-16 13:15:49 +02:00
Weblate (bot)
8792a1df09 Translated using Weblate (#4876)
Co-authored-by: Chan Young Park <designbycy@gmail.com>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Oh Wow! <kasra.hashemi1999@gmail.com>
Co-authored-by: René Bischoff <rene.bischoff@gmail.com>
Co-authored-by: Wei-Cheng Yeh (IID) <iid@ccns.ncku.edu.tw>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: wkwkjddndjdusiq <wkwkjddndjdusiq@onionmail.com>
2024-06-16 13:02:27 +02:00
Kevin Papst
a8678892d6 yarn upgrade 2024-06-15 11:40:35 +02:00
Kevin Papst
72350b76c0 improve equality detection of same user 2024-06-15 11:40:28 +02:00
Kevin Papst
8578bbe033 improve project end handling 2024-06-13 22:31:33 +02:00
Kevin Papst
93ca983e0b invoice: do not use activity name as fallback for description (#4884)
* added replacement field description_safe
2024-06-03 19:01:40 +02:00
Kevin Papst
e29ef25581 link domains for donations 2024-05-22 14:19:11 +02:00
Kevin Papst
a617eba535 link about pages for donations 2024-05-22 14:15:42 +02:00
Kevin Papst
0c445d1bc4 Release 2.17 (#4836)
see https://github.com/kimai/kimai/pull/4836
2024-05-19 17:42:03 +02:00
Weblate (bot)
5f7114e008 Translated using Weblate (#4862)
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: Wei-Cheng Yeh (IID) <iid@ccns.ncku.edu.tw>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
2024-05-19 17:03:48 +02:00
Habibhaidari1
e2c511cf46 Update RateCalculatorInterface (#4851) 2024-05-17 22:55:08 +02:00
Weblate (bot)
e042ef6bfa Translated using Weblate (#4850)
Co-authored-by: Barna László <barna.laszlo@lscomputer.hu>
Co-authored-by: Wei-Cheng Yeh (IID) <iid@ccns.ncku.edu.tw>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
2024-05-12 12:41:06 +02:00
Daniel Langemann
c059e51b3c Fix typo in markdown link (#4845) 2024-05-07 09:30:33 +02:00
Kevin Papst
661bb16342 Fix hidden API token (#4828) 2024-05-03 11:38:33 +02:00
Weblate (bot)
94d6b43dcc Cleanup translation files (#4827) 2024-05-03 11:06:35 +02:00
Kevin Papst
99c296a751 Release 2.16 (#4780) 2024-05-01 14:24:24 +02:00
Weblate (bot)
8f8d228fb3 Translated using Weblate (#4826)
Co-authored-by: Bogumił Kraszewski <gogulcio@gmail.com>
Co-authored-by: Colgrave <hanqixu.blogs@simplelogin.co>
Co-authored-by: Freek <info@f3k.tech>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Nicoara Alex <alex.nicoara@yahoo.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Wei-Cheng Yeh (IID) <iid@ccns.ncku.edu.tw>
Co-authored-by: qeepoo <pfrade1996@protonmail.com>
2024-05-01 13:43:31 +02:00
Weblate (bot)
1214e508fe Translated using Weblate (#4814)
Co-authored-by: Bogumił Kraszewski <gogulcio@gmail.com>
Co-authored-by: Jose Delvani <delvani.eletricista@gmail.com>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: streaming s <fsrmllll1111@gmail.com>
2024-04-24 06:36:55 +02:00
Andreas Brain
7d394fbe56 added ldap-libs to for TLS certificate verification (#4802) 2024-04-23 10:33:51 +02:00
Weblate (bot)
fd4dfc6be4 Translated using Weblate (#4812)
Co-authored-by: Andre Costa <andrecaeu@gmail.com>
Co-authored-by: Balanda Nazarii <balaraz@tuta.io>
Co-authored-by: Joaquim Homrighausen <joho@boojam.se>
Co-authored-by: Jose Delvani <delvani.eletricista@gmail.com>
Co-authored-by: Julia <juliadeo@proton.me>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nicoara Alex <alex.nicoara@yahoo.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Petar Babić <petar@jesteibice.com>
Co-authored-by: SGpro <mark520fay35@gmail.com>
Co-authored-by: Tito Tapiola <ikirouta013@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: حاج حسین نجفی <hajhsynnjfy@gmail.com>
2024-04-22 18:44:21 +02:00
Kevin Papst
9dc9c71a46 added "api_access" permission for limiting API access (#4779) 2024-04-13 17:27:51 +02:00
Kevin Papst
345bb6601e fix test 2024-04-13 17:00:13 +02:00
Kevin Papst
5c8c21d609 fix menu name 2024-04-13 16:52:44 +02:00
Kevin Papst
7219b3f421 Release 2.15 (#4749) 2024-04-12 19:03:09 +02:00
Weblate (bot)
b76a5d5c35 Translations update from Hosted Weblate (#4765)
Co-authored-by: Andre Costa <andrecaeu@gmail.com>
Co-authored-by: Balanda Nazarii <balaraz@tuta.io>
Co-authored-by: Joaquim Homrighausen <joho@boojam.se>
Co-authored-by: Jose Delvani <delvani.eletricista@gmail.com>
Co-authored-by: Julia <juliadeo@proton.me>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Petar Babić <petar@jesteibice.com>
Co-authored-by: Tito Tapiola <ikirouta013@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Kevin Papst <kevinpapst@users.noreply.github.com>
2024-04-12 16:22:17 +02:00
Kevin Papst
40a9027d34 try v5 without discussion 2024-04-06 11:09:02 +02:00
Kevin Papst
553bd20e06 internal: bump versions due to node16 deprecation 2024-04-06 10:33:53 +02:00
Kevin Papst
3ea5803e08 revert to @v4 2024-04-06 10:29:15 +02:00
Kevin Papst
4949afb098 try different token 2024-04-06 10:28:27 +02:00
Kevin Papst
d328badca1 enable debug 2024-04-06 10:26:47 +02:00
Kevin Papst
175d371ef9 change name 2024-04-06 10:17:12 +02:00
Kevin Papst
6bb7b38252 bump version 2024-04-06 10:15:56 +02:00
Kevin Papst
afe0656502 added API tokens, deprecate API passwords (#4637) 2024-04-05 23:51:16 +02:00
Kevin Papst
dd51c8dfba utilize UserService for SAML (#4748) 2024-04-05 19:22:13 +02:00
Kevin Papst
b6c98f871d Release 2.14 (#4710)
- show "link has expired message" in password reset screen
- added date objects as hydrator variables - for custom date formats in invoice templates
- show meta-fields with null values (e.g. booleans with `false` where hidden)
- fix permission check: allow to remove `view_own_timesheet` but still record times
- prevent error 500 if customer country is empty
- fix API 500 error if project does not exist when creating new timesheet
- fix tags are not created in remote-search mode
- do not check "export items" by default
- fix daterange query, if user an request locale are different
- added logging for invalid SAML responses (see various discussions)
2024-04-05 12:38:21 +02:00
Weblate (bot)
19b2d47591 Translated using Weblate (#4746)
Co-authored-by: Joaquim Homrighausen <joho@boojam.se>
Co-authored-by: Jose Delvani <delvani.eletricista@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
2024-04-04 17:50:23 +02:00
Kevin Papst
a636683dee Configurable activity and project number (#4729)
* added configurable activity number
* added configurable project number
* fix deprecations
* added some tests for entity exporter
* better configuration of dropdown pattern for customer, project and activity
2024-04-04 17:43:22 +02:00
Kevin Papst
c1f5d3def4 do not export items by default 2024-04-04 17:35:43 +02:00
Kevin Papst
3e88a007d0 allow to change locale in DateRangeType 2024-04-04 17:35:16 +02:00
Kevin Papst
1fdd11e94c link to wall of love 2024-04-04 00:20:31 +02:00
Toby Batch
2ac783a300 split docker to use base image for faster builds (#4586) 2024-04-02 17:50:24 +02:00
Weblate (bot)
3a358b1434 Translated using Weblate (#4722)
Co-authored-by: Jean-Michel <arsene_lupin_57@hotmail.fr>
Co-authored-by: Joaquim Homrighausen <joho@boojam.se>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Thanh <pancakes21f@gmail.com>
2024-04-02 01:06:57 +02:00
Yannik Beaulieu
216da19cc9 Add support for negative durations in All Users reports (#4717) 2024-03-27 23:57:52 +01:00
Kevin Papst
dee90bb15e Release 2.13 (#4659) 2024-03-10 15:35:59 +01:00
Weblate (bot)
a78e8ed8c4 Translated using Weblate (#4693)
Co-authored-by: Andre Costa <andrecaeu@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: yangyangdaji <1504305527@qq.com>
2024-03-10 15:29:27 +01:00
Kevin Papst
3e3cdc5e69 global pagination support for API collection calls 2024-03-10 13:27:04 +01:00
829 changed files with 21281 additions and 12107 deletions

View File

@@ -2,7 +2,6 @@
ServerAdmin webmaster@localhost
DocumentRoot /opt/kimai/public
PassEnv DATABASE_PREFIX
PassEnv MAILER_FROM
PassEnv APP_ENV
PassEnv APP_SECRET

View File

@@ -0,0 +1,208 @@
# Source base [fpm/apache]
ARG BASE="fpm"
ARG PHP_VER="8.2"
ARG COMPOSER_VER="latest"
ARG TIMEZONE="Europe/London"
###########################
# Shared tools
###########################
# composer base image
FROM composer:${COMPOSER_VER} AS composer
###########################
# PHP extensions
###########################
#fpm alpine php extension base
FROM php:${PHP_VER}-fpm-alpine AS fpm-php-ext-base
RUN apk add --no-cache \
# build-tools
autoconf \
dpkg \
dpkg-dev \
file \
g++ \
gcc \
icu-dev \
libatomic \
libc-dev \
libgomp \
libmagic \
m4 \
make \
mpc1 \
mpfr4 \
musl-dev \
perl \
re2c \
# gd
freetype-dev \
libpng-dev \
# icu
icu-dev \
icu-data-full \
# ldap
openldap-dev \
libldap \
# zip
libzip-dev \
# xsl
libxslt-dev
# apache debian php extension base
FROM php:${PHP_VER}-apache-bookworm AS apache-php-ext-base
RUN apt-get update
RUN apt-get install -y \
libldap2-dev \
libicu-dev \
libpng-dev \
libzip-dev \
libxslt1-dev \
libfreetype6-dev
# php extension gd - 13.86s
FROM ${BASE}-php-ext-base AS php-ext-gd
RUN docker-php-ext-configure gd \
--with-freetype && \
docker-php-ext-install -j$(nproc) gd
# php extension intl : 15.26s
FROM ${BASE}-php-ext-base AS php-ext-intl
RUN docker-php-ext-install -j$(nproc) intl
# php extension ldap : 8.45s
FROM ${BASE}-php-ext-base AS php-ext-ldap
RUN docker-php-ext-configure ldap && \
docker-php-ext-install -j$(nproc) ldap
# php extension pdo_mysql : 6.14s
FROM ${BASE}-php-ext-base AS php-ext-pdo_mysql
RUN docker-php-ext-install -j$(nproc) pdo_mysql
# php extension zip : 8.18s
FROM ${BASE}-php-ext-base AS php-ext-zip
RUN docker-php-ext-install -j$(nproc) zip
# php extension xsl : ?.?? s
FROM ${BASE}-php-ext-base AS php-ext-xsl
RUN docker-php-ext-install -j$(nproc) xsl
# php extension redis
FROM ${BASE}-php-ext-base AS php-ext-redis
RUN yes no | pecl install redis && \
docker-php-ext-enable redis
# php extension opcache
FROM ${BASE}-php-ext-base AS php-ext-opcache
RUN docker-php-ext-install -j$(nproc) opcache
###########################
# fpm base build
###########################
# fpm base build
FROM php:${PHP_VER}-fpm-alpine AS fpm-base
ARG TIMEZONE
RUN apk add --no-cache \
bash \
coreutils \
freetype \
haveged \
icu \
icu-data-full \
libldap \
libpng \
libzip \
libxslt-dev \
fcgi \
tzdata && \
touch /use_fpm
EXPOSE 9000
HEALTHCHECK --interval=20s --timeout=10s --retries=3 \
CMD \
SCRIPT_NAME=/ping \
SCRIPT_FILENAME=/ping \
REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1
###########################
# apache base build
###########################
FROM php:${PHP_VER}-apache-bookworm AS apache-base
ARG TIMEZONE
RUN apt-get update && \
apt-get install -y \
bash \
haveged \
libicu72 \
libldap-common \
libpng16-16 \
libzip4 \
libxslt1.1 \
libfreetype6 && \
echo "Listen 8001" > /etc/apache2/ports.conf && \
a2enmod rewrite && \
touch /use_apache
EXPOSE 8001
HEALTHCHECK --interval=20s --timeout=10s --retries=3 \
CMD curl -f http://127.0.0.1:8001 || exit 1
###########################
# global base build
###########################
FROM ${BASE}-base AS base
ARG TIMEZONE
ENV TIMEZONE=${TIMEZONE}
RUN ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo ${TIMEZONE} > /etc/timezone && \
# make composer home dir
mkdir /composer && \
chown -R www-data:www-data /composer && \
echo "echo -e This imnage is not intended to be used stand alone. It's a base image\necho for the Kimai project. https://www.kimai.org\n" > /entrypoint.sh && \
chmod 777 /entrypoint.sh
# copy composer
COPY --from=composer /usr/bin/composer /usr/bin/composer
# copy php extensions
# PHP extension xsl
COPY --from=php-ext-xsl /usr/local/etc/php/conf.d/docker-php-ext-xsl.ini /usr/local/etc/php/conf.d/docker-php-ext-xsl.ini
COPY --from=php-ext-xsl /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xsl.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xsl.so
# PHP extension pdo_mysql
COPY --from=php-ext-pdo_mysql /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini
COPY --from=php-ext-pdo_mysql /usr/local/lib/php/extensions/no-debug-non-zts-20220829/pdo_mysql.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/pdo_mysql.so
# PHP extension zip
COPY --from=php-ext-zip /usr/local/etc/php/conf.d/docker-php-ext-zip.ini /usr/local/etc/php/conf.d/docker-php-ext-zip.ini
COPY --from=php-ext-zip /usr/local/lib/php/extensions/no-debug-non-zts-20220829/zip.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/zip.so
# PHP extension ldap
COPY --from=php-ext-ldap /usr/local/etc/php/conf.d/docker-php-ext-ldap.ini /usr/local/etc/php/conf.d/docker-php-ext-ldap.ini
COPY --from=php-ext-ldap /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ldap.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ldap.so
# PHP extension gd
COPY --from=php-ext-gd /usr/local/etc/php/conf.d/docker-php-ext-gd.ini /usr/local/etc/php/conf.d/docker-php-ext-gd.ini
COPY --from=php-ext-gd /usr/local/lib/php/extensions/no-debug-non-zts-20220829/gd.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/gd.so
# PHP extension intl
COPY --from=php-ext-intl /usr/local/etc/php/conf.d/docker-php-ext-intl.ini /usr/local/etc/php/conf.d/docker-php-ext-intl.ini
COPY --from=php-ext-intl /usr/local/lib/php/extensions/no-debug-non-zts-20220829/intl.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/intl.so
# PHP extension redis
COPY --from=php-ext-redis /usr/local/etc/php/conf.d/docker-php-ext-redis.ini /usr/local/etc/php/conf.d/docker-php-ext-redis.ini
COPY --from=php-ext-redis /usr/local/lib/php/extensions/no-debug-non-zts-20220829/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/redis.so
COPY --from=php-ext-opcache /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
CMD [ "/entrypoint.sh" ]
# docker build -t kimai/kimai-base:fpm --build-arg BASE=fpm .
# docker build -t kimai/kimai-base:apache --build-arg BASE=apache .

View File

@@ -1,9 +1,9 @@
<?php
$DB_HOST = $argv[1];
$DB_BASE = $argv[2];
$DB_HOST = urldecode($argv[1]);
$DB_BASE = urldecode($argv[2]);
$DB_PORT = $argv[3];
$DB_USER = $argv[4];
$DB_PASS = $argv[5];
$DB_USER = urldecode($argv[4]);
$DB_PASS = urldecode($argv[5]);
echo "Testing DB:";
echo "*";

View File

@@ -1,25 +0,0 @@
#!/bin/sh -e
if [ -z "$DATABASE_URL" ]; then
DATABASE_URL="mysql://kimai:kimai@127.0.0.1:3306/kimai?charset=utf8mb4&serverVersion=5.7.40"
fi
/opt/kimai/bin/console kimai:version
if [ $? != 0 ]; then
echo "PHP/Kimai not responding"
exit 1
fi
# Test FPM CGI
if [ -f /use_fpm ]; then
echo Testing FPM
php-fpm -t
fi
# Test Apache/httpd
if [ -f /use_apache ]; then
echo Testing Apache
apache2ctl -t
fi
exit 0

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,2 @@
github: [kevinpapst]
custom: ["https://www.kimai.org/donate/", "https://www.paypal.me/kevinpapst"]
custom: ["https://www.kimai.org/", "https://www.kimai.cloud/", "https://www.kevinpapst.de/"]

View File

@@ -21,8 +21,6 @@ version-resolver:
- 'translation'
default: patch
template: |
[Upgrade Kimai](https://www.kimai.org/documentation/updates.html) - [Install Kimai](https://www.kimai.org/documentation/installation.html) - [Docker](https://tobybatch.github.io/kimai2/)
**Compatible with PHP 8.1 to 8.3**
$CHANGES

43
.github/workflows/docker-base.yaml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: 'Docker base image'
# act -j build -W ./.github/workflows/docker-base.yaml --secret-file .dockerhub.secrets
on:
workflow_dispatch:
schedule:
- cron: "0 4 5 * *" # build on the 5th of every month
jobs:
build:
strategy:
matrix:
server: [ fpm, apache ]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{secrets.DOCKERHUB_USERNAME}}
password: ${{secrets.DOCKERHUB_PASSWORD}}
- name: Build
uses: docker/build-push-action@v5
with:
context: .docker
file: .docker/Dockerfile.kimai-base
build-args: |
TIMEZONE=Europe/London
BASE=${{ matrix.server }}
platforms: linux/amd64,linux/arm64 #,linux/arm/v8,linux/arm/v7,linux/arm/v6
tags: |
kimai/kimai-base:${{ matrix.server }}
push: true

View File

@@ -10,7 +10,6 @@ jobs:
build:
strategy:
matrix:
type: [ dev, prod ]
server: [ fpm, apache ]
runs-on: ubuntu-latest
@@ -20,13 +19,13 @@ jobs:
uses: actions/checkout@v4
- name: Install buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Install lastversion
run: sudo apt-get update -y; sudo apt-get install --no-install-recommends -y python3 python3-pip; pip install lastversion
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{secrets.DOCKERHUB_USERNAME}}
password: ${{secrets.DOCKERHUB_PASSWORD}}
@@ -36,21 +35,34 @@ jobs:
run: echo "kimai_version=$(lastversion https://github.com/kimai/kimai)" >> $GITHUB_ENV
- name: Build
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
build-args: |
KIMAI=${{ env.kimai_version }}
TIMEZONE=Europe/London
BASE=${{ matrix.server }}
target: ${{ matrix.type }}
platforms: linux/amd64,linux/arm64 #,linux/arm/v8,linux/arm/v7,linux/arm/v6
target: prod
platforms: linux/amd64,linux/arm64
tags: |
kimai/kimai2:${{ matrix.server }}-${{ matrix.type }}
kimai/kimai2:${{ matrix.server }}-${{ env.kimai_version }}-${{ matrix.type }}
kimai/kimai2:${{ matrix.server }}
kimai/kimai2:${{ matrix.server }}-${{ env.kimai_version }}
push: true
- name: Build dev image
if: matrix.server == 'apache'
uses: docker/build-push-action@v5
with:
context: .
build-args: |
KIMAI=${{ env.kimai_version }}
TIMEZONE=Europe/London
BASE=apache
target: dev
platforms: linux/amd64,linux/arm64
tags: |
kimai/kimai2:dev
push: true
- name: Test Lite
run: docker run --rm --entrypoint /assets/test-lite.sh kimai/kimai2:${{ matrix.server }}-${{ matrix.type }}
tag:
needs: build
@@ -58,49 +70,18 @@ jobs:
steps:
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{secrets.DOCKERHUB_USERNAME}}
password: ${{secrets.DOCKERHUB_PASSWORD}}
- name: Install buildx
uses: docker/setup-buildx-action@v3
- name: Pull images
run: |
docker pull kimai/kimai2:fpm-prod
docker pull kimai/kimai2:fpm-dev
docker pull kimai/kimai2:apache-prod
docker pull kimai/kimai2:apache-dev
- name: Tag fpm
run: |
docker tag kimai/kimai2:fpm-prod kimai/kimai2:fpm
docker push kimai/kimai2:fpm
- name: Tag fpm latest
run: |
docker tag kimai/kimai2:fpm-prod kimai/kimai2:fpm-latest
docker push kimai/kimai2:fpm-latest
docker pull kimai/kimai2:fpm
- name: Tag latest
run: |
docker tag kimai/kimai2:fpm-prod kimai/kimai2:latest
docker push kimai/kimai2:latest
- name: Tag prod
run: |
docker tag kimai/kimai2:fpm-prod kimai/kimai2:prod
docker push kimai/kimai2:prod
- name: Tag apache
run: |
docker tag kimai/kimai2:apache-prod kimai/kimai2:apache
docker push kimai/kimai2:apache
- name: Tag apache latest
run: |
docker tag kimai/kimai2:apache-prod kimai/kimai2:apache-latest
docker push kimai/kimai2:apache-latest
- name: Tag dev
run: |
docker tag kimai/kimai2:apache-dev kimai/kimai2:dev
docker push kimai/kimai2:dev
docker buildx imagetools create -t kimai/kimai2:latest kimai/kimai2:fpm

29
.github/workflows/lock-threads.yaml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: 'Lock Threads'
on:
schedule:
- cron: '17 1 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock-threads
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
with:
process-only: 'issues, prs'
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-inactive-days: '90'
issue-comment: >
This thread has been automatically locked since there has not been any recent activity after it was closed.
Please share your experience with the community and [leave a testimonial](https://love.kimai.org/) to support Kimai.
issue-lock-reason: 'resolved'
pr-inactive-days: '180'
log-output: true

View File

@@ -1,56 +0,0 @@
name: 'Lock Threads'
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
with:
github-token: ${{ github.token }}
issue-inactive-days: '90'
exclude-issue-created-before: ''
exclude-issue-created-after: ''
exclude-issue-created-between: ''
exclude-issue-closed-before: ''
exclude-issue-closed-after: ''
exclude-issue-closed-between: ''
include-any-issue-labels: ''
include-all-issue-labels: ''
exclude-any-issue-labels: ''
add-issue-labels: ''
remove-issue-labels: ''
issue-comment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
If you use Kimai on a daily basis, please [consider donating](https://www.kimai.org/donate/) to
support further development of Kimai.
issue-lock-reason: 'resolved'
pr-inactive-days: '180'
exclude-pr-created-before: ''
exclude-pr-created-after: ''
exclude-pr-created-between: ''
exclude-pr-closed-before: ''
exclude-pr-closed-after: ''
exclude-pr-closed-between: ''
include-any-pr-labels: ''
include-all-pr-labels: ''
exclude-any-pr-labels: ''
add-pr-labels: ''
remove-pr-labels: ''
pr-comment: ''
pr-lock-reason: ''
process-only: ''
log-output: false

View File

@@ -25,6 +25,6 @@ jobs:
needs: correct_repository
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -56,7 +56,7 @@ jobs:
run: composer install
- name: Validate Composer
run: composer validate --strict
run: composer validate --strict --no-check-all
- name: Warmup cache
run: APP_ENV=dev bin/console kimai:reload -n
@@ -93,7 +93,6 @@ jobs:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
APP_ENV: test
MAILER_URL: null://localhost
TEST_WITH_BUNDLES: 1
- name: Full test-suite with coverage
if: matrix.php == '8.2'
@@ -102,11 +101,10 @@ jobs:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
APP_ENV: dev
MAILER_URL: null://localhost
TEST_WITH_BUNDLES: 1
- name: Upload code coverage
if: matrix.php == '8.2'
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml

View File

@@ -34,7 +34,7 @@ jobs:
fi
- name: Emit repository_dispatch
uses: peter-evans/repository-dispatch@v2
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.WEBSITE_ACCESS_TOKEN }}
repository: kimai/www.kimai.org

4
.gitignore vendored
View File

@@ -59,4 +59,6 @@
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###
/nbproject/*
/nbproject/*
.dockerhub.secrets

View File

@@ -9,6 +9,7 @@ COMMENT;
$fixer = new PhpCsFixer\Config();
$fixer
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
->setRiskyAllowed(true)
->setRules([
'encoding' => true,

View File

@@ -28,164 +28,16 @@ RUN apk add --no-cache git && \
# production kimai source
FROM git-dev AS git-prod
WORKDIR /opt/kimai
RUN rm -r tests
# composer base image
FROM composer:${COMPOSER_VER} AS composer
###########################
# PHP extensions
###########################
# FPM base
FROM kimai/kimai-base:fpm AS fpm-base
#fpm alpine php extension base
FROM php:${PHP_VER}-fpm-alpine AS fpm-php-ext-base
RUN apk add --no-cache \
# build-tools
autoconf \
dpkg \
dpkg-dev \
file \
g++ \
gcc \
icu-dev \
libatomic \
libc-dev \
libgomp \
libmagic \
m4 \
make \
mpc1 \
mpfr4 \
musl-dev \
perl \
re2c \
# gd
freetype-dev \
libpng-dev \
# icu
icu-dev \
icu-data-full \
# ldap
openldap-dev \
libldap \
# zip
libzip-dev \
# xsl
libxslt-dev
# apache debian php extension base
FROM php:${PHP_VER}-apache-bookworm AS apache-php-ext-base
RUN apt-get update
RUN apt-get install -y \
libldap2-dev \
libicu-dev \
libpng-dev \
libzip-dev \
libxslt1-dev \
libfreetype6-dev
# php extension gd - 13.86s
FROM ${BASE}-php-ext-base AS php-ext-gd
RUN docker-php-ext-configure gd \
--with-freetype && \
docker-php-ext-install -j$(nproc) gd
# php extension intl : 15.26s
FROM ${BASE}-php-ext-base AS php-ext-intl
RUN docker-php-ext-install -j$(nproc) intl
# php extension ldap : 8.45s
FROM ${BASE}-php-ext-base AS php-ext-ldap
RUN docker-php-ext-configure ldap && \
docker-php-ext-install -j$(nproc) ldap
# php extension pdo_mysql : 6.14s
FROM ${BASE}-php-ext-base AS php-ext-pdo_mysql
RUN docker-php-ext-install -j$(nproc) pdo_mysql
# php extension zip : 8.18s
FROM ${BASE}-php-ext-base AS php-ext-zip
RUN docker-php-ext-install -j$(nproc) zip
# php extension xsl : ?.?? s
FROM ${BASE}-php-ext-base AS php-ext-xsl
RUN docker-php-ext-install -j$(nproc) xsl
# php extension redis
FROM ${BASE}-php-ext-base AS php-ext-redis
RUN yes no | pecl install redis && \
docker-php-ext-enable redis
# php extension opcache
FROM ${BASE}-php-ext-base AS php-ext-opcache
RUN docker-php-ext-install -j$(nproc) opcache
###########################
# fpm base build
###########################
# fpm base build
FROM php:${PHP_VER}-fpm-alpine AS fpm-base
ARG KIMAI
ARG TIMEZONE
RUN apk add --no-cache \
bash \
coreutils \
freetype \
haveged \
icu \
icu-data-full \
libldap \
libpng \
libzip \
libxslt-dev \
fcgi \
tzdata && \
touch /use_fpm
EXPOSE 9000
HEALTHCHECK --interval=20s --timeout=10s --retries=3 \
CMD \
SCRIPT_NAME=/ping \
SCRIPT_FILENAME=/ping \
REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1
###########################
# apache base build
###########################
FROM php:${PHP_VER}-apache-bookworm AS apache-base
ARG KIMAI
ARG TIMEZONE
# Apache base
FROM kimai/kimai-base:apache AS apache-base
COPY .docker/000-default.conf /etc/apache2/sites-available/000-default.conf
RUN apt-get update && \
apt-get install -y \
bash \
haveged \
libicu72 \
libldap-common \
libpng16-16 \
libzip4 \
libxslt1.1 \
libfreetype6 && \
echo "Listen 8001" > /etc/apache2/ports.conf && \
a2enmod rewrite && \
touch /use_apache
EXPOSE 8001
HEALTHCHECK --interval=20s --timeout=10s --retries=3 \
CMD curl -f http://127.0.0.1:8001 || exit 1
###########################
# global base build
@@ -195,14 +47,21 @@ FROM ${BASE}-base AS base
ARG KIMAI
ARG TIMEZONE
LABEL maintainer="tobias@neontribe.co.uk"
LABEL maintainer="bastian@schroll-software.de"
LABEL org.opencontainers.image.title="Kimai" \
org.opencontainers.image.description="Kimai is a time-tracking application." \
org.opencontainers.image.authors="Kimai Community" \
org.opencontainers.image.url="https://www.kimai.org/" \
org.opencontainers.image.documentation="https://www.kimai.org/documentation/" \
org.opencontainers.image.source="https://github.com/kimai/kimai" \
org.opencontainers.image.version="${KIMAI}" \
org.opencontainers.image.vendor="Kevin Papst" \
org.opencontainers.image.licenses="AGPL-3.0" \
org.opencontainers.image.base.name="docker.io/library/alpine"
ENV KIMAI=${KIMAI}
ENV TIMEZONE=${TIMEZONE}
RUN ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo ${TIMEZONE} > /etc/timezone && \
# make composer home dir
mkdir /composer && \
mkdir -p /composer && \
chown -R www-data:www-data /composer
# copy startup script & DB checking script
@@ -211,34 +70,6 @@ COPY .docker/service.sh /service.sh
COPY .docker/self-test.sh /self-test.sh
COPY .docker/dbtest.php /dbtest.php
# copy composer
COPY --from=composer /usr/bin/composer /usr/bin/composer
# copy php extensions
# PHP extension xsl
COPY --from=php-ext-xsl /usr/local/etc/php/conf.d/docker-php-ext-xsl.ini /usr/local/etc/php/conf.d/docker-php-ext-xsl.ini
COPY --from=php-ext-xsl /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xsl.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xsl.so
# PHP extension pdo_mysql
COPY --from=php-ext-pdo_mysql /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini
COPY --from=php-ext-pdo_mysql /usr/local/lib/php/extensions/no-debug-non-zts-20220829/pdo_mysql.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/pdo_mysql.so
# PHP extension zip
COPY --from=php-ext-zip /usr/local/etc/php/conf.d/docker-php-ext-zip.ini /usr/local/etc/php/conf.d/docker-php-ext-zip.ini
COPY --from=php-ext-zip /usr/local/lib/php/extensions/no-debug-non-zts-20220829/zip.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/zip.so
# PHP extension ldap
COPY --from=php-ext-ldap /usr/local/etc/php/conf.d/docker-php-ext-ldap.ini /usr/local/etc/php/conf.d/docker-php-ext-ldap.ini
COPY --from=php-ext-ldap /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ldap.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ldap.so
# PHP extension gd
COPY --from=php-ext-gd /usr/local/etc/php/conf.d/docker-php-ext-gd.ini /usr/local/etc/php/conf.d/docker-php-ext-gd.ini
COPY --from=php-ext-gd /usr/local/lib/php/extensions/no-debug-non-zts-20220829/gd.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/gd.so
# PHP extension intl
COPY --from=php-ext-intl /usr/local/etc/php/conf.d/docker-php-ext-intl.ini /usr/local/etc/php/conf.d/docker-php-ext-intl.ini
COPY --from=php-ext-intl /usr/local/lib/php/extensions/no-debug-non-zts-20220829/intl.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/intl.so
# PHP extension redis
COPY --from=php-ext-redis /usr/local/etc/php/conf.d/docker-php-ext-redis.ini /usr/local/etc/php/conf.d/docker-php-ext-redis.ini
COPY --from=php-ext-redis /usr/local/lib/php/extensions/no-debug-non-zts-20220829/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/redis.so
COPY --from=php-ext-opcache /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
ENV DATABASE_URL="mysql://kimai:kimai@127.0.0.1:3306/kimai?charset=utf8mb4&serverVersion=5.7.40"
ENV APP_SECRET=change_this_to_something_unique
# The default container name for nginx is nginx
@@ -266,7 +97,6 @@ VOLUME [ "/opt/kimai/var" ]
CMD [ "/startup.sh" ]
###########################
# final builds
###########################

View File

@@ -26,9 +26,7 @@ and so much more.
There are two [versions](https://www.kimai.org/documentation/versions.html) of Kimai existing:
- [Version 2](https://github.com/kimai/kimai) — the current stable release (PHP 8.1+)
- [Version 1](https://github.com/kimai/kimai/tree/1.x) — EOL since mid of 2023 (PHP 7.4)
Do **NOT** use Version 1, it won't get any more updates!
- [Version 1](https://github.com/kimai/kimai/tree/1.x) — do **NOT** use, EOL since mid of 2023 (PHP 7.4)
### Links
@@ -38,23 +36,20 @@ Do **NOT** use Version 1, it won't get any more updates!
### Requirements
- PHP 8.1.3 minimum
- PHP 8.1.3 minimum (support for PHP 8.2 and 8.3)
- MariaDB or MySQL
- A webserver and subdomain (subdirectory is not supported)
- PHP extensions: `gd`, `intl`, `json`, `mbstring`, `pdo`, `tokenizer`, `xml`, `xsl`, `zip`
Support for PHP 8.2 and 8.3.
## Installation
- [Recommended setup](https://www.kimai.org/documentation/installation.html#recommended-setup) — with Git and Composer
- [Docker](https://hub.docker.com/r/kimai/kimai2) — containerized by [@tobybatch](https://github.com/tobybatch/kimai2)
- Caddy with Docker-Compose at [Hetzner](https://www.kimai.org/documentation/hosting-hetzner-cloud.html) and [DigitalOcean](https://www.kimai.org/documentation/hosting-digital-ocean.html)
- [SSH setup](https://www.kimai.org/documentation/installation.html) with Git and Composer
- [Docker images](https://hub.docker.com/r/kimai/kimai2) with FPM only or incl. Apache
- [Synology](https://www.kimai.org/documentation/synology.html) user can host the Docker version
- [Developer setups](https://www.kimai.org/documentation/developers.html) if you want to create Kimai integrations
There are also documentations for:
- [developer setups](https://www.kimai.org/documentation/developers.html) — on your local machine
- [shared hostings](https://www.kimai.org/documentation/installation.html#shared-hosting) — the least favorable option
- [Synology](https://www.kimai.org/documentation/synology.html) — you could try to host the Docker version instead
- [1-click installer](https://www.kimai.org/documentation/installation.html#hosting-and-1-click-installations) — hosted environments
There are more documented ways for [on-premise hosting](https://www.kimai.org/documentation/chapter-on-premise.html).
And if you don't want to host Kimai, you can use [the Cloud version](https://www.kimai.cloud/) of it.
@@ -65,13 +60,12 @@ And if you don't want to host Kimai, you can use [the Cloud version](https://www
### Plugins
- [Plugin marketplace](https://www.kimai.org/store/) — find existing plugins here
- [Plugins](https://www.kimai.org/store/) — paid and free plugin marketplace
- [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/kimai/kimai/milestones) sections.
It is open for changes and input from the community, your [ideas and questions](https://github.com/kimai/kimai/issues) are welcome.
You can see a rough development [roadmap](https://github.com/orgs/kimai/projects/2), which is open for changes and input from the community, your [ideas](https://github.com/kimai/kimai/issues) are welcome.
Release versions will be created on a regular basis, every couple of weeks latest.
Every code change, whether it's a new feature or a bugfix, will be done on the `main` branch.
@@ -83,7 +77,7 @@ The best way to start is to [open a new issue](https://github.com/kimai/kimai/is
In case you want to contribute, but you wouldn't know how, here are some suggestions:
- Spread the word: More user means more people testing and contributing to Kimai, which in turn means better stability and more and better features. Please vote for Kimai on any software platform, you can toot or tweet about it, share it on LinkedIn, Reddit or any of your favorite social media platforms. Every bit helps!
- Spread the word: Please [write a testimonial for our Wall of love](https://love.kimai.org), vote for Kimai on any software platform, you can toot or tweet about it, share it on LinkedIn, Reddit and any other social media platform!
- Answer questions: You know the answer to another user's problem? Share your knowledge.
- Something can be done better? An essential feature is missing? Create a feature request.
- Report bugs makes Kimai better for everyone.

View File

@@ -1,27 +1,11 @@
# Security Policy
## Supported Versions
As announced in the [README](README.md) I only support the latest available release and `main` branch.
As announced in the [README](README.md) security fixes will only be added to the `main` branch.
| Version | Supported |
|----------------------|--------------------|
| main branch | :white_check_mark: |
| latest minor release | :white_check_mark: |
| older releases | :x: |
## Reporting a Vulnerability
Please read the [Bughunter](https://www.kimai.org/documentation/bughunter.html) documentation before posting.
You can report any security related vulnerability in the [advisory section at GitHub](https://github.com/kimai/kimai/security/advisories)
or via email to [support@kimai.org](mailto:support@kimai.org).
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.
You will be mentioned in the release notes if your issue is valid.
I am grateful for any (discrete) disclosure of vulnerabilities!
Please note: if you are asking for money, I will not reply. I receive these scam messages every day...
You find all information in our [Bughunter documentation](https://www.kimai.org/documentation/bughunter.html).

View File

@@ -8,9 +8,21 @@ you can upgrade your Kimai installation to the latest stable release.
Check below if there are more version specific steps required, which need to be executed after the normal update process.
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
## [2.14.0](https://github.com/kimai/kimai/releases/tag/2.13.0)
**DEVELOPERS ONLY**
The API now uses pagination globally for all collection endpoints. This might break current implementations
All collections are limited to 50 entries by default (changes in Customer, Project, Activity, User).
The parameters to influence pagination, page size and page number are `size` (and `pageSize`) and `page`.
Read the [API pagination docs](https://www.kimai.org/documentation/api-pagination.html) for more infos.
## [2.0.30](https://github.com/kimai/kimai/releases/tag/2.0.30)
The `DATABASE_URL` in your environment settings ([.env](https://github.com/kimai/kimai/issues/4246) or in your [Docker](https://github.com/tobybatch/kimai2/issues/531) or webserver config)
The `DATABASE_URL` in your environment settings (e.g. [.env](https://github.com/kimai/kimai/issues/4246), [docker-compose.yaml](https://github.com/tobybatch/kimai2/issues/531) or webserver config)
now requires the `charset` and `serverVersion` params, e.g.: `DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB` (examples in `.env`).
## [2.0](https://github.com/kimai/kimai/releases/tag/2.0)

View File

@@ -23,6 +23,7 @@ import {
Legend,
Title,
Tooltip,
Colors,
// SubTitle
} from 'chart.js';
@@ -50,6 +51,7 @@ Chart.register(
Legend,
Title,
Tooltip,
Colors
// SubTitle
);

View File

@@ -42,6 +42,7 @@ import KimaiNotification from "./plugins/KimaiNotification";
import KimaiHotkeys from "./plugins/KimaiHotkeys";
import KimaiRemoteModal from "./plugins/KimaiRemoteModal";
import KimaiUser from "./plugins/KimaiUser";
import KimaiAutocompleteTags from "./forms/KimaiAutocompleteTags";
export default class KimaiLoader {
@@ -70,6 +71,7 @@ export default class KimaiLoader {
kimai.registerPlugin(new KimaiDateRangePicker('input[data-daterangepicker="on"]'));
kimai.registerPlugin(new KimaiDatePicker('input[data-datepicker="on"]'));
kimai.registerPlugin(new KimaiAutocomplete());
kimai.registerPlugin(new KimaiAutocompleteTags());
kimai.registerPlugin(new KimaiTimesheetForm());
kimai.registerPlugin(new KimaiTeamForm());
kimai.registerPlugin(new KimaiCopyDataForm());
@@ -83,7 +85,7 @@ export default class KimaiLoader {
kimai.registerPlugin(new KimaiDatatable('section.content', 'table.dataTable'));
kimai.registerPlugin(new KimaiToolbar('form.searchform', 'toolbar-action'));
kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link'));
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form'));
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form', ['td.multiCheckbox', 'td.actions']));
kimai.registerPlugin(new KimaiRemoteModal());
kimai.registerPlugin(new KimaiActiveRecords());
kimai.registerPlugin(new KimaiAPILink('api-link'));

View File

@@ -6,13 +6,12 @@
*/
import TomSelect from 'tom-select';
import KimaiFormPlugin from "./KimaiFormPlugin";
import KimaiFormTomselectPlugin from "./KimaiFormTomselectPlugin";
/**
* Supporting auto-complete fields via API.
* Used for timesheet tagging in toolbar and edit dialogs.
*/
export default class KimaiAutocomplete extends KimaiFormPlugin {
export default class KimaiAutocomplete extends KimaiFormTomselectPlugin {
init()
{
@@ -28,11 +27,23 @@ export default class KimaiAutocomplete extends KimaiFormPlugin {
return true;
}
activateForm(form)
{
loadData(apiUrl, query, callback) {
/** @type {KimaiAPI} API */
const API = this.getContainer().getPlugin('api');
API.get(apiUrl, {'name': query}, (data) => {
let results = [];
for (let item of data) {
results.push({text: item.name, value: item.name});
}
callback(results);
}, () => {
callback();
});
}
activateForm(form)
{
[].slice.call(form.querySelectorAll(this.selector)).map((node) => {
const apiUrl = node.dataset['autocompleteUrl'];
let minChars = 3;
@@ -40,7 +51,7 @@ export default class KimaiAutocomplete extends KimaiFormPlugin {
minChars = parseInt(node.dataset['minimumCharacter']);
}
new TomSelect(node, {
let options = {
// see https://github.com/orchidjs/tom-select/issues/543#issuecomment-1664342257
onItemAdd: function(){
// remove remaining characters from input after selecting an item
@@ -58,36 +69,21 @@ export default class KimaiAutocomplete extends KimaiFormPlugin {
return query.length >= minChars;
},
load: (query, callback) => {
API.get(apiUrl, {'name': query}, (data) => {
const results = [].slice.call(data).map((result) => {
return {text: result, value: result};
});
callback(results);
}, () => {
callback();
});
this.loadData(apiUrl, query, callback);
},
render: {
// eslint-disable-next-line
not_loading: (data, escape) => {
// no default content
},
option_create: (data, escape) => {
const name = escape(data.input);
if (name.length < 3) {
return null;
}
const tpl = this.translate('select.search.create');
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>')
return '<div class="create">' + tplReplaced + '</div>';
},
no_results: (data, escape) => {
const tpl = this.translate('select.search.notfound');
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>')
return '<div class="no-results">' + tplReplaced + '</div>';
},
};
let render = {
// eslint-disable-next-line
not_loading: (data, escape) => {
// no default content
},
});
};
const rendererType = (node.dataset['renderer'] !== undefined) ? node.dataset['renderer'] : 'default';
options.render = {...render, ...this.getRenderer(rendererType)};
new TomSelect(node, options);
});
}

View File

@@ -0,0 +1,34 @@
/*
* 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 KimaiAutocomplete from "./KimaiAutocomplete";
/**
* Used for timesheet tagging in toolbar and edit dialogs.
*/
export default class KimaiAutocompleteTags extends KimaiAutocomplete {
init()
{
this.selector = '[data-form-widget="tags"]';
}
loadData(apiUrl, query, callback) {
/** @type {KimaiAPI} API */
const API = this.getContainer().getPlugin('api');
API.get(apiUrl, {'name': query}, (data) => {
let results = [];
for (let item of data) {
results.push({text: item.name, value: item.name, color: item['color-safe']});
}
callback(results);
}, () => {
callback();
});
}
}

View File

@@ -10,9 +10,9 @@
*/
import TomSelect from 'tom-select';
import KimaiFormPlugin from "./KimaiFormPlugin";
import KimaiFormTomselectPlugin from "./KimaiFormTomselectPlugin";
export default class KimaiFormSelect extends KimaiFormPlugin {
export default class KimaiFormSelect extends KimaiFormTomselectPlugin {
constructor(selector, apiSelects)
{
@@ -82,30 +82,20 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
plugins: plugins,
// if there are more than X entries, the other ones are hidden and can only be found
// by typing some characters to trigger the internal option search
// see App\Form\Type\TagsType::MAX_AMOUNT_SELECT
maxOptions: 500,
sortField:[{field: '$order'}, {field: '$score'}],
};
let render = {
option_create: (data, escape) => {
const name = escape(data.input);
if (name.length < 3) {
return null;
}
const tpl = this.translate('select.search.create');
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>');
return '<div class="create">' + tplReplaced + '</div>';
},
no_results: (data, escape) => {
const tpl = this.translate('select.search.notfound');
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>');
return '<div class="no-results">' + tplReplaced + '</div>';
},
onOptionAdd: (value) => {
node.dispatchEvent(new CustomEvent('create', {detail: {'value': value}}));
},
};
const rendererType = (node.dataset['renderer'] !== undefined) ? node.dataset['renderer'] : 'default';
options.render = {...render, ...this.getRenderer(rendererType)};
if (node.dataset['create'] !== undefined) {
options = {...options, ...{
persist: true,
@@ -124,44 +114,6 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
}};
}
if (node.dataset['renderer'] !== undefined && node.dataset['renderer'] === 'color') {
options.render = {...render, ...{
option: function(data, escape) {
let item = '<div class="list-group-item border-0 p-1 ps-2 text-nowrap">';
if (data.color !== undefined) {
item += '<span style="background-color:' + data.color + '" class="color-choice-item">&nbsp;</span>';
} else {
item += '<span class="color-choice-item">&nbsp;</span>';
}
item += escape(data.text) + '</div>';
return item;
},
item: function(data, escape) {
let item = '<div class="text-nowrap">';
if (data.color !== undefined) {
item += '<span style="background-color:' + data.color + '" class="color-choice-item">&nbsp;</span>';
} else {
item += '<span class="color-choice-item">&nbsp;</span>';
}
item += escape(data.text) + '</div>';
return item;
}
}};
} else {
options.render = {...render, ...{
// the empty entry would collapse and only show as a tiny 5px line if there is no content inside
option: function(data, escape) {
let text = data.text;
if (text === null || text.trim() === '') {
text = '&nbsp;';
} else {
text = escape(text);
}
return '<div>' + text + '</div>';
}
}};
}
const select = new TomSelect(node, options);
node.addEventListener('data-reloaded', (event) => {
select.clear(true);

View File

@@ -0,0 +1,80 @@
/*
* 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] KimaiFormPlugin: base class for all none ID plugin that handle forms
*/
import KimaiFormPlugin from './KimaiFormPlugin';
export default class KimaiFormTomselectPlugin extends KimaiFormPlugin {
/**
* @param {string} rendererType
* @return array
*/
getRenderer(rendererType)
{
// default renderer
let render = {
option_create: (data, escape) => {
const name = escape(data.input);
if (name.length < 3) {
return null;
}
const tpl = this.translate('select.search.create');
const tplReplaced = tpl.replace('%input%', '<strong>' + name + '</strong>')
return '<div class="create">' + tplReplaced + '</div>';
},
no_results: (data, escape) => {
const tpl = this.translate('select.search.notfound');
const tplReplaced = tpl.replace('%input%', '<strong>' + escape(data.input) + '</strong>')
return '<div class="no-results">' + tplReplaced + '</div>';
},
};
if (rendererType === 'color') {
render = {...render, ...{
option: function(data, escape) {
let item = '<div class="list-group-item border-0 p-1 ps-2 text-nowrap">';
// if no color is set, do NOT add an empty placeholder
if (data.color !== undefined) {
item += '<span style="background-color:' + data.color + '" class="color-choice-item">&nbsp;</span>';
}
item += escape(data.text) + '</div>';
return item;
},
item: function(data, escape) {
let item = '<div class="text-nowrap">';
// if no color is set, do NOT add an empty placeholder
if (data.color !== undefined) {
item += '<span style="background-color:' + data.color + '" class="color-choice-item">&nbsp;</span>';
}
item += escape(data.text) + '</div>';
return item;
}
}};
} else {
render = {...render, ...{
// the empty entry would collapse and only show as a tiny 5px line if there is no content inside
option: function(data, escape) {
let text = data.text;
if (text === null || text.trim() === '') {
text = '&nbsp;';
} else {
text = escape(text);
}
return '<div>' + text + '</div>';
}
}};
}
return render;
}
}

View File

@@ -19,7 +19,6 @@ export default class KimaiAPI extends KimaiPlugin {
_headers() {
const headers = new Headers();
headers.append('X-AUTH-SESSION', '1');
headers.append('Content-Type', 'application/json');
return headers;

View File

@@ -17,9 +17,10 @@ import { Modal } from 'bootstrap';
export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
constructor(selector) {
constructor(selector, stopSelector) {
super();
this._selector = selector;
this._stopSelector = stopSelector;
}
getId()
@@ -63,7 +64,7 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
this.addClickHandler(this._selector, (href) => {
this.openUrlInModal(href);
});
}, this._stopSelector);
}
_getModal()

View File

@@ -24,7 +24,7 @@ export default class KimaiAlternativeLinks extends KimaiReducedClickHandler {
init() {
this.addClickHandler(this._selector, function(href) {
window.location = href;
});
}, []);
}
}

View File

@@ -15,10 +15,11 @@ export default class KimaiReducedClickHandler extends KimaiPlugin {
/**
* No _underscore naming for now, as it would be mangled otherwise
* @param selector
* @param callback
* @param {string} selector
* @param {callback} callback
* @param {array<string>} stopSelector
*/
addClickHandler(selector, callback) {
addClickHandler(selector, callback, stopSelector) {
document.body.addEventListener('click', (event) => {
// event.currentTarget is ALWAYS the body
@@ -38,6 +39,12 @@ export default class KimaiReducedClickHandler extends KimaiPlugin {
return;
}
for (let x of stopSelector) {
if (target.matches(x)) {
return;
}
}
target = target.parentNode;
}
@@ -54,6 +61,12 @@ export default class KimaiReducedClickHandler extends KimaiPlugin {
return;
}
for (let x of stopSelector) {
if (target.matches(x)) {
return;
}
}
event.preventDefault();
event.stopPropagation();

View File

@@ -4,7 +4,7 @@ fieldset legend.col-form-label {
}
/* See project assignment to teams */
fieldset.form-fieldset legend {
fieldset.form-fieldset > legend {
font-size: 1rem;
font-weight: bold;
}

View File

@@ -123,7 +123,7 @@ table.dataTable {
}
th.weekend,
td.weekend {
background-color: var(--tblr-bg-surface-secondary);
background-color: var(--tblr-gray-50);
}
/* order is important, "today” should overwrite "weekend" therefor later in the file */
th.today {

View File

@@ -1,8 +1,26 @@
@include color-mode(dark, true) {
$dark-font-opacity: 0.6;
/*
--tblr-body-bg: #1e222b;
--tblr-bg-surface-tertiary: #1e222b;
--tblr-bg-surface: #1e222b;
--tblr-bg-surface-secondary: #17202b;
//--tblr-muted: #d9dcdf;
*/
.table thead th {
color: inherit;
opacity: $dark-font-opacity;
}
table.dataTable {
th.today {
color: var(--tblr-muted);
}
thead .sorting:after {
opacity: $dark-font-opacity;
}
}
.overlay {
@@ -12,4 +30,16 @@
color: var(--tblr-white);
}
}
/* Fix tabs pills (e.g. user profile) */
.nav {
--tblr-nav-link-color: inherit;
opacity: $dark-font-opacity;
}
.datagrid-title {
color: inherit;
opacity: $dark-font-opacity;
}
.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
color: inherit;
}
}

View File

@@ -35,14 +35,14 @@
"friendsofsymfony/rest-bundle": "^3.0",
"gedmo/doctrine-extensions": "^3.6",
"jms/serializer-bundle": "^5.0",
"kevinpapst/tabler-bundle": "^1.2",
"kevinpapst/tabler-bundle": "1.4.2",
"league/csv": "^9.4",
"mpdf/mpdf": "^8.0",
"nelmio/api-doc-bundle": "^4.0",
"nelmio/cors-bundle": "^2.0",
"onelogin/php-saml": "^4.0",
"pagerfanta/pagerfanta": "^3.0",
"phpoffice/phpspreadsheet": "^1.16",
"phpoffice/phpspreadsheet": "^2.0",
"phpoffice/phpword": "^1.0",
"psr/container": "^2.0",
"psr/log": "^3.0",
@@ -84,6 +84,7 @@
"fakerphp/faker": "^1.15",
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-doctrine": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
@@ -170,7 +171,6 @@
"@tests-integration"
],
"linting": [
"composer validate --strict",
"bin/console lint:container",
"bin/console lint:yaml config --parse-tags",
"bin/console lint:twig templates --show-deprecations",
@@ -197,9 +197,6 @@
"symfony/symfony": "*"
},
"extra": {
"branch-alias": {
"dev-main": "2.0.x-dev"
},
"symfony": {
"id": "01C3FWRDJJEX9K6Y3A4XDFXPBR",
"allow-contrib": true,

2941
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -386,8 +386,8 @@
],
'en_ID' =>
[
'date' => 'M/d/yy',
'time' => 'HH:mm',
'date' => 'dd/MM/y',
'time' => 'HH.mm',
'rtl' => false,
'translation' => false,
],
@@ -603,7 +603,7 @@
],
'en_NZ' =>
[
'date' => 'd/MM/yy',
'date' => 'dd/MM/y',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
@@ -870,14 +870,14 @@
'es_AR' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
'es_BO' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
@@ -898,7 +898,7 @@
'es_CL' =>
[
'date' => 'dd-MM-yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
@@ -912,14 +912,14 @@
'es_CR' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
'es_CU' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
@@ -933,7 +933,7 @@
'es_EC' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
@@ -954,28 +954,28 @@
'es_GT' =>
[
'date' => 'd/MM/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
'es_HN' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
'es_MX' =>
[
'date' => 'dd/MM/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
'es_NI' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
@@ -989,7 +989,7 @@
'es_PE' =>
[
'date' => 'd/MM/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
@@ -1010,14 +1010,14 @@
'es_PY' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
'es_SV' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
@@ -1031,7 +1031,7 @@
'es_UY' =>
[
'date' => 'd/M/yy',
'time' => 'HH:mm',
'time' => 'h:mm a',
'rtl' => false,
'translation' => false,
],
@@ -1835,7 +1835,7 @@
],
'sl' =>
[
'date' => 'd. MM. yy',
'date' => 'd. M. yy',
'time' => 'HH:mm',
'rtl' => false,
'translation' => true,
@@ -1905,14 +1905,14 @@
],
'vi' =>
[
'date' => 'dd/MM/y',
'date' => 'd/M/yy',
'time' => 'HH:mm',
'rtl' => false,
'translation' => true,
],
'vi_VN' =>
[
'date' => 'dd/MM/y',
'date' => 'd/M/yy',
'time' => 'HH:mm',
'rtl' => false,
'translation' => false,
@@ -1931,4 +1931,11 @@
'rtl' => false,
'translation' => true,
],
'zh_Hant_TW' =>
[
'date' => 'y/M/d',
'time' => 'Bh:mm',
'rtl' => false,
'translation' => true,
],
];

View File

@@ -22,7 +22,10 @@ doctrine:
types:
datetime: App\Doctrine\UTCDateTimeType
datetime_immutable: App\Doctrine\UTCDateTimeImmutableType
orm:
controller_resolver:
auto_mapping: true
auto_generate_proxy_classes: '%kernel.debug%'
default_entity_manager: default
enable_lazy_ghost_objects: true

View File

@@ -7,6 +7,8 @@ framework:
csrf_protection: true
annotations: false
handle_all_throwables: true
disallow_search_engine_index: true
set_content_language_from_locale: true
serializer:
enable_attributes: true

View File

@@ -105,21 +105,22 @@ kimai:
TEAMS: ['view_team','create_team','edit_team','delete_team']
LOCKDOWN: ['lockdown_grace_timesheet','lockdown_override_timesheet']
REPORTING: ['view_reporting','view_other_reporting','project_reporting','customer_reporting']
EVERYONE: ['api_access']
# permissions which are deactivated, as these features are hidden for now
# brave users can try to activate them and be surprised what happens
REGISTER_BETA: []
# mapping a "role name" to an array of "set names"
maps:
ROLE_USER: ['TIMESHEET','PROFILE']
ROLE_TEAMLEAD: ['ACTIVITIES_TEAMLEAD','PROJECTS_TEAMLEAD','CUSTOMERS_TEAMLEAD','TIMESHEET_OTHER','INVOICE','TIMESHEET','PROFILE','EXPORT','BILLABLE','TAGS','REPORTING']
ROLE_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING']
ROLE_SUPER_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','PROFILE_OTHER','USER','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING']
ROLE_USER: ['TIMESHEET','PROFILE', 'EVERYONE']
ROLE_TEAMLEAD: ['ACTIVITIES_TEAMLEAD','PROJECTS_TEAMLEAD','CUSTOMERS_TEAMLEAD','TIMESHEET_OTHER','INVOICE','TIMESHEET','PROFILE','EXPORT','BILLABLE','TAGS','REPORTING', 'EVERYONE']
ROLE_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING', 'EVERYONE']
ROLE_SUPER_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','PROFILE_OTHER','USER','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING', 'EVERYONE']
# mapping a "role name" to an array of "permission names"
roles:
ROLE_USER: ['view_team_member','time_team_project','create_tag','view_reporting']
ROLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member']
ROLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member','view_all_data','contract_other_profile']
ROLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','supervisor_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template','view_all_data','contract_other_profile']
ROLE_USER: ['view_team_member','time_team_project','create_tag','view_reporting','hours_own_profile']
ROLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member','hours_other_profile','hours_own_profile']
ROLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member','view_all_data','contract_other_profile','hours_other_profile','hours_own_profile']
ROLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','supervisor_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template','view_all_data','contract_other_profile','hours_other_profile','hours_own_profile']
# --------------------------------------------------------------------------------

View File

@@ -19,12 +19,6 @@ when@prod:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!deprecation"]
# deactivated, because currently there are too many deprecations cause by gedmo and doctrine
# deprecation:
# type: stream
# channels: ["deprecation"]
# path: "%kernel.logs_dir%/deprecations.log"
# formatter: monolog.formatter.deprecation
when@dev:
monolog:

View File

@@ -40,27 +40,23 @@ nelmio_api_doc:
- { alias: TeamCollection, type: App\Entity\Team, groups: [Default, Collection, Team] }
- { alias: TeamMember, type: App\Entity\TeamMember, groups: [Team_Entity] }
- { alias: TeamMembership, type: App\Entity\TeamMember, groups: [User_Entity] }
- { alias: Invoice, type: App\Entity\Invoice, groups: [Default, Entity, Invoice, Invoice_Entity] }
- { alias: InvoiceCollection, type: App\Entity\Invoice, groups: [Default, Collection, Invoice] }
areas:
path_patterns:
- ^/api(?!/doc)
documentation:
info:
title: Kimai - API Docs
title: Kimai - API
description: |
JSON API for the Kimai time-tracking software: [API documentation](https://www.kimai.org/documentation/rest-api.html), [Swagger definition file](doc.json)
version: '0.7'
JSON API for the Kimai time-tracking software. Read our [API documentation](https://www.kimai.org/documentation/rest-api.html) and download the [Open API definition](doc.json) to import into your API client.
version: '1.0'
components:
securitySchemes:
apiUser:
type: apiKey
description: 'Value: {Username}'
name: X-AUTH-USER
in: header
apiToken:
type: apiKey
description: 'Value: {API Token}'
name: X-AUTH-TOKEN
in: header
bearer:
type: http
scheme: bearer
bearerFormat: KIMAI
description: API Token
security:
- X-AUTH-USER: []
X-AUTH-TOKEN: []
- bearer: []

View File

@@ -17,9 +17,10 @@ nelmio_cors:
hosts: []
origin_regex: true
forced_allow_origin_value: ~
# allow_private_network: true
paths:
'^/api/':
allow_origin: ['*']
allow_headers: ['X-AUTH-USER', 'X-AUTH-TOKEN', 'Content-Type']
allow_headers: ['X-AUTH-USER', 'X-AUTH-TOKEN', 'Content-Type', 'Authorization']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
max_age: 3600

View File

@@ -0,0 +1,7 @@
framework:
rate_limiter:
session_prediction:
policy: 'fixed_window'
limit: 250
interval: '1 hour'
lock_factory: null

View File

@@ -18,6 +18,10 @@ security:
security: false
api:
access_token:
token_handler: App\API\Authentication\AccessTokenHandler
success_handler: App\API\Authentication\AccessTokenSuccessHandler
remember_me: false
request_matcher: App\API\Authentication\ApiRequestMatcher
user_checker: App\Security\UserChecker
stateless: true
@@ -35,7 +39,6 @@ security:
entry_point: form_login
custom_authenticators:
- App\API\Authentication\SessionAuthenticator
- App\Saml\SamlAuthenticator
remember_me:
@@ -60,7 +63,7 @@ security:
logout:
path: logout
target: homepage
enable_csrf: true
enable_csrf: false
login_throttling:
max_attempts: 5
@@ -69,10 +72,11 @@ security:
login_link:
check_route: link_login_check
signature_properties: ['id']
lifetime: 300
max_uses: 1
lifetime: 900
max_uses: 3
access_decision_manager:
# only grants access if there is no voter denying access
strategy: unanimous
allow_if_all_abstain: false
@@ -85,13 +89,13 @@ security:
access_control:
- { path: '^/auth/2fa', role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
- { path: '^/auth', roles: PUBLIC_ACCESS }
- { path: '^/(%app_locales%)$', role: PUBLIC_ACCESS }
- { path: '^/(%app_locales%)/auth', role: PUBLIC_ACCESS }
- { path: '^/(%app_locales%)/login', role: PUBLIC_ACCESS }
- { path: '^/(%app_locales%)/register', role: PUBLIC_ACCESS }
- { path: '^/(%app_locales%)/resetting', role: PUBLIC_ACCESS }
- { path: '^/(%app_locales%)/', roles: ROLE_USER }
- { path: '^/api', roles: IS_AUTHENTICATED_REMEMBERED }
- { path: '^/{_locale}$', role: PUBLIC_ACCESS }
- { path: '^/{_locale}/auth', role: PUBLIC_ACCESS }
- { path: '^/{_locale}/login', role: PUBLIC_ACCESS }
- { path: '^/{_locale}/register', role: PUBLIC_ACCESS }
- { path: '^/{_locale}/resetting', role: PUBLIC_ACCESS }
- { path: '^/{_locale}/', roles: ROLE_USER }
- { path: '^/api', roles: IS_AUTHENTICATED }
when@test:
# this configuration simplifies testing URLs protected by the security mechanism
@@ -104,7 +108,3 @@ when@test:
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon
firewalls:
secured_area:
http_basic: ~

View File

@@ -29,7 +29,6 @@ tabler:
routes:
tabler_welcome: dashboard
tabler_login: login
tabler_logout: logout
tabler_login_check: security_check
tabler_registration: registration_register
tabler_registration_register: registration_register
@@ -122,7 +121,7 @@ tabler:
search: fas fa-search
settings: fas fa-cog
shop: fas fa-shopping-cart
spinner: fas fa-spinner fa-pulse
spinner: fas fa-spinner
start: fas fa-play
start-small: far fa-play-circle
stop: fas fa-stop

View File

@@ -1,4 +1,6 @@
framework:
# https://symfony.com/doc/current/reference/configuration/framework.html#enabled-locales
enabled_locales: '%kimai_locales%'
default_locale: '%locale%'
translator:
default_path: '%kernel.project_dir%/translations'

View File

@@ -2,8 +2,6 @@ controllers:
resource: ../src/Controller/
type: attribute
prefix: /{_locale}
requirements:
_locale: '%app_locales%'
defaults:
_locale: '%locale%'
@@ -31,8 +29,6 @@ security:
resource: ../src/Controller/Security/
type: attribute
prefix: /{_locale}
requirements:
_locale: '%app_locales%'
defaults:
_locale: '%locale%'
@@ -49,8 +45,6 @@ home:
homeLocale:
path: /{_locale}
requirements:
_locale: '%app_locales%'
defaults:
_controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
_locale: '%locale%'

View File

@@ -1,7 +1,5 @@
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /{_locale}/_error
requirements:
_locale: '%app_locales%'
defaults:
_locale: '%locale%'

View File

@@ -1,7 +1,5 @@
app.test.active_entries:
path: /{_locale}/layou/active_entries
controller: App\Controller\LayoutController::activeEntries
requirements:
_locale: '%app_locales%'
defaults:
_locale: '%locale%'

View File

@@ -1,7 +1,7 @@
parameters:
locale: en
# can be regenerated with "bin/console kimai:reset:locales"
app_locales: ar|cs|da|de|de_CH|el|en|eo|es|eu|fa|fi|fo|fr|he|hr|hu|id|it|ja|ko|nb_NO|nl|pa|pl|pt|pt_BR|ro|ru|sk|sl|sv|tr|uk|vi|zh_CN|zh_Hant|cs_CZ|da_DK|da_GL|de_AT|de_BE|de_CH|de_DE|de_IT|de_LI|de_LU|el_CY|el_GR|en_AE|en_AG|en_AI|en_AS|en_AT|en_AU|en_BB|en_BE|en_BI|en_BM|en_BS|en_BW|en_BZ|en_CA|en_CC|en_CH|en_CK|en_CM|en_CX|en_CY|en_DE|en_DG|en_DK|en_DM|en_ER|en_FI|en_FJ|en_FK|en_FM|en_GB|en_GD|en_GG|en_GH|en_GI|en_GM|en_GU|en_GY|en_HK|en_ID|en_IE|en_IL|en_IM|en_IN|en_IO|en_JE|en_JM|en_KE|en_KI|en_KN|en_KY|en_LC|en_LR|en_LS|en_MG|en_MH|en_MO|en_MP|en_MS|en_MT|en_MU|en_MV|en_MW|en_MY|en_NA|en_NF|en_NG|en_NH|en_NL|en_NR|en_NU|en_NZ|en_PG|en_PH|en_PK|en_PN|en_PR|en_PW|en_RH|en_RW|en_SB|en_SC|en_SD|en_SE|en_SG|en_SH|en_SI|en_SL|en_SS|en_SX|en_SZ|en_TC|en_TK|en_TO|en_TT|en_TV|en_TZ|en_UG|en_UM|en_US|en_VC|en_VG|en_VI|en_VU|en_WS|en_ZA|en_ZM|en_ZW|es_AR|es_BO|es_BR|es_BZ|es_CL|es_CO|es_CR|es_CU|es_DO|es_EA|es_EC|es_ES|es_GQ|es_GT|es_HN|es_IC|es_MX|es_NI|es_PA|es_PE|es_PH|es_PR|es_PY|es_SV|es_US|es_UY|es_VE|eu_ES|fa_AF|fa_IR|fi_FI|fo_DK|fo_FO|fr_BE|fr_BF|fr_BI|fr_BJ|fr_BL|fr_CA|fr_CD|fr_CF|fr_CG|fr_CH|fr_CI|fr_CM|fr_DJ|fr_DZ|fr_FR|fr_GA|fr_GF|fr_GN|fr_GP|fr_GQ|fr_HT|fr_KM|fr_LU|fr_MA|fr_MC|fr_MF|fr_MG|fr_ML|fr_MQ|fr_MR|fr_MU|fr_NC|fr_NE|fr_PF|fr_PM|fr_RE|fr_RW|fr_SC|fr_SN|fr_SY|fr_TD|fr_TG|fr_TN|fr_VU|fr_WF|fr_YT|he_IL|hr_BA|hr_HR|hu_HU|it_CH|it_IT|it_SM|it_VA|ja_JP|ko_CN|ko_KP|ko_KR|nl_AW|nl_BE|nl_BQ|nl_CW|nl_NL|nl_SR|nl_SX|pl_PL|pt_AO|pt_BR|pt_CH|pt_CV|pt_GQ|pt_GW|pt_LU|pt_MO|pt_MZ|pt_PT|pt_ST|pt_TL|ro_MD|ro_RO|ru_BY|ru_KG|ru_KZ|ru_MD|ru_RU|ru_UA|sk_SK|sv_AX|sv_FI|sv_SE|tr_CY|tr_TR|uk_UA|vi_VN
# can be regenerated with: bin/console kimai:reset:locales
kimai_locales: ['ar', 'ca', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'ca_AD', 'ca_ES', 'ca_FR', 'ca_IT', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GU', 'en_GY', 'en_HK', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PN', 'en_PR', 'en_PW', 'en_RH', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN']
services:
# default configuration for services in *this* file
@@ -23,12 +23,14 @@ services:
- '../src/API/Model/'
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Event/'
- '../src/Form/Model/'
- '../src/Model/'
- '../src/Repository/Loader/'
- '../src/Repository/Paginator/'
- '../src/Repository/Query/'
- '../src/Repository/Result/'
- '../src/Event/'
- '../src/Model/'
- '../src/WorkingTime/Calculator/'
- '../src/Kernel.php'
- '../src/Constants.php'
@@ -100,7 +102,9 @@ services:
# SAML Services
# ================================================================================
App\Saml\SamlProvider:
arguments: ['@App\Repository\UserRepository', '@security.user.provider.concrete.kimai_internal', '@App\Configuration\SamlConfigurationInterface']
arguments:
$userProvider: '@security.user.provider.concrete.kimai_internal'
$configuration: '@App\Configuration\SamlConfigurationInterface'
# ================================================================================
# REPOSITORIES
@@ -195,6 +199,11 @@ services:
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\WorkingTime']
App\Repository\AccessTokenRepository:
class: App\Repository\AccessTokenRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\AccessToken']
monolog.formatter.kimai:
class: Monolog\Formatter\LineFormatter
arguments:

View File

@@ -1,6 +1,6 @@
parameters:
locale: en
app_locales: ar|cs|de|de_CH|da|en|eo|es|eu|fr|he|hu|it|ja|ko|nl|pl|pt_BR|ro|ru|sk|sv|tr|vi|zh_CN
kimai_locales: ['ar', 'cs', 'de', 'de_CH', 'da', 'en', 'eo', 'es', 'eu', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'nl', 'pl', 'pt_BR', 'ro', 'ru', 'sk', 'sv', 'tr', 'vi', 'zh_CN']
services:
_defaults:
@@ -23,7 +23,3 @@ services:
App\User\UserService:
public: true
arguments: ['@App\Repository\UserRepository', '@event_dispatcher', '@validator', '@App\Configuration\SystemConfiguration', '@security.user_password_hasher']
App\Plugin\PluginManager:
public: true
arguments: [!tagged kimai.plugin]

View File

@@ -24,11 +24,15 @@ final class <className> extends AbstractMigration
public function up(Schema $schema): void
{
$this->abortIf(true, 'Migration was auto-generated, adapt to your needs before running it.');
<up>
}
public function down(Schema $schema): void
{
$this->abortIf(true, 'Migration was auto-generated, adapt to your needs before running it.');
<down>
}<override>
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* @version 2.14
*/
final class Version20240214061246 extends AbstractMigration
{
public function getDescription(): string
{
return 'Adds the table for API access tokens';
}
public function up(Schema $schema): void
{
$accessTokens = $schema->createTable('kimai2_access_token');
$accessTokens->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]);
$accessTokens->addColumn('user_id', 'integer', ['notnull' => true]);
$accessTokens->addColumn('token', 'string', ['notnull' => true, 'length' => 100]);
$accessTokens->addColumn('name', 'string', ['notnull' => true, 'length' => 50]);
$accessTokens->addColumn('last_usage', 'datetime_immutable', ['notnull' => false, 'default' => null]);
$accessTokens->addColumn('expires_at', 'datetime_immutable', ['notnull' => false, 'default' => null]);
$accessTokens->setPrimaryKey(['id']);
$accessTokens->addIndex(['user_id'], 'IDX_6FB0DB1EA76ED395');
$accessTokens->addUniqueIndex(['token'], 'UNIQ_6FB0DB1E5F37A13B');
$accessTokens->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_6FB0DB1EA76ED395');
}
public function down(Schema $schema): void
{
$table = $schema->getTable('kimai2_access_token');
$table->removeForeignKey('FK_6FB0DB1EA76ED395');
$schema->dropTable('kimai2_access_token');
}
public function isTransactional(): bool
{
return false;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* @version 2.14
*/
final class Version20240326125247 extends AbstractMigration
{
public function getDescription(): string
{
return 'Adds the number columns to activity and project';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE kimai2_activities ADD number VARCHAR(10) DEFAULT NULL');
$this->addSql('ALTER TABLE kimai2_projects ADD number VARCHAR(10) DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE kimai2_projects DROP number');
$this->addSql('ALTER TABLE kimai2_activities DROP number');
}
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* @version 2.22
*/
final class Version20240920105524 extends AbstractMigration
{
public function getDescription(): string
{
return 'Updates the user preferences for configurable work contract type';
}
public function up(Schema $schema): void
{
$ids = $this->connection->fetchFirstColumn("
SELECT DISTINCT user_id
FROM kimai2_user_preferences AS kp
WHERE kp.value > 0
AND kp.name IN ('work_monday', 'work_tuesday', 'work_wednesday', 'work_thursday', 'work_friday', 'work_saturday', 'work_sunday')
AND NOT EXISTS (
SELECT 1
FROM kimai2_user_preferences AS kp2
WHERE kp2.user_id = kp.user_id
AND kp2.name = 'work_contract_type'
);");
foreach ($ids as $id) {
$this->addSql('INSERT INTO kimai2_user_preferences (`user_id`, `name`, `value`) VALUES (:id, :name, :value)', [
'id' => $id,
'name' => 'work_contract_type',
'value' => 'day',
]);
}
if (\count($ids) === 0) {
$this->preventEmptyMigrationWarning();
}
}
public function down(Schema $schema): void
{
$this->addSql("DELETE FROM kimai2_user_preferences WHERE `name` = 'work_contract_type'");
}
public function isTransactional(): bool
{
return true;
}
}

View File

@@ -24,10 +24,10 @@
],
"packageManager": "yarn@3.2.1",
"devDependencies": {
"@babel/core": "^7.19.6",
"@babel/eslint-parser": "^7.18.2",
"@babel/preset-env": "^7.19.4",
"@fortawesome/fontawesome-free": "^6.5.0",
"@babel/core": "^7.19",
"@babel/eslint-parser": "^7.19",
"@babel/preset-env": "^7.19",
"@fortawesome/fontawesome-free": "^6.5",
"@fullcalendar/bootstrap5": "^5.11",
"@fullcalendar/core": "^5.11",
"@fullcalendar/daygrid": "^5.11",
@@ -37,17 +37,17 @@
"@fullcalendar/timegrid": "^5.11",
"@symfony/webpack-encore": "^4.0",
"@tabler/core": "1.0.0-beta19",
"chart.js": "^3.9",
"chart.js": "^4.0",
"core-js": "^3.6",
"eslint": "^8.0",
"eslint-webpack-plugin": "^3.0",
"gridstack": "^7.1",
"litepicker": "^2.0",
"luxon": "^3.3",
"sass": "^1.62.1",
"sass": "^1.62",
"sass-loader": "^13.2",
"tom-select": "^2.2",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
"webpack": "^5.91",
"webpack-cli": "^4.10"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,5 @@
# See https://github.com/symfony/recipes-contrib/blob/main/symfony/apache-pack/1.0/public/.htaccess#L38
# Use the front controller as index file. It serves as a fallback solution when
# every other rewrite/redirect fails (e.g. in an aliased environment without
# mod_rewrite). Additionally, this reduces the matching process for the
@@ -5,30 +7,46 @@
# 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:
# By default, Apache does not evaluate symbolic links if you did not enable this
# feature in your server configuration. Uncomment the following line if you
# install assets as symlinks or if you experience problems related to symlinks
# when compiling LESS/Sass/CoffeScript assets.
# Options +SymLinksIfOwnerMatch
#<IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{SERVER_PORT} 80
# RewriteRule ^(.*)$ https://kimai.example.com/$1 [R,L]
#</IfModule>
# Disabling MultiViews prevents unwanted negotiation, e.g. "/index" should not resolve
# to the front controller "/index.php" but be rewritten to "/index.php/index".
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
<IfModule mod_rewrite.c>
# This Option needs to be enabled for RewriteRule, otherwise it will show an error like
# 'Options FollowSymLinks or SymLinksIfOwnerMatch is off which implies that RewriteRule directive is forbidden'
Options +SymLinksIfOwnerMatch
RewriteEngine On
# Determine the RewriteBase automatically and set it as environment variable.
# If you are using Apache aliases to do mass virtual hosting or installed the
# project in a subdirectory, the base path will be prepended to allow proper
# resolution of the app.php file and to redirect to the correct URI. It will
# resolution of the index.php file and to redirect to the correct URI. It will
# work in environments without path prefix as well, providing a safe, one-size
# fits all solution. But as you do not need it in this case, you can comment
# the following 2 lines to eliminate the overhead.
RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
RewriteRule ^(.*) - [E=BASE:%1]
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
RewriteRule .* - [E=BASE:%1]
# Sets the HTTP_AUTHORIZATION header removed by Apache, if PHP is installed as CGI
# Required for the API and below are 3 different methods to achieve it
RewriteCond %{HTTP:Authorization} .+
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
# This might be the only required line
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
# Apache 2.4.13+
# CGIPassAuth On
# Redirect to URI without front controller to prevent duplicate content
# (with and without `/app.php`). Only do this redirect on the initial
# (with and without `/index.php`). Only do this redirect on the initial
# rewrite by Apache and not on subsequent cycles. Otherwise we would get an
# endless redirect loop (request -> rewrite to front controller ->
# redirect -> request -> ...).
@@ -38,16 +56,14 @@ DirectoryIndex index.php
# - disable this feature by commenting the following 2 lines or
# - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
# following RewriteCond (best solution)
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^index\.php(/(.*)|$) %{ENV:BASE}/$2 [R=301,L]
RewriteCond %{ENV:REDIRECT_STATUS} =""
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
# If the requested filename exists, simply serve it.
# We only want to let Apache serve files and not directories.
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .? - [L]
# Rewrite all other queries to the front controller.
RewriteRule .? %{ENV:BASE}/index.php [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ %{ENV:BASE}/index.php [L]
</IfModule>
<IfModule !mod_rewrite.c>
@@ -55,7 +71,7 @@ DirectoryIndex index.php
# When mod_rewrite is not available, we instruct a temporary redirect of
# the start page to the front controller explicitly so that the website
# and the generated links can still be used.
RedirectMatch 302 ^/$ /index.php/
RedirectMatch 307 ^/$ /index.php/
# RedirectTemp cannot be used instead
</IfModule>
</IfModule>
</IfModule>

View File

@@ -1 +0,0 @@
(self.webpackChunkkimai2=self.webpackChunkkimai2||[]).push([[535],{1548:function(i,n,u){u(846)},846:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=1548,i(i.s=n)}]);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(self.webpackChunkkimai2=self.webpackChunkkimai2||[]).push([[113],{2395:function(i,n,u){u(3865)},3865:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=2395,i(i.s=n)}]);

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

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,13 @@
/*!
* @kurkle/color v0.2.1
* @kurkle/color v0.3.2
* https://github.com/kurkle/color#readme
* (c) 2022 Jukka Kurkela
* (c) 2023 Jukka Kurkela
* Released under the MIT License
*/
/*!
* Chart.js v3.9.1
* Chart.js v4.4.2
* https://www.chartjs.org
* (c) 2022 Chart.js Contributors
* (c) 2024 Chart.js Contributors
* Released under the MIT License
*/

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

View File

@@ -2,26 +2,26 @@
"entrypoints": {
"app": {
"js": [
"/build/runtime.f0079159.js",
"/build/app.6450be5e.js"
"/build/runtime.74179306.js",
"/build/app.3611eec9.js"
],
"css": [
"/build/app.3501ccee.css"
"/build/app.c4fc7cc8.css"
]
},
"app-rtl": {
"js": [
"/build/runtime.f0079159.js",
"/build/app-rtl.3d445498.js"
"/build/runtime.74179306.js",
"/build/app-rtl.97153087.js"
],
"css": [
"/build/app-rtl.670658da.css"
"/build/app-rtl.83358c8a.css"
]
},
"export-pdf": {
"js": [
"/build/runtime.f0079159.js",
"/build/export-pdf.d367a32e.js"
"/build/runtime.74179306.js",
"/build/export-pdf.1442bee7.js"
],
"css": [
"/build/export-pdf.d8a6c23b.css"
@@ -29,8 +29,8 @@
},
"invoice": {
"js": [
"/build/runtime.f0079159.js",
"/build/invoice.2604495e.js"
"/build/runtime.74179306.js",
"/build/invoice.7ef8a0c8.js"
],
"css": [
"/build/invoice.5bea118e.css"
@@ -38,8 +38,8 @@
},
"invoice-pdf": {
"js": [
"/build/runtime.f0079159.js",
"/build/invoice-pdf.ce9aace0.js"
"/build/runtime.74179306.js",
"/build/invoice-pdf.6091a528.js"
],
"css": [
"/build/invoice-pdf.2b749265.css"
@@ -47,14 +47,14 @@
},
"chart": {
"js": [
"/build/runtime.f0079159.js",
"/build/chart.f5becfac.js"
"/build/runtime.74179306.js",
"/build/chart.80ee190c.js"
]
},
"calendar": {
"js": [
"/build/runtime.f0079159.js",
"/build/calendar.d45e4af5.js"
"/build/runtime.74179306.js",
"/build/calendar.e52c9d34.js"
],
"css": [
"/build/calendar.bb473428.css"
@@ -62,8 +62,8 @@
},
"dashboard": {
"js": [
"/build/runtime.f0079159.js",
"/build/dashboard.6774a712.js"
"/build/runtime.74179306.js",
"/build/dashboard.10118fb0.js"
],
"css": [
"/build/dashboard.18f5a8b7.css"
@@ -71,21 +71,21 @@
}
},
"integrity": {
"/build/runtime.f0079159.js": "sha384-H22sAW1aTvyIPqvHOvGXWSWTxf0y6mptp+MsVmyXCfjx/WJjBbhX9gbUZ+qIuihV",
"/build/app.6450be5e.js": "sha384-P5Qc8Rav736nZcS2vqM2rBRZpr3bwMsk4SJENBsOMDPdfEjDX7M8WMpwhjzxj0PQ",
"/build/app.3501ccee.css": "sha384-3chwmsldxvcA6ldtsAftB9R2/bSNCi17vSW9ycC0FqLNNM+IdnMorRBXh/zTW1NF",
"/build/app-rtl.3d445498.js": "sha384-D5VYIVdHCPGzAPnMAIkRN1Ri9H4DxdwGts1BgG40JXJuOamq6UQXblaDBSpszOZ0",
"/build/app-rtl.670658da.css": "sha384-Mo5HQ+W9EWDGM1gOyEZ7aScXNltzBMQa4cWC1HtShvs4XPMNUwzOb97jcXf34qSD",
"/build/export-pdf.d367a32e.js": "sha384-Z5baqnzjI636nYFs4g63ViIKBZKRW4Jhv/7PQmTEQlqhfA7eK0vUMUtiyy0R5A9u",
"/build/runtime.74179306.js": "sha384-OC1hTNUXUalKJcvmzrZ0TMCOIwnhxCxgG9dQkbcwR7WcBBCkl2H8bs3giiT2pAwG",
"/build/app.3611eec9.js": "sha384-uBLSZpUnOLWU2TLPGZ0gZDqmiAEa2xrKhWVkHheU+vBjcStIq8ZQg3/iqxLS9Su0",
"/build/app.c4fc7cc8.css": "sha384-icbl3mA5CyeJXjF1YzLObStpJHF5qZ5GdqALtvDYN4KPOCqREwGdFWDu27GYkI+U",
"/build/app-rtl.97153087.js": "sha384-jX7jRUAa8rH29Eg8jLIUKGfGcOT6RBz/P90plXmZPadf2CXKUBdcNGrspaejCHkr",
"/build/app-rtl.83358c8a.css": "sha384-eKnlBDnocNJ68nIc2l4AWCLn3oUEEbsRJoZ1TUP3mii+ToGt9xs08ih8wEmvO+Dq",
"/build/export-pdf.1442bee7.js": "sha384-C6agvjJnQUsMCxaZ/J7dUZU3cpC7uHKI9ZtnfGpRqYbjyw91YLy1oV3iNYgwYyCF",
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
"/build/invoice.2604495e.js": "sha384-D6JvhGSqlx7z72b/qD3nF3QDXPy+XsCSRGtWfs1icjDKOcd2UzuXwuSa/E1Fg2TJ",
"/build/invoice.7ef8a0c8.js": "sha384-z4lZ1Ig3+NPigrRyGPZoff0gG3n5PnCjaDJ73ATnzdUYk0lOCEtNV9fg35VRD0vG",
"/build/invoice.5bea118e.css": "sha384-KNyGdZSTXnAi8Tn2WqrxJnCP18yRK7BrZTHKCAGAEtl5/4S/XAe4IHKpM5gfsmB7",
"/build/invoice-pdf.ce9aace0.js": "sha384-mtZkXrpsXFMX2YvmmTlCm91QgsTlZLzLJnDAFcTuBDtDZ7TNkqLYkfCeLzFLVfwb",
"/build/invoice-pdf.6091a528.js": "sha384-yJz/MDBgBpNHNxF7DuSyFbJN9EL7oDJjraoPRB7VYlHMdb1T0ZfTcI0L8VMDB46U",
"/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7",
"/build/chart.f5becfac.js": "sha384-GSqETm8wULiVXyizvwRompfwu63r/C0Qd/AvrHDE4cqAKiIGCssb3QyBtGu1WN+W",
"/build/calendar.d45e4af5.js": "sha384-wi3/ek3Msn+rk9jebeJnzEXaHUUWlzxkOZwhZyJP5Cg+AKWtolWAXD/hCXQAg0kq",
"/build/chart.80ee190c.js": "sha384-KrQszq859PR0u+KHpGy+RO6K1rcMqCFZx+sNiLVNVZHwEi7RwTXqnYbu8zvfWzUI",
"/build/calendar.e52c9d34.js": "sha384-a6P/nyvUkmPZglNqIfSKEyon791oFKB2+zgfUZHpy1awKi/FBchr5TqeRYupRDQn",
"/build/calendar.bb473428.css": "sha384-900W9o8666Qpw21rLP7hn5Ql8tWd40k0IcE3QpFm7E7V6bKva7xQgGlyzVuuh4GK",
"/build/dashboard.6774a712.js": "sha384-lBwkNqUPv+IBbznagiFakY2BOIueq/Bg89wHO2XeM9YAZ51rPkrvdMd+XmFupHsM",
"/build/dashboard.10118fb0.js": "sha384-pasZR1GYf8/7lMFS9OrsxX8nJ9tSlRYr32fWKUhGoF2mNAGrokLEWdgtbYMRHgFB",
"/build/dashboard.18f5a8b7.css": "sha384-PBD8ftb2yBoSjRe2sN5Xb4dEz045kOEUfcIufdSis+tWoWdAx5j4Yic+F/6CYbrP"
}
}

View File

@@ -0,0 +1 @@
(self.webpackChunkkimai2=self.webpackChunkkimai2||[]).push([[872],{878:function(i,n,u){u(6154)},6154:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=878,i(i.s=n)}]);

View File

@@ -1 +0,0 @@
(self.webpackChunkkimai2=self.webpackChunkkimai2||[]).push([[185],{8810:function(i,n,u){u(9508)},9508:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=8810,i(i.s=n)}]);

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
(self.webpackChunkkimai2=self.webpackChunkkimai2||[]).push([[117],{1555:function(i,n,u){u(8837)},8837:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=1555,i(i.s=n)}]);

View File

@@ -1 +0,0 @@
(self.webpackChunkkimai2=self.webpackChunkkimai2||[]).push([[266],{35:function(i,n,u){u(6197)},6197:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=35,i(i.s=n)}]);

View File

@@ -1 +0,0 @@
(self.webpackChunkkimai2=self.webpackChunkkimai2||[]).push([[149],{474:function(i,n,u){u(9822)},9822:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=474,i(i.s=n)}]);

View File

@@ -0,0 +1 @@
(self.webpackChunkkimai2=self.webpackChunkkimai2||[]).push([[896],{4820:function(i,n,u){u(5650)},5650:function(i,n,u){"use strict";u.r(n)}},function(i){var n;n=4820,i(i.s=n)}]);

View File

@@ -1,24 +1,24 @@
{
"build/app.css": "/build/app.3501ccee.css",
"build/app.js": "/build/app.6450be5e.js",
"build/app-rtl.css": "/build/app-rtl.670658da.css",
"build/app-rtl.js": "/build/app-rtl.3d445498.js",
"build/app.css": "/build/app.c4fc7cc8.css",
"build/app.js": "/build/app.3611eec9.js",
"build/app-rtl.css": "/build/app-rtl.83358c8a.css",
"build/app-rtl.js": "/build/app-rtl.97153087.js",
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
"build/export-pdf.js": "/build/export-pdf.d367a32e.js",
"build/export-pdf.js": "/build/export-pdf.1442bee7.js",
"build/invoice.css": "/build/invoice.5bea118e.css",
"build/invoice.js": "/build/invoice.2604495e.js",
"build/invoice.js": "/build/invoice.7ef8a0c8.js",
"build/invoice-pdf.css": "/build/invoice-pdf.2b749265.css",
"build/invoice-pdf.js": "/build/invoice-pdf.ce9aace0.js",
"build/chart.js": "/build/chart.f5becfac.js",
"build/invoice-pdf.js": "/build/invoice-pdf.6091a528.js",
"build/chart.js": "/build/chart.80ee190c.js",
"build/calendar.css": "/build/calendar.bb473428.css",
"build/calendar.js": "/build/calendar.d45e4af5.js",
"build/calendar.js": "/build/calendar.e52c9d34.js",
"build/dashboard.css": "/build/dashboard.18f5a8b7.css",
"build/dashboard.js": "/build/dashboard.6774a712.js",
"build/runtime.js": "/build/runtime.f0079159.js",
"build/fonts/fa-solid-900.ttf": "/build/fonts/fa-solid-900.bb5be820.ttf",
"build/fonts/fa-brands-400.ttf": "/build/fonts/fa-brands-400.4c05cd9b.ttf",
"build/fonts/fa-solid-900.woff2": "/build/fonts/fa-solid-900.4cbe3db2.woff2",
"build/fonts/fa-brands-400.woff2": "/build/fonts/fa-brands-400.498f4d22.woff2",
"build/fonts/fa-regular-400.ttf": "/build/fonts/fa-regular-400.96e19e1a.ttf",
"build/fonts/fa-regular-400.woff2": "/build/fonts/fa-regular-400.83824cd5.woff2"
"build/dashboard.js": "/build/dashboard.10118fb0.js",
"build/runtime.js": "/build/runtime.74179306.js",
"build/fonts/fa-solid-900.ttf": "/build/fonts/fa-solid-900.bacd5de6.ttf",
"build/fonts/fa-brands-400.ttf": "/build/fonts/fa-brands-400.60127e35.ttf",
"build/fonts/fa-solid-900.woff2": "/build/fonts/fa-solid-900.4d986b00.woff2",
"build/fonts/fa-brands-400.woff2": "/build/fonts/fa-brands-400.455ea818.woff2",
"build/fonts/fa-regular-400.ttf": "/build/fonts/fa-regular-400.eb91f7b9.ttf",
"build/fonts/fa-regular-400.woff2": "/build/fonts/fa-regular-400.21cb8f55.woff2"
}

View File

@@ -1 +1 @@
!function(){"use strict";var e,r={},n={};function t(e){var o=n[e];if(void 0!==o)return o.exports;var i=n[e]={id:e,loaded:!1,exports:{}};return r[e].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}t.m=r,t.amdO={},e=[],t.O=function(r,n,o,i){if(!n){var u=1/0;for(l=0;l<e.length;l++){n=e[l][0],o=e[l][1],i=e[l][2];for(var f=!0,a=0;a<n.length;a++)(!1&i||u>=i)&&Object.keys(t.O).every((function(e){return t.O[e](n[a])}))?n.splice(a--,1):(f=!1,i<u&&(u=i));if(f){e.splice(l--,1);var c=o();void 0!==c&&(r=c)}}return r}i=i||0;for(var l=e.length;l>0&&e[l-1][2]>i;l--)e[l]=e[l-1];e[l]=[n,o,i]},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,{a:r}),r},t.d=function(e,r){for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.hmd=function(e){return(e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:function(){throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},function(){var e={666:0};t.O.j=function(r){return 0===e[r]};var r=function(r,n){var o,i,u=n[0],f=n[1],a=n[2],c=0;if(u.some((function(r){return 0!==e[r]}))){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(a)var l=a(t)}for(r&&r(n);c<u.length;c++)i=u[c],t.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return t.O(l)},n=self.webpackChunkkimai2=self.webpackChunkkimai2||[];n.forEach(r.bind(null,0)),n.push=r.bind(null,n.push.bind(n))}()}();
!function(){"use strict";var e,r={},n={};function t(e){var o=n[e];if(void 0!==o)return o.exports;var i=n[e]={id:e,loaded:!1,exports:{}};return r[e].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}t.m=r,t.amdO={},e=[],t.O=function(r,n,o,i){if(!n){var u=1/0;for(l=0;l<e.length;l++){n=e[l][0],o=e[l][1],i=e[l][2];for(var f=!0,a=0;a<n.length;a++)(!1&i||u>=i)&&Object.keys(t.O).every((function(e){return t.O[e](n[a])}))?n.splice(a--,1):(f=!1,i<u&&(u=i));if(f){e.splice(l--,1);var c=o();void 0!==c&&(r=c)}}return r}i=i||0;for(var l=e.length;l>0&&e[l-1][2]>i;l--)e[l]=e[l-1];e[l]=[n,o,i]},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,{a:r}),r},t.d=function(e,r){for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.hmd=function(e){return(e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:function(){throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},function(){var e={121:0};t.O.j=function(r){return 0===e[r]};var r=function(r,n){var o,i,u=n[0],f=n[1],a=n[2],c=0;if(u.some((function(r){return 0!==e[r]}))){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(a)var l=a(t)}for(r&&r(n);c<u.length;c++)i=u[c],t.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return t.O(l)},n=self.webpackChunkkimai2=self.webpackChunkkimai2||[];n.forEach(r.bind(null,0)),n.push=r.bind(null,n.push.bind(n))}()}();

View File

@@ -4,6 +4,9 @@ use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
// TODO remove once PARTIAL usage was replaced entirely
\Doctrine\Deprecations\Deprecation::ignoreDeprecations('https://github.com/doctrine/orm/issues/8471');
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

View File

@@ -18,7 +18,6 @@ use App\Event\PageActionsEvent;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
@@ -27,7 +26,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
#[Route(path: '/actions')]
#[IsGranted('IS_AUTHENTICATED_REMEMBERED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Actions')]
final class ActionsController extends BaseApiController
{
@@ -73,8 +72,6 @@ final class ActionsController extends BaseApiController
#[OA\Parameter(name: 'view', in: 'path', description: 'View to display the actions at (e.g. index, custom)', required: true)]
#[OA\Parameter(name: 'locale', in: 'path', description: 'Language to translate the action title to (e.g. de, en)', required: true)]
#[Route(methods: ['GET'], path: '/timesheet/{id}/{view}/{locale}', name: 'get_timesheet_actions', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getTimesheetActions(Timesheet $timesheet, string $view, string $locale): Response
{
$event = new PageActionsEvent($this->getUser(), ['timesheet' => $timesheet], 'timesheet', $view);
@@ -93,8 +90,6 @@ final class ActionsController extends BaseApiController
#[OA\Parameter(name: 'view', in: 'path', description: 'View to display the actions at (e.g. index, custom)', required: true)]
#[OA\Parameter(name: 'locale', in: 'path', description: 'Language to translate the action title to (e.g. de, en)', required: true)]
#[Route(methods: ['GET'], path: '/activity/{id}/{view}/{locale}', name: 'get_activity_actions', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getActivityActions(Activity $activity, string $view, string $locale): Response
{
$event = new PageActionsEvent($this->getUser(), ['activity' => $activity], 'activity', $view);
@@ -113,8 +108,6 @@ final class ActionsController extends BaseApiController
#[OA\Parameter(name: 'view', in: 'path', description: 'View to display the actions at (e.g. index, custom)', required: true)]
#[OA\Parameter(name: 'locale', in: 'path', description: 'Language to translate the action title to (e.g. de, en)', required: true)]
#[Route(methods: ['GET'], path: '/project/{id}/{view}/{locale}', name: 'get_project_actions', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getProjectActions(Project $project, string $view, string $locale): Response
{
$event = new PageActionsEvent($this->getUser(), ['project' => $project], 'project', $view);
@@ -133,8 +126,6 @@ final class ActionsController extends BaseApiController
#[OA\Parameter(name: 'view', in: 'path', description: 'View to display the actions at (e.g. index, custom)', required: true)]
#[OA\Parameter(name: 'locale', in: 'path', description: 'Language to translate the action title to (e.g. de, en)', required: true)]
#[Route(methods: ['GET'], path: '/customer/{id}/{view}/{locale}', name: 'get_customer_actions', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getCustomerActions(Customer $customer, string $view, string $locale): Response
{
$event = new PageActionsEvent($this->getUser(), ['customer' => $customer], 'customer', $view);

View File

@@ -24,7 +24,6 @@ use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Request\ParamFetcherInterface;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Nelmio\ApiDocBundle\Annotation\Security as ApiSecurity;
use OpenApi\Attributes as OA;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
@@ -34,7 +33,7 @@ use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/activities')]
#[IsGranted('IS_AUTHENTICATED_REMEMBERED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Activity')]
final class ActivityController extends BaseApiController
{
@@ -44,10 +43,10 @@ final class ActivityController extends BaseApiController
public const GROUPS_RATE = ['Default', 'Entity', 'Activity_Rate'];
public function __construct(
private ViewHandlerInterface $viewHandler,
private ActivityRepository $repository,
private EventDispatcherInterface $dispatcher,
private ActivityRateRepository $activityRateRepository
private readonly ViewHandlerInterface $viewHandler,
private readonly ActivityRepository $repository,
private readonly EventDispatcherInterface $dispatcher,
private readonly ActivityRateRepository $activityRateRepository
) {
}
@@ -56,8 +55,6 @@ final class ActivityController extends BaseApiController
*/
#[OA\Response(response: 200, description: 'Returns a collection of activities', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/ActivityCollection')))]
#[Route(methods: ['GET'], path: '', name: 'get_activities')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\QueryParam(name: 'project', requirements: '\d+', strict: true, nullable: true, description: 'Project ID to filter activities')]
#[Rest\QueryParam(name: 'projects', map: true, requirements: '\d+', strict: true, nullable: true, default: [], description: 'List of project IDs to filter activities, e.g.: projects[]=1&projects[]=2')]
#[Rest\QueryParam(name: 'visible', requirements: '1|2|3', default: 1, strict: true, nullable: true, description: 'Visibility status to filter activities: 1=visible, 2=hidden, 3=all')]
@@ -65,13 +62,13 @@ final class ActivityController extends BaseApiController
#[Rest\QueryParam(name: 'orderBy', requirements: 'id|name|project', strict: true, nullable: true, description: 'The field by which results will be ordered. Allowed values: id, name, project (default: name)')]
#[Rest\QueryParam(name: 'order', requirements: 'ASC|DESC', strict: true, nullable: true, description: 'The result order. Allowed values: ASC, DESC (default: ASC)')]
#[Rest\QueryParam(name: 'term', description: 'Free search term')]
#[Rest\QueryParam(name: 'page', requirements: '\d+', strict: true, nullable: true, description: 'The page to display, renders a 404 if not found (default: 1)')]
#[Rest\QueryParam(name: 'size', requirements: '\d+', strict: true, nullable: true, description: 'The amount of entries for each page (default: 50)')]
public function cgetAction(ParamFetcherInterface $paramFetcher, ProjectRepository $projectRepository): Response
{
/** @var User $user */
$user = $this->getUser();
$query = new ActivityQuery();
$query->setCurrentUser($user);
$query->loadTeams();
$this->prepareQuery($query, $paramFetcher);
$order = $paramFetcher->get('order');
if (\is_string($order) && $order !== '') {
@@ -112,9 +109,9 @@ final class ActivityController extends BaseApiController
$query->setSearchTerm(new SearchTerm($term));
}
$query->setIsApiCall(true);
$data = $this->repository->getActivitiesForQuery($query);
$view = new View($data, 200);
$pagination = $this->repository->getPagerfantaForQuery($query);
$view = $this->createPaginatedView($pagination);
$view->getContext()->setGroups(self::GROUPS_COLLECTION);
return $this->viewHandler->handle($view);
@@ -126,8 +123,7 @@ final class ActivityController extends BaseApiController
#[OA\Response(response: 200, description: 'Returns one activity entity', content: new OA\JsonContent(ref: '#/components/schemas/ActivityEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Activity ID to fetch', required: true)]
#[Route(methods: ['GET'], path: '/{id}', name: 'get_activity', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[IsGranted('view', 'activity')]
public function getAction(Activity $activity): Response
{
$view = new View($activity, 200);
@@ -142,8 +138,6 @@ final class ActivityController extends BaseApiController
#[OA\Post(description: 'Creates a new activity and returns it afterwards', responses: [new OA\Response(response: 200, description: 'Returns the new created activity', content: new OA\JsonContent(ref: '#/components/schemas/ActivityEntity'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ActivityEditForm'))]
#[Route(methods: ['POST'], path: '', name: 'post_activity')]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postAction(Request $request): Response
{
if (!$this->isGranted('create_activity')) {
@@ -185,8 +179,6 @@ final class ActivityController extends BaseApiController
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ActivityEditForm'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Activity ID to update', required: true)]
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_activity', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function patchAction(Request $request, Activity $activity): Response
{
$event = new ActivityMetaDefinitionEvent($activity);
@@ -222,8 +214,6 @@ final class ActivityController extends BaseApiController
#[OA\Response(response: 200, description: 'Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.', content: new OA\JsonContent(ref: '#/components/schemas/ActivityEntity'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Activity record ID to set the meta-field value for', required: true)]
#[Route(methods: ['PATCH'], path: '/{id}/meta', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Rest\RequestParam(name: 'name', strict: true, nullable: false, description: 'The meta-field name')]
#[Rest\RequestParam(name: 'value', strict: true, nullable: false, description: 'The meta-field value')]
public function metaAction(Activity $activity, ParamFetcherInterface $paramFetcher): Response
@@ -255,8 +245,6 @@ final class ActivityController extends BaseApiController
#[OA\Response(response: 200, description: 'Returns a collection of activity rate entities', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/ActivityRate')))]
#[OA\Parameter(name: 'id', in: 'path', description: 'The activity whose rates will be returned', required: true)]
#[Route(methods: ['GET'], path: '/{id}/rates', name: 'get_activity_rates', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function getRatesAction(Activity $activity): Response
{
$rates = $this->activityRateRepository->getRatesForActivity($activity);
@@ -274,8 +262,6 @@ final class ActivityController extends BaseApiController
#[OA\Delete(responses: [new OA\Response(response: 204, description: 'Returns no content: 204 on successful delete')])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The activity whose rate will be removed', required: true)]
#[OA\Parameter(name: 'rateId', in: 'path', description: 'The rate to remove', required: true)]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
#[Route(methods: ['DELETE'], path: '/{id}/rates/{rateId}', name: 'delete_activity_rate', requirements: ['id' => '\d+', 'rateId' => '\d+'])]
public function deleteRateAction(Activity $activity, #[MapEntity(mapping: ['rateId' => 'id'])] ActivityRate $rate): Response
{
@@ -298,8 +284,6 @@ final class ActivityController extends BaseApiController
#[OA\Parameter(name: 'id', in: 'path', description: 'The activity to add the rate for', required: true)]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/ActivityRateForm'))]
#[Route(methods: ['POST'], path: '/{id}/rates', name: 'post_activity_rate', requirements: ['id' => '\d+'])]
#[ApiSecurity(name: 'apiUser')]
#[ApiSecurity(name: 'apiToken')]
public function postRateAction(Activity $activity, Request $request): Response
{
$rate = new ActivityRate();

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\API\Authentication;
use App\Repository\AccessTokenRepository;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
final class AccessTokenHandler implements AccessTokenHandlerInterface
{
public function __construct(
private readonly AccessTokenRepository $accessTokenRepository
)
{
}
public function getUserBadgeFrom(string $accessToken): UserBadge
{
$accessToken = $this->accessTokenRepository->findByToken($accessToken);
if (null === $accessToken) {
throw new BadCredentialsException('Invalid credentials.');
}
if (!$accessToken->isValid()) {
throw new BadCredentialsException('Invalid token.');
}
$now = new \DateTimeImmutable();
// record last usage only if this is the first time OR once every minute
if ($accessToken->getLastUsage() === null || $now->getTimestamp() > $accessToken->getLastUsage()->getTimestamp() + 60) {
$accessToken->setLastUsage($now);
$this->accessTokenRepository->saveAccessToken($accessToken);
}
return new UserBadge($accessToken->getUser()->getUserIdentifier(), fn (string $userIdentifier) => $accessToken->getUser());
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\API\Authentication;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
final class AccessTokenSuccessHandler implements AuthenticationSuccessHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token): ?Response
{
$token->setAttribute('api-token', true);
return null;
}
}

View File

@@ -16,16 +16,35 @@ final class ApiRequestMatcher implements RequestMatcherInterface
{
public function matches(Request $request): bool
{
if (str_contains($request->getRequestUri(), '/api/doc')) {
// we do not want to handle URLs that are not in the API scope
if (!str_starts_with($request->getRequestUri(), '/api/')) {
return false;
}
if (str_contains($request->getRequestUri(), '/api/')) {
// API documentation is only available to registered and logged-in users
if (str_starts_with($request->getRequestUri(), '/api/doc')) {
return false;
}
return !$request->headers->has(SessionAuthenticator::HEADER_JAVASCRIPT) &&
$request->headers->has(TokenAuthenticator::HEADER_USERNAME) &&
$request->headers->has(TokenAuthenticator::HEADER_TOKEN);
// ------------------------------------------------------------------------------------
// the next two checks are primarily here to make sure to return proper error messages
// let's use this firewall if a Bearer token is set in the header
// other cases like "bearer" are rejected earlier
if (($auth = $request->headers->get('Authorization')) !== null && str_starts_with($auth, 'Bearer ')) {
return true;
}
// let's use this firewall if the deprecated username & token combination is available
if ($request->headers->has(TokenAuthenticator::HEADER_USERNAME) &&
$request->headers->has(TokenAuthenticator::HEADER_TOKEN)) {
return true;
}
// ------------------------------------------------------------------------------------
// checking for a previous session allows us to skip the API firewall and token access handler
// we simply re-use the existing session when doing API calls from the frontend.
// it is not necessary to check headers. if there is no valid session, we should always use this firewall
return !$request->hasPreviousSession();
}
}

View File

@@ -1,69 +0,0 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\API\Authentication;
use Scheb\TwoFactorBundle\Security\Http\Authenticator\TwoFactorAuthenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
final class SessionAuthenticator extends AbstractAuthenticator
{
public const HEADER_JAVASCRIPT = 'X-AUTH-SESSION';
public function __construct(private TokenAuthenticator $authenticator)
{
}
public function createToken(Passport $passport, string $firewallName): TokenInterface
{
$token = parent::createToken($passport, $firewallName);
// this should not be necessary, as /api/ is excluded from 2FA process, but just to make sure this
// authenticator never triggers 2FA, we add the attribute to the token
// https://symfony.com/bundles/SchebTwoFactorBundle/6.x/custom_conditions.html
$token->setAttribute(TwoFactorAuthenticator::FLAG_2FA_COMPLETE, true);
return $token;
}
public function supports(Request $request): bool
{
if (str_contains($request->getRequestUri(), '/api/')) {
// API docs can only be access, when the user is logged in
if (str_contains($request->getRequestUri(), '/api/doc')) {
return false;
}
return !$request->headers->has(self::HEADER_JAVASCRIPT);
}
return false;
}
public function authenticate(Request $request): Passport
{
return $this->authenticator->authenticate($request);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return $this->authenticator->onAuthenticationSuccess($request, $token, $firewallName);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{
return $this->authenticator->onAuthenticationFailure($request, $exception);
}
}

View File

@@ -29,14 +29,25 @@ final class TokenAuthenticator extends AbstractAuthenticator
public const HEADER_USERNAME = 'X-AUTH-USER';
public const HEADER_TOKEN = 'X-AUTH-TOKEN';
public function __construct(private ApiUserRepository $userProvider, private PasswordHasherFactoryInterface $passwordHasherFactory)
public function __construct(
private readonly ApiUserRepository $userProvider,
private readonly PasswordHasherFactoryInterface $passwordHasherFactory
)
{
}
public function supports(Request $request): bool
{
if (str_contains($request->getRequestUri(), '/api/')) {
return !str_contains($request->getRequestUri(), '/api/doc');
if (str_contains($request->getRequestUri(), '/api/doc')) {
return false;
}
if ($request->headers->has(self::HEADER_USERNAME) && $request->headers->has(self::HEADER_TOKEN)) {
@trigger_error('You are using deprecated API access, please upgrade your APP to use API tokens instead.', E_USER_DEPRECATED);
return true;
}
}
return false;

View File

@@ -13,6 +13,7 @@ use App\Entity\User;
use App\Repository\Query\BaseQuery;
use App\Timesheet\DateTimeFactory;
use App\Utils\Pagination;
use FOS\RestBundle\Request\ParamFetcherInterface;
use FOS\RestBundle\View\View;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
@@ -52,6 +53,46 @@ abstract class BaseApiController extends AbstractController
return DateTimeFactory::createByUser($user);
}
protected function prepareQuery(BaseQuery $query, ParamFetcherInterface $paramFetcher): void
{
$query->setIsApiCall(true);
$query->setCurrentUser($this->getUser());
// there is no function has() in ParamFetcherInterface, so we need to use all() and check for the key
$all = $paramFetcher->all(true);
if (\array_key_exists('page', $all)) {
$page = $all['page'];
if (is_numeric($page)) {
$query->setPage((int) $page);
}
}
if (\array_key_exists('size', $all)) {
$size = $all['size'];
if (is_numeric($size)) {
$query->setPageSize((int) $size);
}
}
if (\array_key_exists('pageSize', $all)) {
$size = $all['pageSize'];
if (is_numeric($size)) {
$query->setPageSize((int) $size);
}
}
}
protected function createPaginatedView(Pagination $pagination): View
{
$results = (array) $pagination->getCurrentPageResults();
$view = new View($results, 200);
$this->addPagination($view, $pagination);
return $view;
}
protected function addPagination(View $view, Pagination $pagination): void
{
$view->setHeader('X-Page', (string) $pagination->getCurrentPage());

Some files were not shown because too many files have changed in this diff Show More