Compare commits

...

222 Commits

Author SHA1 Message Date
Kevin Papst
0420eb27c7 random improvements (#5506)
* fix order of destroying form elements
* API documentation - fixes #1949
* make command path independent
* use PhpSubprocess to execute migrations
* allow to configure optional SAML attributes
* fix deprecation
* unify wording of exported state - fixes #5392
2025-05-28 13:34:01 +02:00
Kevin Papst
81107377f4 Relax time input format requirements (#5504)
* remove locale support from time-input, only allow 12 and 24 hour format
* added blur listeners to parse all kinds of time inputs
* fix duration inputs like ":9" for 9 minutes
2025-05-28 09:36:29 +02:00
Kevin Papst
e9c172daea improve API endpoint names (#5502) 2025-05-27 13:23:08 +02:00
Kevin Papst
f42d42f512 fix searchterm parsing (#5501) 2025-05-27 13:20:27 +02:00
Weblate (bot)
b38c6fbb0e Translated using Weblate (#5498)
Co-authored-by: Maksim_220 Кабанов <law820314@gmail.com>
2025-05-27 13:15:55 +02:00
Kevin Papst
e2a1146670 Use arrow keys to change duration (#5495) 2025-05-27 13:07:29 +02:00
Kevin Papst
e69ac6968b Improved Locales (#5497)
- Switch Chinese time format to 24-hours - fixes #5496
- Use always 4 chars for year
- Use one `HH` instead of `H`, so we don't have to convert during runtime
- Update all locales
- Allow seeing time and date format in help UI
- Fix RTL in UI
- Show stats about found formats in Locales command
2025-05-26 20:59:39 +02:00
Weblate (bot)
87168de4f1 Translated using Weblate (#5484)
Co-authored-by: Piotr Laszczkowski <swistach@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
2025-05-24 22:59:07 +02:00
Kevin Papst
7b158d57b5 API improvements (#5494)
* hide logo in docs UI
* always return a user in api base controller
* added example for duration fields
2025-05-24 22:09:48 +02:00
Kevin Papst
1e0fbf0b73 Release 2.35 (#5470)
* open up API for plugins by removing internal
* use constants in entity column definition
* simplified entity management API
* bump packages
* allow installing assets and run database migrations independently
* bump to apidoc-bundle 5
* do not duplicate http method in API operationId
* new security entries in Open API definition
* changed API UI provider for Swagger to Stoplight, improved endpoint titles, hide internal endpoints
* deactivate swagger json endpoint
2025-05-24 14:28:39 +02:00
Weblate (bot)
6e26a37c4d Translated using Weblate (#5471)
Co-authored-by: LordTenebrous <lordtenebrous@users.noreply.hosted.weblate.org>
Co-authored-by: Mandeep <mandeeps708@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Piotr Laszczkowski <swistach@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
2025-05-18 14:57:19 +02:00
Weblate (bot)
fac5a910d8 Translated using Weblate (#5462)
Co-authored-by: Harshit Sethi <hstsethi@outlook.com>
Co-authored-by: Henry Higgins <leserboka@outlook.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
2025-05-09 14:30:29 +02:00
Kevin Papst
dfd97fd6f3 Release 2.34 (#5465)
* fix timing issue in timesheet edit form with deactivated rounding
* bump packages
* only show update messages for newer plugin versions
* replace deprecated method
* remove internal from API
* remove technical terms from translation
* prevent calls to internal symfony methods
* helper method to flag entry as modified
* support meta-fields in weekly-hourse view
* fix running timesheets were deleted in weekly-hourse
2025-05-09 14:22:47 +02:00
Kevin Papst
452a8d9390 improved search with negation (#5453) 2025-05-05 18:18:00 +02:00
Weblate (bot)
2a9de506a1 Translated using Weblate (#5451)
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: John Titor <utkin2007@gmail.com>
Co-authored-by: Lucas <merlin.lucas99@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Sotski Eugene <jekakmail@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
2025-05-03 11:40:11 +02:00
Kevin Papst
e663cc2b02 Release 2.33.0 (#5438) 2025-04-22 20:43:22 +02:00
Weblate (bot)
a70c803158 Translated using Weblate (#5440)
Co-authored-by: C. H <them4z@gmail.com>
Co-authored-by: GiannosOB <giannos2105@gmail.com>
Co-authored-by: Kuwush <79j0hancarl@gmail.com>
Co-authored-by: Raphael-11 <raef.laffi@medtech.tn>
Co-authored-by: fran secs <fransecs@gmail.com>
2025-04-22 10:36:44 +02:00
Weblate (bot)
9ca69e1114 Translated using Weblate (#5414)
Co-authored-by: Fajar Shiddiq <justgamers0102@gmail.com>
Co-authored-by: Hien <hienly@yandex.com>
Co-authored-by: Marc Sánchez Díaz <msanchez@aporta.biz>
Co-authored-by: Marc-Daniel DALEBA <marcodxv10@gmail.com>
Co-authored-by: Tachibana Saza <tachibanasaza@proton.me>
Co-authored-by: gacarel <gacarel657@bariswc.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
2025-04-06 11:36:12 +02:00
Kevin Papst
2e6b700b43 Release 2.32 (#5411)
* bump packages
* dynamic invoice options
* make sure that invoice previews can be detected
* support for mpdf associated files
* do not include any future times in work contract calculation
* re-add username column in Excel spreadsheet
* deactivate internal rate editing
* show if plugin update exists
* shorten name to Kimai only, without Time-Tracking
* remove check for existing id in work contract
* fix metafield already defined in search
* helper methods to unlock months
* new translation
* send event on unlock month
2025-04-06 09:53:48 +02:00
Kevin Papst
2a75cd6230 random improvements (#5382)
* remove permission check, as own timesheets should always be visible
* new methods to create datetime
* allow access to user roles in javascript
* support class for dropdown actions
* allow to edit internal rate
* support human readable duration in export via user configuration
* allow to order timesheet listing by user, exported and billable field
* bump codecov action
2025-03-13 18:00:50 +01:00
Weblate (bot)
934fdeb107 Translated using Weblate (#5383)
Co-authored-by: Dawid Kiełkowski <dawid55a@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Xyruz <alphxyruz@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: hugompd <hugompd@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
2025-03-12 18:26:08 +01:00
Kevin Papst
14cdcd3f63 Release 2.31 (#5372)
* simplify translation
* bump version
* deprecate translations
* pass date-range as argument to export and timesheet filter URL from monthly overview report
* added class for use in responsive screens
* show technical role name
* simplify multi-update title
* more statistic models
* bump composer packages
2025-02-27 17:41:25 +01:00
Weblate (bot)
674bf3a6b5 Translated using Weblate (#5371)
Co-authored-by: Vág Csaba <vagcsaba@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
2025-02-27 17:22:17 +01:00
Kevin Papst
cce49cc409 Translation cleanup (#5370)
* use correct translation key
* de-duplicate translation
2025-02-21 20:50:49 +01:00
Weblate (bot)
04cb403ea9 Translated using Weblate (#5369)
Co-authored-by: Jussi Juven <jussi.juven@disec.fi>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Kristoffer Grundström <swedishsailfishosuser@tutanota.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
2025-02-21 20:50:23 +01:00
Kevin Papst
afaa845a9a fix export dates are not localized (#5368) 2025-02-20 15:07:28 +01:00
Weblate (bot)
928b5208e9 Translated using Weblate (#5362)
Co-authored-by: Kristoffer Grundström <swedishsailfishosuser@tutanota.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
2025-02-20 15:04:53 +01:00
Kevin Papst
8a8d0503f7 prepare break time field (#5366) 2025-02-20 14:46:20 +01:00
Kevin Papst
b341358d0a Release 2.30 (#5345)
- added missing InvoiceTemplate company, title) field validator
- graceful fallback for missing working-contract mode
- improve email test command (use configured MAIL_FROM)
- additional form types for simple usage in SystemConfiguration and UserPreferences
- allow to extend the working time query via event
2025-02-17 08:32:22 +01:00
Weblate (bot)
d30166e691 Translated using Weblate (#5344)
Co-authored-by: 3limssmile <33elimssmile@gmail.com>
Co-authored-by: Alfredo Sola <alfredo@sola.es>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Serhii Horichenko <serhii@horichenko.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: vaykly <vaykly@icloud.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
2025-02-17 08:31:54 +01:00
Kevin Papst
b42c77a2a1 Release 2.29 (#5325)
* bump composer packages
* fixes #5329 quotes for ANSI_MODE
* improve year selection
* improve year selection via dropdown
* added range selector in month-picker
* fix week number if week starts with sunday
* fix first day of month in URL
* predefined options for week chooser
* z-index issue with sticky table header
* replace duplicated translations
* add logout button to allow user switch without having to re-login in "remember me" login
* new flag to detect if invoice entry is a fixed rate
* improve export column lengths
2025-02-09 00:16:03 +01:00
Weblate (bot)
8444928ae4 Translated using Weblate (#5342)
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Serhii Horichenko <serhii@horichenko.com>
2025-02-08 19:29:33 +01:00
Weblate (bot)
b250edc2a1 Translated using Weblate (#5318)
Co-authored-by: Antonín JUDYTKA <judytka@judytka.cz>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Lukáš Granatier <lukas.granatier@compacer.cz>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: on the road simson <d-youtube@kopaszhegy.hu>
Co-authored-by: polarwood <polarwood@users.noreply.hosted.weblate.org>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
2025-02-08 19:27:49 +01:00
Kevin Papst
4a8c2a79c9 added last months and last quarters to daterange-picker selections (#5317) 2025-01-24 15:21:54 +01:00
Weblate (bot)
bff72b30cc Translations update from Hosted Weblate (#5306)
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: TaaviLepik <info@brunex.ee>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
2025-01-24 13:37:03 +01:00
Kevin Papst
9d7b418d99 fix date column in export (#5308) 2025-01-20 16:09:29 +01:00
Kevin Papst
f2fb338539 Release 2.28 (#5253)
* fix year in dashboard
* make batch actions accessible via javascript
* bump packages
* remove BOM from CSV
* rebuild assets
* fix duplicated automated-email warning
2025-01-18 01:49:16 +01:00
Weblate (bot)
1b3ff212d2 Translated using Weblate (#5293)
Co-authored-by: Evgeniy Khramov <65224669+thejenja@users.noreply.github.com>
Co-authored-by: John Titor <utkin2007@gmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
2025-01-18 01:48:48 +01:00
Maarten Becker
c07979e3a9 fix usage of activity api globals parameter (#5284) 2025-01-10 10:02:14 +01:00
Weblate (bot)
23e8dcf495 Translated using Weblate (added Tamil) (#5254)
Co-authored-by: Biscuittttt <biscuitwithtea.tall310@passinbox.com>
Co-authored-by: Carlos Carreras <mytriponlinux@gmail.com>
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: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: abdelbasset jabrane <ribago9317@cubene.com>
Co-authored-by: yblis <yblisss@yahoo.fr>
Co-authored-by: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
2025-01-09 12:16:45 +01:00
Kevin Papst
4332ef95a2 Release 2.27 (#5212) 2024-12-22 22:50:42 +01:00
Weblate (bot)
4fdfb6f478 Translations update from Hosted Weblate (#5211)
Co-authored-by: Dawid <dawidgorski.m@gmail.com>
Co-authored-by: Florent Berthelot <florentius.b@gmail.com>
Co-authored-by: Giorgos Skafidas <giorgos@skafidas.online>
Co-authored-by: Kamborio <Kamborio15@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: Serhii Horichenko <serhii@horichenko.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: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: BouenMarsh <mveretsuk@yandex.ru>
2024-12-22 19:15:32 +01:00
Kevin Papst
136104d4b0 faster spreadsheet exporter based on opensout and other export improvements (#5238) 2024-12-22 18:36:47 +01:00
Kevin Papst
c7f0508707 Upgrade tests to PhpUnit 10 (#5252) 2024-12-22 01:25:30 +01:00
buti1021
9bd37fb695 update php versions (#5246) 2024-12-20 14:56:06 +01:00
Kevin Papst
21c031f2c8 export filtered timesheets without additional search form (#5234) 2024-12-15 18:38:30 +01:00
Kevin Papst
17a815e5a9 updated frontend builds (#5210)
* do not rely on node_modules path
* bump eslint to v9, run eslint via npm task, remove from build task
* loosen dependencies and update all packages
* rebuild assets with latest frontend packages
* bump webpack encore and dependencies
* bump to latest stable yarn
* explicitly mention dependencies
2024-12-06 14:31:04 +01:00
Kevin Papst
648686c001 added open collective 2024-12-06 14:27:06 +01:00
Kevin Papst
82a3b99a31 Release 2.26 (#5189)
* bring back deprecated methods
* bump packages
* fix SAML redirect
* config flag for break times
* use class constant instead of string in attributes
* throw if all tags were not found - fixes #4792
2024-12-05 10:42:07 +01:00
Weblate (bot)
70741eebfd Translations update from Hosted Weblate (#5192)
Co-authored-by: Lassi Määttä <lassi.maetta@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
2024-12-05 09:42:37 +01:00
Weblate (bot)
eb933e7021 Translations update from Hosted Weblate (#5185)
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
2024-11-30 14:26:07 +01:00
Weblate (bot)
372f3c8507 Translations update from Hosted Weblate (#5179)
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Serhii Horichenko <m@sgg.im>
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 <kevin@kevinpapst.de>
Co-authored-by: Doctorredits_here <alkaf.alkaf2018@tutamail.com>
2024-11-27 15:35:26 +01:00
Kevin Papst
e030ff08db API endpoints to delete customer/project/activity (#5181)
* added service methods with events to delete customer, project, activity
* added API endpoints to delete customer, project, activity
* added tests for new API endpoints
2024-11-27 15:25:13 +01:00
Kevin Papst
f13b81ede7 Query hints & persistent cache for latest approvals (#5176) 2024-11-25 21:04:53 +01:00
Weblate (bot)
46c4449504 Translations update from Hosted Weblate (#5172)
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Serhii Horichenko <m@sgg.im>
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 <kevin@kevinpapst.de>
2024-11-25 21:04:22 +01:00
Kevin Papst
8b55cd3693 trigger form change upon copy-data event 2024-11-25 20:46:34 +01:00
Kevin Papst
406ac7b9cb allow PHP 8.4 (#5173) 2024-11-21 23:00:00 +01:00
Kevin Papst
0c26a2678e Release 2.25 (#5109) 2024-11-21 22:44:49 +01:00
Weblate (bot)
49eb7068c9 Translated using Weblate (#5130)
Co-authored-by: Adrien N <adriennathaniel1999@gmail.com>
Co-authored-by: Igor Coimbra Carvalheira <igorccarvalheira111@gmail.com>
Co-authored-by: John Titor <utkin2007@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org>
Co-authored-by: Serhii Horichenko <m@sgg.im>
Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
2024-11-18 13:52:50 +01:00
Kevin Papst
dcc52f1a95 API begin and end fields for Admins (#5134) 2024-10-25 10:47:58 +02:00
Weblate (bot)
31bae44f3c Translated using Weblate (#5120)
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
2024-10-20 22:30:35 +02:00
Kevin Papst
84a0ca5ce6 removed duplicate installation call 2024-10-14 23:37:45 +02:00
Kevin Papst
9e736b26f6 Improve docker installation (#5115)
* ignore certain connection errors on startup
* remove invalid code that checks for the existence of the migration table
* fetch kimai code via tar archive instead of git clone, to respect .gitattributes
2024-10-14 23:09:14 +02:00
Weblate (bot)
32a1306394 Translated using Weblate (#5110)
Co-authored-by: Dklfajsjfi49wefklsf32 <nlincus@users.noreply.hosted.weblate.org>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
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: Максим Горпиніч <mgorpinic2005@gmail.com>
2024-10-14 21:46:28 +02:00
Kevin Papst
96043afd6a better support for installing plugins via composer (#5112)
* merge installation and update commands
* generate metadata from array
* new command to list available packages
* added a management script to simplify updates
* added directory for dev files
* helper functions for installation and listing of packages
* run plugin database installers
2024-10-14 21:44:42 +02:00
Kevin Papst
255c7d77d6 Ignore files from showing up in a release (#5111)
* moved email css files to template directory
* prevent developer files from showing up in a release
2024-10-12 23:45:13 +02:00
Weblate (bot)
e6d5a720bf Translations update from Hosted Weblate (#5098)
Co-authored-by: Lukáš Kaňka <lukas.kanka@outlook.cz>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2024-10-11 22:33:57 +02:00
Kevin Papst
ff9bf163ee Release 2.24 (#5097) 2024-10-11 21:56:54 +02:00
Kevin Papst
5fac6a642c Docker improvements (#5096)
* install missing unzip
* run update and install in one step
* remove unused TRUSTED_HOSTS env
2024-10-11 19:36:40 +02:00
Kevin Papst
b1b81ee8a6 remove dev dependencies from prod image 2024-10-04 17:35:25 +02:00
Kevin Papst
1ea64bf18e allow to manually run workflow 2024-10-04 17:03:31 +02:00
Kevin Papst
0c4fdda00a allow to manually run workflow 2024-10-04 17:01:25 +02:00
Kevin Papst
215bd6a01a allow to manually run workflow 2024-10-04 16:57:48 +02:00
Kevin Papst
63b8164926 install missing buildx 2024-10-04 16:45:51 +02:00
Weblate (bot)
8c26ff9aed Translated using Weblate (#5092)
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
2024-10-04 11:24:50 +02:00
Kevin Papst
921a703ea2 Merge Docker images (#5094) 2024-10-04 11:14:10 +02:00
Kevin Papst
855a546683 Docker improvements (#5093) 2024-10-04 10:25:46 +02:00
Kevin Papst
52b949daa3 cleanup docker environment variables (#5074) 2024-10-03 14:27:33 +02:00
Weblate (bot)
ee040d29b6 Translations update from Hosted Weblate (#5079)
Co-authored-by: Doctorredits_here <alkaf.alkaf2018@tutamail.com>
Co-authored-by: Hien <hienly@yandex.com>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: Roman Ondráček <mail@romanondracek.cz>
Co-authored-by: Tito Tapiola <ikirouta013@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-10-03 10:38:48 +02:00
Kevin Papst
fb9a0dc499 Release 2.23.0 (#5075) 2024-10-03 10:34:20 +02:00
Kevin Papst
40154be8f9 Upgrade Tabler frontend to 1.0.21 (#5066) 2024-09-23 18:31:33 +02:00
Kevin Papst
13649e146c Improve pagination support in API (#5073) 2024-09-22 17:26:02 +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
Weblate (bot)
e293d62aa1 Translated using Weblate (#4658)
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: DOHEX <xuyaohui617@outlook.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: Oğuz Ersen <oguz@ersen.moe>
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: Сергій <sergiy.goncharuk.1@gmail.com>
2024-02-22 18:00:51 +01:00
Kevin Papst
f016221e8e Fixes for 1.12 (#4655)
* fix missing locales
* bump composer packages
* changed translations
* assert timezone on customer
* include user accountNumber in excel export
* improve next customer number calculation
* fix z-index of contextmenu / action dropdown
2024-02-22 13:03:09 +01:00
Kevin Papst
36c578452e show top nav links as buttons (#4633) 2024-02-12 17:50:22 +01:00
Kevin Papst
6e643ee99c revert to classical bootstrap form layout (#4632) 2024-02-12 17:35:30 +01:00
Kevin Papst
58cbf1776f Weekly hours improvements (#4631)
* fix table padding
* reduced minimum quick-entry recent activity row amount
* use activity favorites in weekly hours
2024-02-12 14:20:17 +01:00
Kevin Papst
85a16a9363 Release 2.12 (#4609) 2024-02-07 23:47:25 +01:00
Kevin Papst
7abe787778 added project filter in user-list reports (#4615) 2024-02-07 18:00:29 +01:00
Weblate (bot)
e98732f44a Translated using Weblate (#4614)
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
2024-02-06 16:06:40 +01:00
Kevin Papst
c2afebbb5e Fix Doctrine deprecations (#4608) 2024-02-02 15:48:11 +01:00
Kevin Papst
49e69d1ae3 Release 2.11 (#4580) 2024-02-02 13:52:24 +01:00
Andreas Brain
a9a32c83ac Add library to re-enable TLS certificate verification for LDAP connections (#4598) 2024-02-02 13:46:40 +01:00
Weblate (bot)
64fa67f15d Translated using Weblate (#4587)
Co-authored-by: Alex Terlan <terlan2012@gmail.com>
Co-authored-by: Bruno Paré-Simard <bruno.p@brunops.com>
Co-authored-by: Bálint László <blaszlobors@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: 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: abdelbasset jabrane <ribago9317@cubene.com>
Co-authored-by: delvani <inavleb@users.noreply.hosted.weblate.org>
2024-02-02 13:44:25 +01:00
Kevin Papst
df3ca9d5a9 Split user language (UI translation) from locale (formatted values) (#4595) 2024-01-30 00:09:53 +01:00
Kevin Papst
12ef19df28 support default time on date-time-picker (#4579) 2024-01-21 01:59:30 +01:00
Kevin Papst
a5b4a92086 new phpstan version 2024-01-21 00:28:03 +01:00
Kevin Papst
3aaaf9ffc0 bump packages 2024-01-20 02:12:26 +01:00
Weblate (bot)
3f73f2c570 Translated using Weblate (#4576)
Co-authored-by: Bálint László <blaszlobors@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Saveliy <jitropolit@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: yangyangdaji <1504305527@qq.com>
2024-01-20 01:27:25 +01:00
Kevin Papst
46739795ff Release 2.10 (#4549) 2024-01-19 12:05:07 +01:00
Kevin Papst
afd60a67f6 merged and removed small translation files (#4572)
* merged about with messages
* merged invoice-numbergenerator with messages
2024-01-16 23:39:13 +01:00
Weblate (bot)
bb325888a1 Translated using Weblate (#4551)
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Miloš Havlíček <milos.havlicek@cothema.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: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: сэр Аноним <mcptminei@gmail.com>
2024-01-16 23:16:46 +01:00
Kevin Papst
fdda9841fa allow to edit username (#4559) 2024-01-14 13:02:44 +01:00
Ronald Derksen
5acd33dd64 Make 'pagebreak' optional in pdf layout exporter (#4561) 2024-01-13 14:44:31 +01:00
Kevin Papst
6531e7fe52 Release 2.9.0 (#4526)
* added fix to work around bc break in new phpword version
* fix phpoffice deprecations
* mark unused option as deprecated
* support for DateTimeInterface and DateTimeImmutable where possible
* use TRUSTED_PROXIES setting - fixes #4533
* re-enable Kimai test in docker test build script (#4541)
* bump dependencies
2024-01-10 12:43:07 +01:00
Weblate (bot)
c6a651456e Translated using Weblate (#4531)
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com>
2024-01-09 23:53:56 +01:00
Kevin Papst
8eeb9c120a Release 2.8.0 (#4508) 2024-01-03 00:28:35 +01:00
Janne Heß
d8f89d9f3d release-drafter: adapt supported PHP versions (#4512) 2024-01-03 00:25:24 +01:00
Simon Schaufelberger
26a89725eb fix wrong default protocol for customer homepage (#4514) 2024-01-02 15:20:24 +01:00
Weblate (bot)
9f0165ef4d Translated using Weblate (#4517)
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com>
2024-01-01 21:05:29 +01:00
Kevin Papst
3d0a35f0cd bump checkout action version 2023-12-27 02:10:46 +01:00
Kevin Papst
c5dcf9e0fd replace deprecated set-output with environment usage, deactivate broken test-lite 2023-12-26 17:18:08 +01:00
Kevin Papst
ad645f5b58 Release 2.7.0 (#4506)
* added api URL for simpler integration
* allow to request password change upon next login
* make ModifiedAt timesheet independent
* improved plugin api
* replace kernel calls with AutoconfigureTag and TaggedIterator attributes
* new translation keys (e.g. for days)
* support setting min and max date on date and daterange pickers
2023-12-26 14:49:11 +01:00
Weblate (bot)
f86eca05ae Translated using Weblate (#4507)
Co-authored-by: Alan Aparecido da coneicao <alanaparecidodaconeicao@gmail.com>
Co-authored-by: Balanda Nazarii <balaraz@tuta.io>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
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: Nikolay Soloshin <nigiriits@users.noreply.hosted.weblate.org>
Co-authored-by: Petal <Petalzu@outlook.com>
Co-authored-by: Thomas <jzh20180312@163.com>
Co-authored-by: Vipul Hangadiya <hangadiyavipul@gmail.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>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2023-12-26 01:32:18 +01:00
Kevin Papst
ac2a6ba2c8 use a full mysql database URL as default (#4504) 2023-12-25 23:41:13 +01:00
Christian Schupfner
e23d0474fd Upgrade Docker to PHP 8.2 (#4476)
- base image changed to PHP 8.2
- base images changed to bookworm
- libicu63 upgraded to libicu72
2023-12-17 17:05:38 +01:00
Kevin Papst
2edcbcca50 sort invoice items by date before passing to template (#4495) 2023-12-17 16:50:47 +01:00
Kevin Papst
5f4b6d3cfa Release 2.6.0 (#4472)
- Added: calendar entry title combination for customer, project and activity
- Added: show not_invoiced and not_exported data in detail screens
- Added: force logout if user is disabled
- Added: reduced amount of database queries on several screens
- Fixed: open-close status on work-contract screen for users without configuration
- Fixed: failsafe order/orderBy in query if manually manipulated to be null
- Fixed: unify statistic calculation (not_invoiced and not_exported) across screens
- Tech: bump packages
- Tech: Symfony 6.4
2023-12-17 16:19:50 +01:00
Kevin Papst
af82fd040d removed deprecated test script (#4490) 2023-12-14 12:12:17 +01:00
Kevin Papst
e0769bc3dc allow PHP 8.3 (#4471)
Fix use of range() and auto-width columns for Excel exports
2023-12-01 22:40:02 +01:00
Weblate (bot)
8fd2e7029e Translated using Weblate (#4466)
Co-authored-by: EngageIndo <engageindo@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Shimpei <sfutami0821@gmail.com>
Co-authored-by: albanobattistella <albano_battistella@hotmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: nigiriits <i79046290200@gmail.com>
Co-authored-by: nigiriits <nigiriits@users.noreply.hosted.weblate.org>
Co-authored-by: xorly <patrikbachan@gmail.com>
2023-12-01 22:38:32 +01:00
Kevin Papst
f570b5506a added missing translations 2023-12-01 18:33:09 +01:00
Kevin Papst
0e1bd818bf bump packages 2023-12-01 16:42:51 +01:00
Kevin Papst
02bcd45116 Release 2.5.0 (#4454)
- added command to list users
- corrected wrong german translation
- work contract validations
- prevent error with missing params
- use collapsible, minor UI improvement
- added classes to target menu buttons in custom rules
- disable webpack notifier, incompatible with mac arm
- use explicit menu service to generate menu
- bump to fontawesome 6 and replace restart icon
- change repeat icon for recent activities
- moved user bookmarks (favorites) to top nav
- fix totp seconds window (leeway)
- new migration to fix remaining user preferences with dots in name
- remove duplicate named column in user screen
- unify and added translations
- added missing filter and tags to InvoiceSecurity
2023-12-01 11:38:17 +01:00
Kevin Papst
16d4a691f8 Fix LDAP with internal users (#4453)
* added logging, in case Laminas libs are not available
* make sure that internal users can still login if LDAP is activated
2023-11-20 21:33:36 +01:00
Kevin Papst
20164295f8 Release 2.4.0 (#4427)
* button to duplicate old timesheets, even those from lockdown period
* bump composer packages
* fixes tooltip remains in view #4426
* fix js error if all widgets were removed
* allow to open timesheet edit dialog from export listing
* css classes for timesheet context menu, so they can be hidden
* added flag to force a user to set password upon login
* enable lazy-ghost-objects to fix deprecation
* change user, username, internal_rate export column labels
* helper method to find user by displayname
* log improvements and format changes
* deactivate broken schema validation
2023-11-19 16:05:43 +01:00
Weblate (bot)
9d8e5ecd7a Translated using Weblate (#4447)
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Matti O <matt1@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: Patrick Kartschewski <weblate@patrick-kartschewski.de>
Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Victor Hitriy <pvnhome@yandex.ru>
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: gallegonovato <fran-carro@hotmail.es>
2023-11-19 16:01:35 +01:00
Kevin Papst
8a1059fdd1 added docker build and publish workflow (#4446) 2023-11-17 14:37:32 +01:00
Kevin Papst
92c6922afb fix LDAP issues and allow to migrate from local to LDAP account (#4445) 2023-11-17 14:37:00 +01:00
Kevin Papst
2fb9ea9239 bump packages 2023-11-17 13:22:50 +01:00
Toby Batch
350875be2a moved dockerfile to main repo (#4393) 2023-11-17 13:07:51 +01:00
Kevin Papst
6a828cc9bd Release 2.3.0 (#4412)
- move user-preferences to edit submenu
- unify user preferences with other forms
- use light fieldset for all user-edit-forms
- support offcanvas elements
- allow to dynamically inject toolbar buttons (via plugins)
- use interface for formatting dates
- improve docker issue template
- bump version and composer packages
2023-11-08 22:30:22 +01:00
Weblate (bot)
7034066de9 Translated using Weblate (#4346)
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Benjamin Alan Jamie <benjamin@weblate.org>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Den SE <dense800@gmail.com>
Co-authored-by: EngageIndo <engageindo@gmail.com>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Liam Azofra <liamazofra@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Prefill add-on <noreply-addon-prefill@weblate.org>
Co-authored-by: Serhii Horichenko <m@sgg.im>
Co-authored-by: Singh <spamio@protonmail.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: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: yangyangdaji <1504305527@qq.com>
Co-authored-by: مهدی امیری راد <mahdiamererad@gmail.com>
2023-11-07 12:41:57 +01:00
Kevin Papst
2a9bdb7982 fix locale definitions starting with AM/PM (#4408)
* adapt locale script to fix locales starting with AM/PM
* fix korean locales that start with AM / PM
* added missing uk and zh_Hant locales
2023-11-07 12:27:11 +01:00
Kevin Papst
a1d9874c13 added role cards to permission screen (#4401)
* simplify creating new roles by auto-replacing input
2023-11-04 20:17:05 +01:00
Kevin Papst
0b1625175b remove link to external docker repo 2023-11-03 00:06:19 +01:00
Kevin Papst
0f6a6e7561 Update docker.yml (#4395) 2023-11-03 00:05:21 +01:00
Kevin Papst
7bbd497ef1 change URL to support landing page 2023-11-02 23:55:42 +01:00
Kevin Papst
0b3f645bda added docker bug report template 2023-11-02 23:54:29 +01:00
Kevin Papst
c840fee911 Release 2.2.1 (#4389)
* added missing functions to invoice security policy
2023-11-01 01:16:17 +01:00
1915 changed files with 63131 additions and 44348 deletions

20
.docker/000-default.conf Normal file
View File

@@ -0,0 +1,20 @@
<VirtualHost *:8001>
ServerAdmin webmaster@localhost
DocumentRoot /opt/kimai/public
PassEnv MAILER_FROM
PassEnv APP_ENV
PassEnv APP_SECRET
PassEnv DATABASE_URL
PassEnv MAILER_URL
PassEnv TRUSTED_PROXIES
<Directory "/opt/kimai/public">
Require all granted
DirectoryIndex index.php
AllowOverride All
</Directory>
</VirtualHost>
ServerName localhost

51
.docker/dbtest.php Normal file
View File

@@ -0,0 +1,51 @@
<?php
$DATABASE_HOST = urldecode($argv[1]);
$DATABASE_BASE = urldecode($argv[2]);
$DATABASE_PORT = $argv[3];
$DATABASE_USER = urldecode($argv[4]);
$DATABASE_PASS = urldecode($argv[5]);
echo "Testing DB:";
try {
$pdo = new \PDO("mysql:host=$DATABASE_HOST;dbname=$DATABASE_BASE;port=$DATABASE_PORT", "$DATABASE_USER", "$DATABASE_PASS", [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
]);
} catch(\Exception $ex) {
switch ($ex->getCode()) {
case 1045:
// we can immediately stop here and show the error message
echo 'Access denied (1045)';
die(1);
case 1049:
// error "Unknown database (1049)" can be ignored, the database will be created by Kimai
return;
// a lot of errors share the same meaningless error code zero
case 0:
// this error includes the database name, so we can only search for the static part of the error message
if (stripos($ex->getMessage(), 'SQLSTATE[HY000] [1049] Unknown database') !== false) {
// error "Unknown database (1049)" can be ignored, the database will be created by Kimai
return;
}
switch ($ex->getMessage()) {
// eg. no response (fw) - the startup script should retry it a couple of times
case 'SQLSTATE[HY000] [2002] Operation timed out':
echo 'Operation timed out (0-2002)';
die(4);
// special case "localhost" with a stopped db server (should not happen in docker compose setup)
case 'SQLSTATE[HY000] [2002] No such file or directory':
echo 'Connection could not be established (0-2002)';
die(5);
// using IP with stopped db server - the startup script should retry it a couple of times
case 'SQLSTATE[HY000] [2002] Connection refused':
echo 'Connection refused (0-2002)';
die(5);
}
echo $ex->getMessage() . " (0)";
die(7);
default:
// unknown error
echo $ex->getMessage() . " (?)";
die(10);
}
}

102
.docker/entrypoint.sh Executable file
View File

@@ -0,0 +1,102 @@
#!/bin/bash -x
KIMAI=$(cat /opt/kimai/version.txt)
echo $KIMAI
function waitForDB() {
# Parse sql connection data
DATABASE_USER=$(awk -F '[/:@]' '{print $4}' <<< "$DATABASE_URL")
DATABASE_PASS=$(awk -F '[/:@]' '{print $5}' <<< "$DATABASE_URL")
DATABASE_HOST=$(awk -F '[/:@]' '{print $6}' <<< "$DATABASE_URL")
DATABASE_PORT=$(awk -F '[/:@]' '{print $7}' <<< "$DATABASE_URL")
DATABASE_BASE=$(awk -F '[/?]' '{print $4}' <<< "$DATABASE_URL")
re='^[0-9]+$'
if ! [[ $DATABASE_PORT =~ $re ]] ; then
DATABASE_PORT=3306
fi
echo "Wait for database connection ..."
until php /dbtest.php "$DATABASE_HOST" "$DATABASE_BASE" "$DATABASE_PORT" "$DATABASE_USER" "$DATABASE_PASS"; do
echo Checking DB: $?
sleep 3
done
echo "Connection established"
}
function handleStartup() {
# set mem limits and copy in custom logger config
if [ -z "$memory_limit" ]; then
memory_limit=512M
fi
sed -i "s/memory_limit.*/memory_limit=$memory_limit/g" /usr/local/etc/php/php.ini
cp /assets/monolog.yaml /opt/kimai/config/packages/monolog.yaml
if [ -z "$USER_ID" ]; then
USER_ID=$(id -u www-data)
fi
if [ -z "$GROUP_ID" ]; then
GROUP_ID=$(id -g www-data)
fi
# if group doesn't exist
if grep -w "$GROUP_ID" /etc/group &>/dev/null; then
echo Group already exists
else
echo www-kimai:x:"$GROUP_ID": >> /etc/group
grpconv
fi
# if user doesn't exist
if id "$USER_ID" &>/dev/null; then
echo User already exists
else
echo www-kimai:x:"$USER_ID":"$GROUP_ID":www-kimai:/var/www:/usr/sbin/nologin >> /etc/passwd
pwconv
fi
if [ -e /use_apache ]; then
export APACHE_RUN_USER=$(id -nu "$USER_ID")
# This doesn't _exactly_ run as the specified GID, it runs as the GID of the specified user but WTF
export APACHE_RUN_GROUP=$(id -ng "$USER_ID")
export APACHE_PID_FILE=/var/run/apache2/apache2.pid
export APACHE_RUN_DIR=/var/run/apache2
export APACHE_LOCK_DIR=/var/lock/apache2
export APACHE_LOG_DIR=/var/log/apache2
export LANG=C
elif [ -e /use_fpm ]; then
sed -i "s/user = .*/user = $USER_ID/g" /usr/local/etc/php-fpm.d/www.conf
sed -i "s/group = .*/group = $GROUP_ID/g" /usr/local/etc/php-fpm.d/www.conf
echo "Setting fpm to run as ${USER_ID}:${GROUP_ID}"
else
echo "Error, unknown server type"
fi
}
function prepareKimai() {
# These are idempotent, so we can run them on every start-up
/opt/kimai/bin/console -n kimai:install
if [ ! -z "$ADMINPASS" ] && [ ! -a "$ADMINMAIL" ]; then
/opt/kimai/bin/console kimai:user:create admin "$ADMINMAIL" ROLE_SUPER_ADMIN "$ADMINPASS"
fi
echo "$KIMAI" > /opt/kimai/var/installed
echo "Kimai is ready"
}
function runServer() {
# Just while I'm fixing things
/opt/kimai/bin/console kimai:reload --env="$APP_ENV"
chown -R $USER_ID:$GROUP_ID /opt/kimai/var
if [ -e /use_apache ]; then
exec /usr/sbin/apache2 -D FOREGROUND
elif [ -e /use_fpm ]; then
exec php-fpm
else
echo "Error, unknown server type"
fi
}
waitForDB
handleStartup
prepareKimai
runServer

48
.docker/monolog.yaml Normal file
View File

@@ -0,0 +1,48 @@
when@prod:
monolog:
channels: ["deprecation"]
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [403, 404]
nested:
type: stream
level: info
path: php://stderr
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
channels: ["deprecation"]
path: php://stderr
when@dev:
monolog:
channels: ["deprecation"]
handlers:
main:
type: stream
path: php://stderr
level: info
channels: ["!event"]
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
deprecation:
type: stream
channels: ["deprecation"]
path: php://stderr
when@test:
monolog:
handlers:
main:
type: stream
path: php://stderr
level: info
channels: ["!event"]

View File

@@ -1,11 +0,0 @@
module.exports = {
env: {
"browser": true,
"node": true,
"es6": true,
"amd": true,
},
parser: '@babel/eslint-parser',
extends: ['eslint:recommended'],
ignorePatterns: ["assets/*.js"],
}

18
.gitattributes vendored Normal file
View File

@@ -0,0 +1,18 @@
.docker export-ignore
.github export-ignore
assets export-ignore
tests export-ignore
.codecov.yml export-ignore
.editorconfig export-ignore
eslint.config.js export-ignore
.gitattributes export-ignore
.gitignore export-ignore
php-cs-fixer.dist.php export-ignore
babel.config.js export-ignore
package.json export-ignore
php-cs-fixer.sh export-ignore
phpstan.neon export-ignore
phpstan.sh export-ignore
phpunit.xml.dist export-ignore
webpack.config.js export-ignore
yarn.lock export-ignore

3
.github/FUNDING.yml vendored
View File

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

View File

@@ -53,12 +53,11 @@ body:
attributes:
label: Which PHP version are you using?
options:
- "8.4"
- "8.2"
- "8.3"
- "8.1"
- Unknown
- "8.2"
- "8.0"
- "7.4"
- "7.3"
- Other (please mention below)
validations:
required: true

View File

@@ -3,12 +3,9 @@ contact_links:
- name: Read our documentation
url: https://www.kimai.org/documentation/
about: Save your time! There is an instant solution for many issues in the documentation.
- name: Docker image issue tracker
url: https://github.com/tobybatch/kimai2/issues
about: For issues specific for Kimai Docker deployment
- name: Ask a Question
url: https://github.com/kimai/kimai/discussions
about: Want to discuss something with a community? Do it in discussions!
- name: Get Professional support
url: https://www.kimai.org/store/custom-plugins.html
about: As a customer, you will always receive fast and helpful replies from the developer.
url: https://www.kimai.org/en/support.html
about: As a customer, you will always receive fast replies from the developer.

40
.github/ISSUE_TEMPLATE/docker.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Docker issue
description: Experiencing an issue with your Docker Setup?
labels: [ "docker" ]
body:
- type: markdown
attributes:
value: |
Thank you for reporting an issue with your docker setup! This form will guide you to create a useful issue report.
- type: textarea
id: describe
attributes:
label: Describe the problem
description: >
Please provide a clear and concise description of what the issue is and the steps to reproduce the behaviour:
1. Start the container '...'
2. Click on '....'
3. See error xyz
validations:
required: true
- type: textarea
id: environment
attributes:
label: Describe your setup and add your Docker compose file (redact your credentials)
description: >
Your working environment:
- OS: [e.g. Linux, Windows, Mac]
- Docker version: [e.g. 19.03.2]
- Docker compose version: [e.g. 1.21.0]
Docker compose file:
version: '3.5'
services:
image: ...
validations:
required: true
- type: input
id: command
attributes:
label: Command used to run the container
description: e.g. docker run -v ....

View File

@@ -1,17 +1,5 @@
name-template: '$RESOLVED_VERSION'
tag-template: '$RESOLVED_VERSION'
categories:
- title: 'Enhancements'
labels:
- 'feature request'
- 'technical debt'
- 'translation'
- title: 'Fixed bugs'
labels:
- 'bug'
- title: 'Infrastructure'
labels:
- 'infrastructure'
exclude-labels:
- 'duplicate'
- 'invalid'
@@ -19,6 +7,7 @@ exclude-labels:
- 'release'
exclude-contributors:
- 'dependabot'
- 'weblate'
change-template: '- $TITLE (#$NUMBER)'
change-title-escapes: '\<*_&`#@'
version-resolver:
@@ -33,10 +22,8 @@ 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 and 8.2**
**Compatible with PHP 8.1 to 8.4**
$CHANGES
Involved in this release: $CONTRIBUTORS - thank you!
Involved in this release: $CONTRIBUTORS

View File

@@ -1,16 +1,93 @@
name: 'Docker Build'
on:
release:
types: [released]
workflow_dispatch:
inputs:
kimai_tag:
description: 'Kimai tag to build'
required: true
release:
types: [released]
jobs:
build:
name: Trigger docker image build
runs-on: ubuntu-latest
steps:
- name: Emit repository_dispatch
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.DOCKER_ACCESS_TOKEN }}
repository: tobybatch/kimai2
event-type: kimai_release
build:
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: Determine version
run: |
input="${{ github.event.inputs.kimai_tag }}"
# Determine between manual trigger and release event
if [ -z "$input" ]; then
echo "Using release tag: ${{ github.event.release.tag_name }}"
version="${{ github.event.release.tag_name }}"
else
echo "Using tag provided: $input"
version="$input"
fi
if [[ ! $version =~ ^2\.(0|[1-9]*)(0?)\.(0|[0-9]*)(0?)$ ]]; then
echo "Invalid version number: $version"
exit 1
fi
echo "kimai_version=$version" >> $GITHUB_ENV
- name: FPM image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile
build-args: |
KIMAI=${{ env.kimai_version }}
BASE=fpm
target: prod
platforms: linux/amd64,linux/arm64
tags: |
kimai/kimai2:latest
kimai/kimai2:fpm
kimai/kimai2:fpm-${{ env.kimai_version }}
push: true
- name: Apache image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile
build-args: |
KIMAI=${{ env.kimai_version }}
BASE=apache
target: prod
platforms: linux/amd64,linux/arm64
tags: |
kimai/kimai2:apache
kimai/kimai2:apache-${{ env.kimai_version }}
push: true
- name: Development image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile
build-args: |
KIMAI=${{ env.kimai_version }}
BASE=apache
target: dev
platforms: linux/amd64,linux/arm64
tags: |
kimai/kimai2:dev
push: true

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

@@ -11,7 +11,7 @@ jobs:
steps:
- name: Clone Kimai
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
persist-credentials: false
@@ -19,5 +19,5 @@ jobs:
uses: xalvarez/prevent-file-change-action@v1
with:
githubToken: ${{ secrets.GITHUB_TOKEN }}
pattern: .*.lock
pattern: .*\.lock$
trustedAuthors: kevinpapst, dependabot

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

@@ -19,13 +19,13 @@ jobs:
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
matrix:
php: ['8.1', '8.2']
php: ['8.1', '8.2', '8.3', '8.4']
name: Integration (${{ matrix.php }})
steps:
- name: Clone Kimai
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
persist-credentials: false
@@ -39,12 +39,15 @@ jobs:
env:
fail-fast: true
- name: Determine software versions
run: mysql --version && php --version
- name: Determine composer cache directory
id: composer-cache
run: echo "composer_cache_directory=$(composer config cache-dir)" >> $GITHUB_ENV
- name: Cache Composer dependencies
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: "${{ env.composer_cache_directory }}"
key: ${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
@@ -53,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
@@ -79,21 +82,29 @@ jobs:
- name: Run quick unit-tests
run: composer tests-unit
env:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
APP_ENV: dev
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
- name: Full test-suite
if: matrix.php != '8.2'
run: vendor/bin/phpunit tests/
env:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?charset=utf8mb4&serverVersion=8.0.35
APP_ENV: test
MAILER_URL: null://localhost
- name: Full test-suite with coverage
if: matrix.php == '8.2'
run: vendor/bin/phpunit tests/ --coverage-clover=coverage.xml
env:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
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.1'
uses: codecov/codecov-action@v3
if: matrix.php == '8.2'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
@@ -106,7 +117,7 @@ jobs:
bin/console doctrine:migrations:migrate -n
bin/console doctrine:migrations:migrate first -n
env:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
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

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

13
.gitignore vendored
View File

@@ -2,8 +2,7 @@
/public/.htaccess
/.env-*
/.idea/
/.DS_Store
/phpstan.sh
.DS_Store
# custom apache rules e.g. to deactivate ioncube loader
/public/.user.ini
@@ -19,15 +18,15 @@
# for keeping empty directories
/config/packages/local.yaml
/config/bundles-local.php
/var/dev/*
/var/data/*
/var/cache/*
/var/invoices*
/var/export/*
/var/export*
/var/log/*
/var/sessions/*
/var/packages/*
/var/plugins/*
/var/plugins_old/
/var/plugins*
/var/plugins/*/*.disabled
###> symfony/framework-bundle ###
@@ -60,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,
@@ -42,6 +43,7 @@ $fixer
'single_line_after_imports' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'php_unit_method_casing' => true,
'array_syntax' => [
'syntax' => 'short'
],
@@ -60,7 +62,7 @@ $fixer
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'native_function_casing' => true,
'new_with_braces' => true,
'new_with_parentheses' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_comment' => true,
@@ -82,7 +84,7 @@ $fixer
'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_around_offset' => true,
'no_trailing_comma_in_singleline' => true,
'no_unneeded_curly_braces' => true,
'no_unneeded_braces' => true,
'no_unneeded_final_method' => true,
'no_unused_imports' => true,
'no_whitespace_before_comma_in_array' => true,
@@ -151,7 +153,7 @@ $fixer
],
'scope' => 'namespaced'
],
'native_function_type_declaration_casing' => true,
'native_type_declaration_casing' => true,
'no_alias_functions' => [
'sets' => [
'@internal'
@@ -170,6 +172,7 @@ $fixer
->setFinder(
PhpCsFixer\Finder::create()
->in([
__DIR__ . '/migrations/',
__DIR__ . '/src/',
__DIR__ . '/tests/',
])

View File

@@ -1,15 +1,16 @@
# Contributing
Kimai is an open source project, contributions made by the community are welcome.
Kimai is an open source project, contributions made by the community are welcome.
But we can only accept contributions with a signed CLA (Contributor License Agreement) to prevent issues in the future (you will see a link when opening a PR).
Send your ideas, code reviews, pull requests and feature requests to help to improve this project.
## Pull request rules
- Make your changes in a new git branch, based on the latest code in `main`
- Apply our code-style by running `composer codestyle-fix`
- Run the static code analysis with `composer phpstan`
- Verify everything still works with `composer tests-unit` and `composer tests-integration`
- Verify everything still works with `composer tests-unit`
- Add tests for your changes
- When sending in a PR, you must accept that your contributions/code will be published under MIT license (see the [LICENSE](LICENSE) file as well), otherwise your PR will be closed
- If one of the PR checks/builds fails, fix it before asking for a review
Further documentation can be found in the [developer documentation](https://www.kimai.org/documentation/developers.html).

330
Dockerfile Normal file
View File

@@ -0,0 +1,330 @@
# _ ___ _
# | |/ (_)_ __ ___ __ _(_)
# | ' /| | '_ ` _ \ / _` | |
# | . \| | | | | | | (_| | |
# |_|\_\_|_| |_| |_|\__,_|_|
#
# Kimai images for:
# - plain PHP FPM (kimai/kimai2:fpm)
# - Apache with PHP (kimai/kimai2:apache)
# - Development (kimai/kimai2:dev)
# ---------------------------------------------------------------------
# For local testing by maintainer:
#
# docker build --no-cache -t kimai-fpm --build-arg BASE=fpm .
# docker build --no-cache -t kimai-apache --build-arg BASE=apache .
# docker run -d --name kimai-apache-app kimai-apache
# docker exec -ti kimai-apache-app /bin/bash
# ---------------------------------------------------------------------
# Official PHP images: https://hub.docker.com/_/php/
# https://github.com/docker-library/docs/blob/master/php/README.md#supported-tags-and-respective-dockerfile-links
# Pass-through Arguments: https://benkyriakou.com/posts/docker-args-empty
# Best practices: https://docs.docker.com/build/building/best-practices/
# ---------------------------------------------------------------------
# Source base, one of: fpm, apache
ARG BASE="fpm"
# Kimai branch/tag to run
ARG KIMAI="main"
# Timezone for images
ARG TIMEZONE="Europe/Berlin"
###########################
# Shared tools
###########################
# composer base image
FROM composer:latest AS composer
###########################
# PHP extensions
###########################
# fpm alpine php extension base
FROM php:8.3-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:8.3-apache-bookworm AS apache-php-ext-base
RUN apt-get update && \
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 opcache
FROM ${BASE}-php-ext-base AS php-ext-opcache
RUN docker-php-ext-install -j$(nproc) opcache
###########################
# fpm base build
###########################
FROM php:8.3-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 && \
sed -i "s/;ping.path/ping.path/g" /usr/local/etc/php-fpm.d/www.conf && \
sed -i "s/;access.suppress_path\[\] = \/ping/access.suppress_path\[\] = \/ping/g" /usr/local/etc/php-fpm.d/www.conf
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:8.3-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 \
unzip && \
echo "Listen 8001" > /etc/apache2/ports.conf && \
a2enmod rewrite && \
touch /use_apache
COPY .docker/000-default.conf /etc/apache2/sites-available/000-default.conf
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 php-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
# 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-20230831/xsl.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/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-20230831/pdo_mysql.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/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-20230831/zip.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/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-20230831/ldap.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/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-20230831/gd.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/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-20230831/intl.so /usr/local/lib/php/extensions/no-debug-non-zts-20230831/intl.so
# PHP extension opcache
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
###########################
# fetch Kimai sources
###########################
FROM alpine:latest AS git-prod
ARG KIMAI
ARG TIMEZONE
# the convention in the Kimai repository is: tags are always version numbers, branch names always start with a letter
# if the KIMAI variable starts with a number (e.g. 2.24.0) we assume its a tag, otherwise its a branch
RUN [[ $KIMAI =~ ^[0-9] ]] && export REF='tags' || export REF='heads' && \
wget -O "/opt/kimai.tar.gz" "https://github.com/kimai/kimai/archive/refs/${REF}/${KIMAI}.tar.gz" && \
tar -xpzf /opt/kimai.tar.gz -C /opt/ && \
mv /opt/kimai-${KIMAI} /opt/kimai
###########################
# global base build
###########################
FROM php-base AS base
ARG KIMAI
ARG TIMEZONE
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"
ENV KIMAI=${KIMAI}
ENV TIMEZONE=${TIMEZONE}
RUN ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo ${TIMEZONE} > /etc/timezone && \
mkdir -p /composer && \
chown -R www-data:www-data /composer
# copy startup script & DB checking script
COPY .docker/dbtest.php /dbtest.php
COPY .docker/entrypoint.sh /entrypoint.sh
ENV DATABASE_URL="mysql://kimai:kimai@127.0.0.1:3306/kimai?charset=utf8mb4&serverVersion=8.3"
ENV APP_SECRET=change_this_to_something_unique
# The default container name for nginx is nginx
ENV TRUSTED_PROXIES=nginx,localhost,127.0.0.1
ENV MAILER_FROM=kimai@example.com
ENV MAILER_URL=null://localhost
ENV ADMINPASS=
ENV ADMINMAIL=
ENV USER_ID=
ENV GROUP_ID=
# default values to configure composer behavior
ENV COMPOSER_MEMORY_LIMIT=-1
ENV COMPOSER_ALLOW_SUPERUSER=1
VOLUME [ "/opt/kimai/var" ]
CMD [ "/entrypoint.sh" ]
###########################
# final builds
###########################
# development build
FROM base AS dev
# copy kimai develop source
COPY --from=git-prod --chown=www-data:www-data /opt/kimai /opt/kimai
COPY .docker /assets
# do the composer deps installation
RUN \
export COMPOSER_HOME=/composer && \
composer --no-ansi install --working-dir=/opt/kimai --optimize-autoloader && \
composer --no-ansi clearcache && \
composer --no-ansi require --working-dir=/opt/kimai laminas/laminas-ldap && \
cp /usr/local/etc/php/php.ini-development /usr/local/etc/php/php.ini && \
chown -R www-data:www-data /opt/kimai /usr/local/etc/php/php.ini && \
mkdir -p /opt/kimai/var/logs && chmod 777 /opt/kimai/var/logs && \
sed "s/128M/-1/g" /usr/local/etc/php/php.ini-development > /opt/kimai/php-cli.ini && \
sed -i "s/env php/env -S php -c \/opt\/kimai\/php-cli.ini/g" /opt/kimai/bin/console && \
/opt/kimai/bin/console kimai:version | awk '{print $2}' > /opt/kimai/version.txt
ENV APP_ENV=dev
ENV DATABASE_URL=
ENV memory_limit=512M
# the "prod" stage (production build) is configured as last stage in the file, as this is the default target in BuildKit
FROM base AS prod
# copy kimai production source
COPY --from=git-prod --chown=www-data:www-data /opt/kimai /opt/kimai
COPY .docker /assets
# do the composer deps installation
RUN \
export COMPOSER_HOME=/composer && \
composer --no-ansi install --working-dir=/opt/kimai --no-dev --optimize-autoloader && \
composer --no-ansi clearcache && \
composer --no-ansi require --update-no-dev --working-dir=/opt/kimai laminas/laminas-ldap && \
cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && \
sed -i "s/expose_php = On/expose_php = Off/g" /usr/local/etc/php/php.ini && \
sed -i "s/;opcache.enable=1/opcache.enable=1/g" /usr/local/etc/php/php.ini && \
sed -i "s/;opcache.memory_consumption=128/opcache.memory_consumption=256/g" /usr/local/etc/php/php.ini && \
sed -i "s/;opcache.interned_strings_buffer=8/opcache.interned_strings_buffer=24/g" /usr/local/etc/php/php.ini && \
sed -i "s/;opcache.max_accelerated_files=10000/opcache.max_accelerated_files=100000/g" /usr/local/etc/php/php.ini && \
sed -i "s/opcache.validate_timestamps=1/opcache.validate_timestamps=0/g" /usr/local/etc/php/php.ini && \
sed -i "s/session.gc_maxlifetime = 1440/session.gc_maxlifetime = 604800/g" /usr/local/etc/php/php.ini && \
mkdir -p /opt/kimai/var/logs && chmod 777 /opt/kimai/var/logs && \
sed "s/128M/-1/g" /usr/local/etc/php/php.ini-development > /opt/kimai/php-cli.ini && \
chown -R www-data:www-data /opt/kimai /usr/local/etc/php/php.ini && \
/opt/kimai/bin/console kimai:version | awk '{print $2}' > /opt/kimai/version.txt
ENV APP_ENV=prod
ENV DATABASE_URL=
ENV memory_limit=512M

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,21 +36,20 @@ Do **NOT** use Version 1, it won't get any more updates!
### Requirements
- PHP 8.1 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`
## 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.
@@ -63,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.
@@ -81,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,10 @@
# 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).

24
UPGRADING-3.md Normal file
View File

@@ -0,0 +1,24 @@
# Upgrading Kimai - Version 3.x
_Make sure to create a backup before you start!_
Read the [updates documentation](https://www.kimai.org/documentation/updates.html) to find out how you can upgrade your Kimai installation to the latest stable release.
Check below if there are more version specific steps required, which need to be executed after the normal update process.
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
## 3.0
**!! This release requires minimum PHP version 8.4 !!**
### Developer
Removed translations:
- `action.edit`: use `edit` instead
- `my.profile`: use `user_profile` instead
- `stats.userAmountToday`: use `` instead
- `stats.userAmountWeek`: use `` instead
- `stats.userAmountMonth`: use `` instead
- `stats.userAmountYear`: use `` instead
- `stats.userAmountTotal`: use `` instead
- `update_multiple`

View File

@@ -1,4 +1,4 @@
# Upgrading Kimai - Version2.x
# Upgrading Kimai - Version 2.x
_Make sure to create a backup before you start!_
@@ -10,7 +10,7 @@ Perform EACH version specific task between your version and the new one, otherwi
## [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)

0
assets/.gitignore vendored
View File

1
assets/app-rtl.js Normal file
View File

@@ -0,0 +1 @@
require('./sass/_app-rtl.scss');

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

@@ -154,7 +154,7 @@ export default class KimaiPlugin {
const width = Math.max(
document.documentElement.clientWidth,
window.innerWidth || 0
)
);
return width < 576;
}

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

@@ -29,7 +29,7 @@ export default class KimaiCopyDataForm extends KimaiFormPlugin {
/**
* @param {HTMLFormElement} form
*/
activateForm(form) // eslint-disable-line no-unused-vars
activateForm(form)
{
if (this._eventHandler === undefined) {
this._eventHandler = (event) => {
@@ -48,10 +48,10 @@ export default class KimaiCopyDataForm extends KimaiFormPlugin {
if (element.dataset.event !== undefined) {
for (const event of element.dataset.event.split(' ')) {
target.dispatchEvent(new Event(event));
}
} else if (element.dataset.eventBubbles !== undefined) {
for (const event of element.dataset.eventBubbles.split(' ')) {
target.dispatchEvent(new Event(event, {bubbles: true}));
const form = target.closest('form');
if (form !== null) {
form.dispatchEvent(new Event(event));
}
}
}
event.preventDefault();
@@ -63,7 +63,7 @@ export default class KimaiCopyDataForm extends KimaiFormPlugin {
/**
* @param {HTMLFormElement} form
*/
destroyForm(form) // eslint-disable-line no-unused-vars
destroyForm(form)
{
form.removeEventListener('click', this._eventHandler);
}

View File

@@ -57,6 +57,16 @@ export default class KimaiDatePicker extends KimaiFormPlugin {
if (element.dataset.format === undefined) {
console.log('Trying to bind litepicker to an element without data-format attribute');
}
if (element.hasAttribute('min') !== undefined) {
options = {...options, ...{
'minDate': element.getAttribute('min'),
}};
}
if (element.hasAttribute('max') !== undefined) {
options = {...options, ...{
'maxDate': element.getAttribute('max'),
}};
}
options = {...options, ...{
format: element.dataset.format,
showTooltip: false,
@@ -66,7 +76,7 @@ export default class KimaiDatePicker extends KimaiFormPlugin {
firstDay: FIRST_DOW, // Litepicker: 0 = Sunday, 1 = Monday
setup: (picker) => {
// nasty hack, because litepicker does not trigger change event on the input and the available
// event "selected" is triggered why to often, even when moving the cursor inside the input
// event "selected" is triggered way to often, even when moving the cursor inside the input
// element (not even typing is necessary) and so we have to make sure that the manual "click" event
// (works for touch as well) happened before we actually dispatch the change event manually ...
// what? report forms would be submitted upon cursor move without the "preselect” check

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,29 +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,
@@ -123,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);
@@ -283,8 +236,13 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
options.push(optGroup);
}
// log the one with a group name first (e.g. non-global activities)
options.forEach(child => node.appendChild(child));
emptyOpts.forEach(child => node.appendChild(child));
// append the ones with no parent at the end (e.g. global activities)
const optGroupEmpty = this._createOptgroup('');
emptyOpts.forEach(child => optGroupEmpty.appendChild(child));
node.appendChild(optGroupEmpty);
// if available, re-select the previous selected option (mostly usable for global activities)
node.value = selectedValue;
@@ -482,7 +440,7 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
targetSelect.dataset['reloading'] = '0';
targetSelect.disabled = false;
});
}
};
document.addEventListener('change', this._eventHandlerApiSelects);
}
@@ -518,14 +476,14 @@ export default class KimaiFormSelect extends KimaiFormPlugin {
newValue = [...targetField.selectedOptions].map(o => o.value);
} else if (newValue !== '') {
if (targetField.type === 'date') {
const timeId = targetField.id.replace('_date', '_time')
const timeId = targetField.id.replace('_date', '_time');
const timeElement = document.getElementById(timeId);
const time = timeElement === null ? '12:00:00' : timeElement.value;
// using 12:00 as fallback, because timezone handling might change the date if we use 00:00
const newDate = this.getDateUtils().fromHtml5Input(newValue, time);
newValue = this.getDateUtils().formatForAPI(newDate, false);
} else if (targetField.type === 'text' && targetField.name.includes('date')) {
const timeId = targetField.id.replace('_date', '_time')
const timeId = targetField.id.replace('_date', '_time');
const timeElement = document.getElementById(timeId);
// using 12:00 as fallback, because timezone handling might change the date if we use 00:00
let time = '12:00:00';

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 me-2">&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 me-2">&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

@@ -40,17 +40,27 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
if (this._beginTime !== undefined) {
this._beginTime.removeEventListener('change', this._beginListener);
delete this._beginListener;
this._beginTime.removeEventListener('blur', this._beginBlurListener);
delete this._beginBlurListener;
delete this._beginTime;
}
if (this._endTime !== undefined) {
this._endTime.removeEventListener('change', this._endListener);
delete this._endListener;
this._endTime.removeEventListener('blur', this._endBlurListener);
delete this._endBlurListener;
delete this._endTime;
}
if (this._duration !== undefined) {
this._duration.removeEventListener('change', this._durationListener);
delete this._durationListener;
this._duration.removeEventListener('keydown', this._durationKeyListener);
delete this._durationKeyListener;
this._duration.removeEventListener('blur', this._durationBlurListener);
delete this._durationBlurListener;
delete this._duration;
}
@@ -108,13 +118,21 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
}
this._beginListener = () => this._changedBegin();
this._beginBlurListener = () => this._parseBeginTime();
this._endListener = () => this._changedEnd();
this._endBlurListener = () => this._parseEndTime();
this._durationListener = () => this._changedDuration();
this._durationKeyListener = (event) => this._changeDurationOnKeypress(event);
this._durationBlurListener = () => this._parseDuration();
this._beginDate.addEventListener('change', this._beginListener);
this._beginTime.addEventListener('change', this._beginListener);
this._beginTime.addEventListener('blur', this._beginBlurListener);
this._endTime.addEventListener('change', this._endListener);
this._endTime.addEventListener('blur', this._endBlurListener);
this._duration.addEventListener('change', this._durationListener);
this._duration.addEventListener('keydown', this._durationKeyListener);
this._duration.addEventListener('blur', this._durationBlurListener);
if (this._duration !== null && this._durationToggle !== null) {
this._durationToggleListener = () => {
@@ -124,6 +142,147 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
}
}
_parseBeginTime()
{
let newBeginTime = this._formatTimeForParsing(this._beginTime.value, this._beginTime.dataset['format']);
if (newBeginTime !== this._beginTime.value) {
this._beginTime.value = newBeginTime;
this._changedBegin();
}
}
_parseEndTime()
{
let newEndTime = this._formatTimeForParsing(this._endTime.value, this._endTime.dataset['format']);
if (newEndTime !== this._endTime.value) {
this._endTime.value = newEndTime;
this._changedEnd();
}
}
_parseDuration()
{
this._setDurationAsString(this._getParsedDuration());
}
/**
* Receives a time, written by a human, probably in an invalid format.
* This method supports 12-hour or 24-hour format, the format string contains an uppercase "A" in case of the 12-hour format.
* If it is 12-hour format, then always en-US locallized with AM/PM.
*
* Ruleset:
* - Some locales use a dot instead of a colon, always replace the dot in HH.mm with a colon as in HH:mm
* - If there is an "am" or "pm", always uppercase them
* - Split the string into time and prefix: if AM/PM is included remove it and remember for later
* - If the time is a 1 or 2 character long number: use as hours
* - If the time now is 3 character long: use the 1 char as hour and the 2 and 3 char as minute
* - If the time now is 4 character long: use the 1 and 2 char as hour and the 3 and 4 char as minutes
* - If the format is 12-hour: try to identify the correct time and suffix
* - If the format is 12-hour and misses the AM/PM: try to detect whether it
* - If the time contains AM or PM, make sure that it is always prefixed by a space character
*
* @param {string} time
* @param {string} format
* @returns {string}
* @private
*/
_formatTimeForParsing(time, format)
{
let formatted = time.trim();
// replace dot with colon
formatted = formatted.replace(/\./g, ':');
// uppercase 12-hour format
formatted = formatted.replace(/am/i, 'AM');
formatted = formatted.replace(/pm/i, 'PM');
// Split time and AM/PM suffix if present
let suffix = '';
let hour = 0;
let minute = 0;
let timePart = formatted;
const ampmMatch = formatted.match(/\s*(AM|PM)$/i);
if (ampmMatch) {
suffix = ampmMatch[1].toUpperCase();
timePart = formatted.replace(/\s*(AM|PM)$/i, '').trim();
}
if (timePart.indexOf(':') !== -1) {
const match = timePart.match(/(?:(\d+):)?(\d+)/);
hour = parseInt(match?.[1] || 0, 10);
minute = parseInt(match?.[2] || 0, 10);
} else {
timePart = timePart.replace(/:/, '');
if (/^\d{1,2}$/.test(timePart)) {
hour = timePart;
}
if (/^\d{3}$/.test(timePart)) {
hour = timePart.slice(0, 1);
minute = timePart.slice(1);
}
if (/^\d{4}$/.test(timePart)) {
hour = timePart.slice(0, 2);
minute = timePart.slice(2);
}
}
hour = parseInt(hour);
minute = parseInt(minute);
// just in case a person entered a wrong time like 35 hours
hour = hour % 24;
minute = minute % 60;
// format is 12-hour
if (format.toUpperCase().indexOf('A') !== -1) {
// time entered in 24-hour: convert to 12-hour format
if (hour > 12 && hour < 24) {
hour = hour - 12;
suffix = 'PM';
}
// if the person forgot to add a suffix, calculate it and convert time
if (suffix === '') {
if (hour === 0) {
hour = 12;
suffix = 'AM';
} else if (hour === 12) {
suffix = 'PM';
} else {
suffix = 'AM';
}
}
if (suffix === 'PM' && hour === 0) {
hour = 12;
}
} else {
// this is the 34-hour format branch
// check if the person entered time in 12-hour format and convert it
if (suffix === 'AM' && hour === 12) {
hour = 0;
} else if (suffix === 'PM' && hour !== 12) {
hour = (hour + 12) % 24;
}
// make sure we have no suffix
suffix = '';
}
formatted = hour + ':' + minute.toString().padStart(2, '0');
if (suffix !== '') {
formatted = formatted + ' ' + suffix.trim();
}
return formatted;
}
_isDurationConnected()
{
if (this._duration === null && this._durationToggle === null) {
@@ -315,11 +474,11 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
if (begin === null && end === null) {
const newBegin = DateTime.now();
this._applyDateToField(newBegin, this._beginDate, this._beginTime);
this._applyDateToField(newBegin.plus({seconds: seconds}), null, this._endTime);
this._addSecondsToEndDate(newBegin, seconds);
} else if (begin === null && end !== null) {
this._applyDateToField(end.minus({seconds: seconds}), this._beginDate, this._beginTime);
} else if (begin !== null && seconds >= 0) {
this._applyDateToField(begin.plus({seconds: seconds}), null, this._endTime);
this._addSecondsToEndDate(begin, seconds);
}
}
@@ -367,7 +526,23 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
*/
_getParsedDuration()
{
return this.getDateUtils().parseDuration(this._duration.value.toUpperCase());
return this.getDateUtils().parseDuration(this._duration.value);
}
/**
* @param {DateTime} dateTime
* @param {int} seconds
* @private
*/
_addSecondsToEndDate(dateTime, seconds)
{
// if the duration is longer than one day, the end field should be empty
// so kimai can calculate it after submitting the data from start + duration
if (seconds < 86400) {
this._applyDateToField(dateTime.plus({seconds: seconds}), null, this._endTime);
} else {
this._endTime.value = '';
}
}
/**
@@ -390,4 +565,122 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
timeField.value = this.getDateUtils().format(timeField.dataset['format'], dateTime);
}
/**
* @param {KeyboardEvent} event
* @private
*/
_changeDurationOnKeypress(event)
{
switch (event.key) {
case 'ArrowUp':
case 'ArrowDown':
case 'PageUp':
case 'PageDown':
case 'Home':
case 'End':
this._setDurationAsString(this._getParsedDuration());
break;
default:
return; // Ignore other keys
}
this._changeTimeOnKeypress(event, this._duration, 99999, this._durationListener);
}
/**
* This method helps the user to change a duration field with simple keyboard interaction:
* - Read the current duration from the given timeField input in format HH:MM (no seconds)
* - Change the duration based on the rules below
* - Write the new duration back to the field
* - If the field is empty or invalid it uses 00:00 as start-time
* - Duration cannot exceed maxtime (which is given in minutes)
* - Duration cannot drop below 00:00
* - Read the position of the cursor and decide whether to increase minutes or hours: if the cursor is in the hour section (before the colon) change hours, if the cursor is in the minute section (after the colon) change minutes
* - It reads the pressed key from the given KeyboardEvent and changes the duration accordingly to the rules below
*
* Rules to apply when a key is pressed:
* - ArrowUp key to increase the duration (either 5 minutes or 1 hour, depending on the cursor position)
* - ArrowDown key to decrease the duration (either 5 minutes or 1 hour, depending on the cursor position)
* - PageUp key to increase the duration by 1 hour
* - PageDown key to decrease the duration by 1 hour
* - Home key to set the duration to 08:00
* - End key to set the duration to 00:00
* - all other keys are ignored
*
* @param {KeyboardEvent} event
* @param {HTMLElement} timeField
* @param {int} maxTime
* @param {function} changeCallback
* @private
*/
_changeTimeOnKeypress(event, timeField, maxTime, changeCallback)
{
// Parse current value or default to 00:00
let value = timeField.value || '00:00';
let [hours, minutes] = value.split(':').map(Number);
if (isNaN(hours)) { hours = 0; }
if (isNaN(minutes)) { minutes = 0; }
// Cursor position: before or after colon
const cursorPos = timeField.selectionStart || 0;
const colonPos = value.indexOf(':');
const inHour = cursorPos <= colonPos;
// Helper to clamp values
const clamp = (h, m) => {
let total = h * 60 + m;
if (total < 0) { total = 0; }
if (total > maxTime) { total = maxTime; }
h = Math.floor(total / 60);
m = total % 60;
return [h, m];
};
switch (event.key) {
case 'ArrowUp':
if (inHour) {
[hours, minutes] = clamp(hours + 1, minutes);
} else {
[hours, minutes] = clamp(hours, minutes + 5);
}
break;
case 'ArrowDown':
if (inHour) {
[hours, minutes] = clamp(hours - 1, minutes);
} else {
[hours, minutes] = clamp(hours, minutes - 5);
}
break;
case 'PageUp':
[hours, minutes] = clamp(hours + 1, minutes);
event.preventDefault();
break;
case 'PageDown':
[hours, minutes] = clamp(hours - 1, minutes);
event.preventDefault();
break;
case 'Home':
// TODO this should use the configured working time for today
hours = 8;
minutes = 0;
event.preventDefault();
break;
case 'End':
hours = 0;
minutes = 0;
event.preventDefault();
break;
default:
return; // Ignore other keys
}
// Format and set value
timeField.value = `${hours}:${minutes.toString().padStart(2, '0')}`;
// trigger update of linked fields
changeCallback(timeField);
// Move cursor to original position if possible
setTimeout(() => {
timeField.setSelectionRange(cursorPos, cursorPos);
}, 0);
}
}

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,12 +64,12 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
this.addClickHandler(this._selector, (href) => {
this.openUrlInModal(href);
});
}, this._stopSelector);
}
_getModal()
{
return Modal.getOrCreateInstance(this._getModalElement())
return Modal.getOrCreateInstance(this._getModalElement());
}
/**

View File

@@ -52,7 +52,7 @@ export default class KimaiAlert extends KimaiPlugin {
}
const html = `
<div class="modal modal-blur fade" id="` + id + `" tabindex="-1" role="dialog">
<div class="modal" id="` + id + `" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-status bg-` + this._mapClass('danger') + `"></div>
@@ -128,7 +128,7 @@ export default class KimaiAlert extends KimaiPlugin {
}
const html = `
<div class="modal modal-blur fade" tabindex="-1" role="dialog">
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-status bg-` + this._mapClass(type) + `"></div>
@@ -208,7 +208,7 @@ export default class KimaiAlert extends KimaiPlugin {
const toast = new Toast(element);
element.addEventListener('hidden.bs.toast', function () {
container.removeChild(element);
})
});
toast.show();
}
@@ -227,7 +227,7 @@ export default class KimaiAlert extends KimaiPlugin {
const css = this._mapClass('info');
const html = `
<div class="modal modal-blur fade" tabindex="-1" role="dialog" data-bs-backdrop="static">
<div class="modal fade" tabindex="-1" role="dialog" data-bs-backdrop="static">
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-status bg-` + css + `"></div>

View File

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

View File

@@ -273,7 +273,10 @@ export default class KimaiDateUtils extends KimaiPlugin {
let luxonDuration = null;
if (duration.indexOf(':') !== -1) {
const [, hours, minutes, seconds] = duration.match(/(\d+):(\d+)(?::(\d+))*/);
const match = duration.match(/(?:(\d+):)?(\d+)(?::(\d+))?/);
const hours = parseInt(match?.[1] || 0, 10);
const minutes = parseInt(match?.[2] || 0, 10);
const seconds = parseInt(match?.[3] || 0, 10);
luxonDuration = Duration.fromObject({hours: hours, minutes: minutes, seconds: seconds});
} else if (duration.indexOf('.') !== -1 || duration.indexOf(',') !== -1) {
duration = duration.replace(/,/, '.');

View File

@@ -36,7 +36,7 @@ export default class KimaiHotkeys extends KimaiPlugin {
elements[0].click();
}
}
})
});
}
// adopted from Bootstrap 5.1.1, MIT

View File

@@ -13,9 +13,14 @@ import KimaiPlugin from '../KimaiPlugin';
export default class KimaiMultiUpdateTable extends KimaiPlugin {
getId()
{
return 'datatable-batch-action';
}
init()
{
if (document.getElementById('multi_update_all') === null) {
if (document.getElementsByClassName('multi_update_all').length === 0) {
return;
}
@@ -23,64 +28,101 @@ export default class KimaiMultiUpdateTable extends KimaiPlugin {
// via KimaiDatable and everything inside will be removed, including event listeners
const element = document.querySelector('div.page-body');
element.addEventListener('change', (event) => {
if (event.target.matches('#multi_update_all')) {
// the "check all" checkbox in the upper start corner of the table
const checked = event.target.checked;
for (const element of document.querySelectorAll('.multi_update_single')) {
element.checked = checked;
}
this._toggleForm();
if (event.target.matches('.multi_update_all')) {
this.toggle(event.target.checked, event.target.closest('table'));
event.stopPropagation();
} else if (event.target.matches('.multi_update_single')) {
// single checkboxes in front of each row
this._toggleForm();
this._toggleDatatable(event.target.closest('table'));
event.stopPropagation();
}
});
element.addEventListener('click', (event) => {
if (event.target.matches('.multi_update_table_action')) {
const selectedItem = event.target;
const ids = this._getSelectedIds();
const form = document.getElementById('multi_update_form');
const question = form.dataset['question'].replace(/%action%/, selectedItem.textContent).replace(/%count%/, ids.length.toString());
const selectedButton = event.target;
const form = selectedButton.form;
const ids = form.querySelector('.multi_update_ids').value.split(',');
const question = form.dataset['question'].replace(/%action%/, selectedButton.textContent).replace(/%count%/, ids.length.toString());
/** @type {KimaiAlert} ALERT */
const ALERT = this.getPlugin('alert');
ALERT.question(question, function(value) {
if (value) {
const form = document.getElementById('multi_update_form');
form.action = selectedItem.dataset['href'];
form.action = selectedButton.dataset['href'];
form.submit();
}
});
}
});
}
_getSelectedIds()
/**
* @param {boolean} checked
* @param {HTMLTableElement} table
*/
toggle(checked, table)
{
for (const element of table.querySelectorAll('.multi_update_single')) {
element.checked = checked;
}
this._toggleDatatable(table);
}
/**
* @param {boolean} checked
*/
toggleAll(checked)
{
for (const element of document.querySelectorAll('.multi_update_all')) {
this._toggleAll(checked, element);
}
}
/**
* @param {boolean} checked
* @param {string} name
*/
toggleByName(checked, name)
{
for (const element of document.querySelectorAll('#multi_update_all_' + name)) {
this._toggleAll(checked, element);
}
}
/**
* @param {boolean} checked
* @param {Element} name
*/
_toggleAll(checked, element)
{
element.checked = checked;
this.toggle(checked, element.closest('table'));
}
/**
* @param {HTMLTableElement} table
* @private
*/
_toggleDatatable(table)
{
const card = table.closest('div.card.data_table');
let ids = [];
for (const box of document.querySelectorAll('input.multi_update_single:checked')) {
for (const box of table.querySelectorAll('input.multi_update_single:checked')) {
ids.push(box.value);
}
return ids;
}
_toggleForm()
{
const ids = this._getSelectedIds();
document.getElementById('multi_update_table_entities').value = ids.join(',');
card.querySelector('.multi_update_ids').value = ids.join(',');
if (ids.length > 0) {
for (const element of document.getElementsByClassName('multi_update_form_hide')) {
for (const element of card.querySelectorAll('.multi_update_form_hide')) {
element.style.setProperty('display', 'none', 'important');
}
document.getElementById('multi_update_form').style.display = null;//'block';
card.querySelector('form.multi_update_form').style.display = null;//'block';
} else {
document.getElementById('multi_update_form').style.setProperty('display', 'none', 'important');
for (const element of document.getElementsByClassName('multi_update_form_hide')) {
card.querySelector('form.multi_update_form').style.setProperty('display', 'none', 'important');
for (const element of card.querySelectorAll('.multi_update_form_hide')) {
element.style.display = null;
}
}

View File

@@ -43,7 +43,7 @@ export default class KimaiNotification extends KimaiPlugin {
callback(false);
}
});
} catch (e) {
} catch (e) { // eslint-disable-line no-unused-vars
Notification.requestPermission((permission) => {
if (permission === "granted") {
callback(true);

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

@@ -34,7 +34,7 @@ export default class KimaiRemoteModal extends KimaiPlugin {
this._showModal(event.currentTarget);
event.stopPropagation();
event.preventDefault();
}
};
for (let link of document.querySelectorAll(this._selector)) {
link.addEventListener('click', this.handle);

View File

@@ -9,7 +9,7 @@
* [KIMAI] KimaiThemeInitializer: initialize theme functionality
*/
import { Tooltip } from 'bootstrap';
import { Tooltip, Offcanvas } from 'bootstrap';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiThemeInitializer extends KimaiPlugin {
@@ -21,6 +21,10 @@ export default class KimaiThemeInitializer extends KimaiPlugin {
return new Tooltip(tooltipTriggerEl);
});
// support for offcanvas elements
const offcanvasElementList = document.querySelectorAll('.offcanvas');
[...offcanvasElementList].map(offcanvasEl => new Offcanvas(offcanvasEl));
// activate all form plugins
/** @type {KimaiForm} FORMS */
const FORMS = this.getContainer().getPlugin('form');

View File

@@ -49,4 +49,11 @@ export default class KimaiUser extends KimaiPlugin {
return this.user.superAdmin;
}
/**
* @returns {array}
*/
getRoles() {
return this.user.roles;
}
}

View File

@@ -14,7 +14,7 @@ import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import bootstrap5Plugin, { BootstrapTheme } from '@fullcalendar/bootstrap5';
import googlePlugin from '@fullcalendar/google-calendar';
import iCalendarPlugin from '@fullcalendar/icalendar'
import iCalendarPlugin from '@fullcalendar/icalendar';
import interactionPlugin, { Draggable } from '@fullcalendar/interaction';
import arLocale from '@fullcalendar/core/locales/ar';
import csLocale from '@fullcalendar/core/locales/cs';
@@ -109,12 +109,11 @@ export default class KimaiCalendar {
nextYear: this.options['icons']['nextYear'],
};
BootstrapTheme.prototype.rtlIconClasses = {
prev: this.options['icons']['next'],
next: this.options['icons']['previous'],
prevYear: this.options['icons']['nextYear'],
nextYear: this.options['icons']['previousYear'],
prev: this.options['icons']['previous'],
next: this.options['icons']['next'],
prevYear: this.options['icons']['previousYear'],
nextYear: this.options['icons']['nextYear'],
};
let calendarOptions = {
locales: [ enGbLocale, enUsLocale, arLocale, csLocale, daLocale, deLocale, deAtLocale, elLocale,
esLocale, euLocale, faLocale, fiLocale, frLocale, heLocale, hrLocale, huLocale, itLocale, jaLocale, koLocale,
@@ -265,7 +264,7 @@ export default class KimaiCalendar {
contextMenu.createFromApi(jsEvent, result);
}, (e) => { console.log('Failed to load actions for context menu', e); });
}
})
});
},
};
@@ -410,13 +409,13 @@ export default class KimaiCalendar {
this.hidePopover(info.el);
},
eventDrop: (eventDropInfo) => {
this.changeHandler(eventDropInfo)
this.changeHandler(eventDropInfo);
},
eventResizeStart: (info) => {
this.hidePopover(info.el);
},
eventResize: (eventResizeInfo) => {
this.changeHandler(eventResizeInfo)
this.changeHandler(eventResizeInfo);
},
}};
}
@@ -569,7 +568,7 @@ export default class KimaiCalendar {
color = apiItem.project.customer.color;
}
}
if (color == null) {
if (color === null) {
color = defaultColor;
}

View File

@@ -70,7 +70,10 @@ export default class KimaiContextMenu {
{
const dropdownElement = this.getContextMenuElement();
dropdownElement.style.zIndex = '1021'; // stay on top of sticky elements (like table header)
if (!dropdownElement.classList.contains('action-dropdown')) {
dropdownElement.classList.add('action-dropdown');
}
dropdownElement.innerHTML = html;
dropdownElement.style.position = 'fixed';
dropdownElement.style.top = (event.clientY) + 'px';
@@ -86,7 +89,7 @@ export default class KimaiContextMenu {
}
dropdownElement.removeEventListener('click', dropdownListener);
document.removeEventListener('click', dropdownListener);
}
};
dropdownElement.addEventListener('click', dropdownListener);
document.addEventListener('click', dropdownListener);

View File

@@ -16,7 +16,6 @@ export default class KimaiPaginatedBoxWidget {
constructor(boxId) {
this.selector = boxId;
const widget = document.querySelector(this.selector);
this.href = widget.dataset['href'];
if (widget.dataset['reload'] !== undefined) {
this.events = widget.dataset['reload'].split(' ');
@@ -61,7 +60,7 @@ export default class KimaiPaginatedBoxWidget {
// and this event will hide it afterwards
const hideOverlay = () => {
document.dispatchEvent(new Event('kimai.reloadedContent'));
}
};
window.kimai.getPlugin('fetch').fetch(url)
.then(response => {
@@ -93,7 +92,7 @@ export default class KimaiPaginatedBoxWidget {
if (node.tagName !== undefined && node.tagName === 'SCRIPT') {
const script = document.createElement('script');
script.text = node.innerHTML;
node.parentNode.replaceChild(script, node );
node.parentNode.replaceChild(script, node);
} else {
for (const child of node.childNodes) {
this._makeScriptExecutable(child);

45
assets/sass/_app-rtl.scss Normal file
View File

@@ -0,0 +1,45 @@
/*
* 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.
*/
$modal-backdrop-opacity: 0.6 !default;
/*
THIS DOES NOT WORK AS EXPECTED, AS TABLER IS NOT REGENERATED HERE.
SO WE CANNOT CHANGE VARIABLES, LIKE THE ABOVE...
*/
@import '~@tabler/core/dist/css/tabler.rtl.css';
@import '~@tabler/core/dist/css/tabler-vendors.rtl.css';
/**
* Everyone needs icons
*/
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import '~@fortawesome/fontawesome-free/scss/regular';
@import '~@fortawesome/fontawesome-free/scss/solid';
@import '~@fortawesome/fontawesome-free/scss/brands';
/**
* We need that, to compile the rest of the file
*/
@import "~bootstrap/scss/functions";
@import "~@tabler/core/src/scss/variables";
@import "~@tabler/core/src/scss/variables-dark";
@import "~@tabler/core/src/scss/mixins";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/variables-dark";
@import "~bootstrap/scss/maps";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/utilities";
/**
* Kimai related stuff
*/
@import "kimai";
@import "rtl";

View File

@@ -5,33 +5,25 @@
* file that was distributed with this source code.
*/
@import "bootstrap/scss/functions";
$modal-backdrop-opacity: 0.6 !default;
@import "@tabler/core/src/scss/variables";
@import "@tabler/core/src/scss/variables-dark";
@import "@tabler/core/src/scss/mixins";
@import '~@tabler/core/src/scss/tabler';
@import '~@tabler/core/src/scss/vendor/litepicker';
@import '~@tabler/core/src/scss/vendor/tom-select';
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";
/**
* Everyone needs icons
*/
@import "layout";
@import "error-page";
@import "print";
@import "content";
@import "loading-spinner";
@import "tables";
@import "calendar";
@import "ticktac";
@import "selectpicker";
@import "forms";
@import "modal";
@import "progressbar";
@import "avatar";
@import "theme-dark";
@import "pages";
@import "tabler-fixes";
@import "help";
@import "rtl";
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import '~@fortawesome/fontawesome-free/scss/regular';
@import '~@fortawesome/fontawesome-free/scss/solid';
@import '~@fortawesome/fontawesome-free/scss/brands';
/**
* Kimai specific additions
*/
@import "kimai";

View File

@@ -5,111 +5,30 @@
* file that was distributed with this source code.
*/
body {
font-family: sans-serif;
font-size: 10pt;
margin: 4px;
padding: 0;
}
table, tr, td, th, p, h1, h2, h3, h4, h5 {
padding: 0;
margin: 0;
}
th {
border: none;
text-align: left;
}
td {
vertical-align: top;
}
p {
margin-bottom: 12px;
}
.text-small {
font-size: 7pt;
}
.padding-left {
padding-left: 15px;
}
.text-center {
text-align: center;
}
.text-left, .text-begin {
text-align: left;
}
.text-right, .text-end {
text-align: right;
}
.text-nowrap {
white-space: nowrap;
}
.date {
font-size: 80%;
text-align: right;
padding-top: 6px;
}
table {
width: 100%;
border-spacing: 0;
}
table.addresses, p {
line-height: 14pt;
}
/* The address table */
table.addresses {
margin-bottom: 30px;
}
/* The PDF header */
table.header {
padding-bottom: 4px;
border-bottom: 1px solid #999;
}
table.header .title {
font-weight: normal;
font-size: 140%;
}
/* The PDF footer */
table.footer {
border-top: 1px solid #999;
font-size: 80%;
padding-top: 8px;
line-height: 14px;
}
table.footer td {
vertical-align: top;
}
/* The invoice items list */
table.items {
border-collapse: collapse;
margin-top: 50px;
margin-bottom: 60px;
}
table.items thead th,
table.items tbody td {
border-bottom: 1px solid #ccc;
}
table.items thead th.first,
table.items tbody td.first {
padding-left: 0;
}
table.items thead th.last,
table.items tbody td.last,
table.items tfoot td.last {
padding-right: 0;
}
table.items td,
table.items th {
padding: 8px 6px;
}
table.items tfoot td,
table.items tfoot th {
padding: 20px 0 0 0;
line-height: 1px;
}
table.items tfoot td.last {
padding-left: 10px;
}
table.items tr.odd {
background-color: #f5f5f5;
}
body { font-family: sans-serif; font-size: 10pt; margin: 4px; padding: 0; }
table, tr, td, th, p, h1, h2, h3, h4, h5 { padding: 0; margin: 0;}
th { border: none; text-align: left; }
td { vertical-align: top; }
p { margin-bottom: 12px; }
.text-small { font-size: 7pt; }
.padding-left { padding-left: 15px; }
.text-center { text-align: center; }
.text-left, .text-begin { text-align: left; }
.text-right, .text-end { text-align: right; }
.text-nowrap { white-space: nowrap; }
.date { font-size: 80%; text-align: right; padding-top: 6px; }
table { width: 100%; border-spacing: 0; }
table.addresses, p { line-height: 14pt; }
table.addresses { margin-bottom: 30px; }
table.header { padding-bottom: 4px; border-bottom: 1px solid #999; }
table.header .title { font-weight: normal; font-size: 140%; }
table.footer { border-top: 1px solid #999; font-size: 80%; padding-top: 8px; line-height: 14px; }
table.footer td { vertical-align: top; }
table.items { border-collapse: collapse; margin-top: 50px; margin-bottom: 60px; }
table.items thead th, table.items tbody td { border-bottom: 1px solid #ccc; }
table.items thead th.first, table.items tbody td.first { padding-left: 0; }
table.items thead th.last, table.items tbody td.last, table.items tfoot td.last { padding-right: 0; }
table.items td, table.items th { padding: 8px 6px; }
table.items tfoot td, table.items tfoot th { padding: 20px 0 0 0; line-height: 1px; }
table.items tfoot td.last { padding-left: 10px; }
table.items tr.odd { background-color: #f5f5f5; }

18
assets/sass/_kimai.scss Normal file
View File

@@ -0,0 +1,18 @@
@import "layout";
@import "error-page";
@import "print";
@import "content";
@import "loading-spinner";
@import "tables";
@import "calendar";
@import "ticktac";
@import "selectpicker";
@import "forms";
@import "modal";
@import "progressbar";
@import "avatar";
@import "theme-dark";
@import "pages";
@import "tabler-fixes";
@import "help";
@import "rtl";

View File

@@ -52,6 +52,13 @@ table.dataTable thead > tr > th.hw-min {
width: 1%;
white-space: normal;
}
@include media-breakpoint-up(sm) {
th.w-sm-min,
td.w-sm-min {
width: 1%;
white-space: nowrap;
}
}
/* If a table column contains ONLY avatar <img> it will collapse, so make it a defined width */
.w-avatar {
width: 40px;

View File

@@ -26,6 +26,9 @@
margin-bottom: 0;
}
}
.input-group {
width: unset;
}
}
}
@@ -33,7 +36,6 @@
width: 20px;
height: 20px;
display: inline-block;
margin-right: 10px;
border-radius: var(--tblr-border-radius);
}

View File

@@ -1,9 +1,5 @@
.inline-search {
max-width: 200px;
/* the entire inline-search should actually use .input-group-flat, but that causes some weird css problems inside the dropdown menu */
#searchTerm {
border-right: 0;
}
}
@media (min-width: 360px) {
@@ -43,3 +39,21 @@ h1.navbar-brand a {
.navbar {
--tblr-navbar-brand-font-size: 1rem;
}
/* user shortcuts dropdown */
.user-shortcuts {
.dropdown-menu-card {
min-width: 260px;
}
.user-shortcuts-icon {
background-color: var(--tblr-bg-surface-secondary);
color: var(--tblr-gray-500);
height: 3.125rem;
width: 3.125rem;
margin-left: auto;
margin-right: auto;
display: flex;
align-items: center;
justify-content: center;
}
}

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;
}
@@ -17,18 +17,46 @@ fieldset.form-fieldset legend {
--tblr-dropdown-divider-margin-y: .5rem;
}
/*
fixes translucent scroll border, e.g. in timesheet modal duration dropdown
https://github.com/tabler/tabler/issues/1606
*/
/* fix translucent scroll border (e.g. timesheet modal duration dropdown) - see https://github.com/tabler/tabler/issues/1606 */
.dropdown-menu {
background-clip: border-box;
}
/*
hide empty fieldset
https://github.com/tabler/tabler/issues/1650
*/
/* hide empty fieldset - see https://github.com/tabler/tabler/issues/1650 */
fieldset:empty {
display: none;
}
:host,:root,[data-bs-theme=light] {
/* remove the shadow from all form element groups */
--tblr-box-shadow-input: none;
}
.navbar[data-bs-theme=dark] {
/* give navigation elements slightly higher contrast */
--tblr-navbar-color: hsla(0,0%,100%,.8);
}
/* this is not a bugfix, but it adds a border for better visual segregation */
.nav-pills .nav-link.active {
border-width: 1px;
border-style: solid;
}
[data-bs-theme=dark] {
/* fixes https://github.com/tabler/tabler/issues/1974 */
.litepicker .container__days .day-item.is-in-range {
--litepicker-is-in-range-color: var(--tblr-primary-text-emphasis);
}
}
/* fixes contrast of batch-update checkboxes - https://github.com/kimai/kimai/issues/5146 */
.multiupdater[type=checkbox]
{
--tblr-border-color-translucent: rgba(4, 32, 69, .2);
}
/* fixes height for long tags https://github.com/kimai/kimai/issues/5169 */
.tag {
--tblr-tag-height: unset;
}

View File

@@ -26,7 +26,7 @@ table.dataTable {
thead .sorting_asc:after,
thead .sorting_desc:after {
padding-left: 5px;
font-family: 'Font Awesome 5 Free';
font-family: 'Font Awesome 6 Free';
opacity: 0.5;
font-size: 12px;
}
@@ -98,7 +98,7 @@ table.dataTable {
width: 15px;
}
&.overlapping {
border-top: 2px solid rgba(214,57,57,.1);
border-top: 2px solid rgba(214,57,57,.2);
}
&.exported {
opacity: 0.7;
@@ -123,7 +123,7 @@ table.dataTable {
}
th.weekend,
td.weekend {
background-color: var(--tblr-bg-surface-secondary);
background-color: var(--tblr-bg-surface-tertiary);
}
/* order is important, "today” should overwrite "weekend" therefor later in the file */
th.today {
@@ -153,3 +153,10 @@ table.table-hover {
}
}
}
/* For the context menu */
.action-dropdown,
.actions .dropdown .dropdown-menu {
// stay on top of sticky elements (like table header)
z-index: 1021;
}

View File

@@ -1,7 +1,22 @@
@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;
}
}
@@ -12,4 +27,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

@@ -1,17 +0,0 @@
module.exports = {
"sourceType": "unambiguous",
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {},
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import"
]
}

View File

@@ -14,7 +14,7 @@
}
],
"require": {
"php": "8.1.*||8.2.*",
"php": "8.1.*||8.2.*||8.3.*||8.4.*",
"ext-gd": "*",
"ext-intl": "*",
"ext-json": "*",
@@ -28,21 +28,23 @@
"azuyalabs/yasumi": "^2.6",
"composer/semver": "^3.3",
"doctrine/doctrine-bundle": "^2.7",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^2.8",
"easybill/zugferd-php": "^2.1",
"endroid/qr-code": "^4.8",
"erusev/parsedown": "^1.6",
"friendsofsymfony/rest-bundle": "^3.0",
"gedmo/doctrine-extensions": "^3.6",
"jms/serializer-bundle": "^5.0",
"kevinpapst/tabler-bundle": "^1.0",
"kevinpapst/tabler-bundle": "^1.4",
"league/csv": "^9.4",
"mpdf/mpdf": "^8.0",
"nelmio/api-doc-bundle": "^4.0",
"nelmio/api-doc-bundle": "^5.0",
"nelmio/cors-bundle": "^2.0",
"onelogin/php-saml": "^4.0",
"openspout/openspout": "^4.0",
"pagerfanta/pagerfanta": "^3.0",
"phpoffice/phpspreadsheet": "^1.16",
"phpoffice/phpspreadsheet": "^2.0",
"phpoffice/phpword": "^1.0",
"psr/container": "^2.0",
"psr/log": "^3.0",
@@ -59,7 +61,9 @@
"symfony/http-client": "^6.0",
"symfony/intl": "^6.0",
"symfony/mailer": "^6.0",
"symfony/mime": "^6.0",
"symfony/monolog-bundle": "^3.4",
"symfony/process": "^6.0",
"symfony/rate-limiter": "^6.0",
"symfony/runtime": "^6.0",
"symfony/security-bundle": "^6.0",
@@ -78,16 +82,17 @@
},
"require-dev": {
"ext-simplexml": "*",
"dama/doctrine-test-bundle": "^7.0",
"dama/doctrine-test-bundle": "^8.0",
"doctrine/doctrine-fixtures-bundle": "^3.2",
"fakerphp/faker": "^1.15",
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-doctrine": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpstan/phpstan-symfony": "^1.0",
"phpunit/phpunit": "9.5.*",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-doctrine": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpstan/phpstan-symfony": "^2.0",
"phpunit/phpunit": "^10.0",
"symfony/browser-kit": "^6.0",
"symfony/css-selector": "^6.0",
"symfony/debug-bundle": "^6.0",
@@ -132,6 +137,10 @@
"symfony/polyfill-ctype": "*",
"symfony/polyfill-mbstring": "*",
"symfony/polyfill-intl": "*",
"symfony/polyfill-intl-icu": "*",
"symfony/polyfill-intl-grapheme": "*",
"symfony/polyfill-intl-idn": "*",
"symfony/polyfill-intl-normalizer": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php81": "*",
"symfony/polyfill-php80": "*",
@@ -169,12 +178,11 @@
"@tests-integration"
],
"linting": [
"composer validate --strict",
"bin/console lint:container",
"bin/console lint:yaml config --parse-tags",
"bin/console lint:twig templates --show-deprecations",
"bin/console lint:xliff translations",
"bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction"
"bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction",
"bin/console lint:xliff translations"
],
"tests": "vendor/bin/phpunit tests/",
"tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
@@ -196,13 +204,10 @@
"symfony/symfony": "*"
},
"extra": {
"branch-alias": {
"dev-main": "2.0.x-dev"
},
"symfony": {
"id": "01C3FWRDJJEX9K6Y3A4XDFXPBR",
"allow-contrib": true,
"require": "6.3.*"
"require": "6.4.*"
}
}
}

5973
composer.lock generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -17,14 +17,20 @@ doctrine:
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
collation: utf8mb4_unicode_ci
schema_manager_factory: doctrine.dbal.default_schema_manager_factory
types:
datetime: App\Doctrine\UTCDateTimeType
datetime_immutable: App\Doctrine\UTCDateTimeImmutableType
orm:
controller_resolver:
# FIXME this is causing a deprecation and needs to be changed
# auto_mapping: false
auto_mapping: true
auto_generate_proxy_classes: '%kernel.debug%'
default_entity_manager: default
enable_lazy_ghost_objects: true
entity_managers:
default:
report_fields_where_declared: true
@@ -47,7 +53,10 @@ doctrine:
when@test:
doctrine:
dbal:
logging: false
connections:
default:
logging: false
use_savepoints: true
when@prod:
doctrine:

View File

@@ -5,3 +5,4 @@ doctrine_migrations:
table_name: 'migration_versions'
migrations_paths:
'DoctrineMigrations': '%kernel.project_dir%/migrations'
custom_template: '%kernel.project_dir%/migrations/MigrationTemplate.txt'

View File

@@ -1,11 +1,25 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
parameters:
env(TRUSTED_PROXIES): ''
framework:
secret: '%env(APP_SECRET)%'
csrf_protection: true
annotations: false
handle_all_throwables: true
disallow_search_engine_index: true
set_content_language_from_locale: true
serializer:
enable_attributes: true
trusted_proxies: '%env(string:TRUSTED_PROXIES)%'
exceptions:
App\Validator\ValidationFailedException:
log_level: debug
secret: '%env(APP_SECRET)%'
default_locale: en
csrf_protection: true
http_method_override: false
# Enables session support. Note that the session will ONLY be started if you read or write from it.
@@ -25,6 +39,7 @@ framework:
validation:
email_validation_mode: html5
enable_attributes: true
when@dev:
framework:

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','hours_own_profile']
# 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_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member','hours_other_profile']
ROLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member','view_all_data','contract_other_profile','hours_other_profile']
ROLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','supervisor_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template','view_all_data','contract_other_profile','hours_other_profile']
# --------------------------------------------------------------------------------

View File

@@ -1,43 +1,58 @@
monolog:
channels: ["deprecation"]
when@prod:
monolog:
# channels: ["deprecation"]
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [403, 404]
nested:
handler: file_log
excluded_http_codes:
- {400: ['^/api/']}
- {401: ['^/api/']}
- 403
- 404
- {405: ['/homepage$', '/login_check$', '/export/data$']}
channels: ["!deprecation"]
file_log:
type: stream
level: info
path: "%kernel.logs_dir%/%kernel.environment%.log"
formatter: monolog.formatter.kimai
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
# deactivated, because currently there are too many deprecations cause by gedmo and doctrine
# deprecation:
# type: stream
# channels: ["deprecation"]
# path: "%kernel.logs_dir%/deprecations.log"
channels: ["!event", "!doctrine", "!deprecation"]
when@dev:
monolog:
channels: ["deprecation"]
handlers:
main:
type: fingers_crossed
action_level: notice
handler: file_log
excluded_http_codes:
- {400: ['^/api/']}
- {401: ['^/api/']}
- 403
- 404
- {405: ['/homepage$', '/login_check$', '/export/data$']}
channels: ["!event", "!deprecation"]
file_log:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: info
channels: ["!event"]
level: debug
formatter: monolog.formatter.kimai
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
channels: ["!event", "!doctrine", "!deprecation"]
deprecation:
type: stream
channels: ["deprecation"]
path: "%kernel.logs_dir%/deprecations.log"
formatter: monolog.formatter.deprecation
when@test:
monolog:
@@ -47,3 +62,4 @@ when@test:
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: info
channels: ["!event"]
formatter: monolog.formatter.kimai

View File

@@ -1,4 +1,5 @@
nelmio_api_doc:
operation_id_generation: conditionally_prepend
models:
use_jms: true
names:
@@ -40,27 +41,31 @@ 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)
default:
path_patterns:
- ^/api(?!/doc)
security:
bearer:
type: 'http'
description: 'API Token'
bearerFormat: 'KIMAI'
scheme: 'bearer'
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'
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
JSON API for the Kimai time-tracking software. Find more infos in our [API documentation](https://www.kimai.org/documentation/rest-api.html).
version: '1.1'
security:
- X-AUTH-USER: []
X-AUTH-TOKEN: []
- bearer: []
html_config:
# assets_mode: cdn
# https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
swagger_ui_config: []
# https://redocly.com/docs/redoc/config/
redocly_config: []
# https://docs.stoplight.io/docs/elements/b074dc47b2826-elements-configuration-options
stoplight_config: { basePath: '/api/doc', router: 'memory', logo: '/touch-icon-192x192.png', hideInternal: true }

View File

@@ -3,12 +3,7 @@ parameters:
nelmio_cors:
defaults:
allow_credentials: false
# allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
# allow_headers: ['Content-Type', 'Authorization', 'X-AUTH-USER', 'X-AUTH-TOKEN']
# allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
# expose_headers: ['Link']
# max_age: 3600
# for security reasons we do not allow CORS by default
allow_origin: []
allow_headers: []
allow_methods: []
@@ -18,8 +13,17 @@ nelmio_cors:
origin_regex: true
forced_allow_origin_value: ~
paths:
# only the API endpoints are accessible
'^/api/':
# allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_origin: ['*']
allow_headers: ['X-AUTH-USER', 'X-AUTH-TOKEN', 'Content-Type']
allow_headers: ['Content-Type', 'Authorization', 'X-AUTH-USER', 'X-AUTH-TOKEN']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
# expose_headers: ['Link']
max_age: 3600
when@dev:
nelmio_cors:
paths:
'^/api/':
allow_private_network: true

View File

@@ -0,0 +1,21 @@
framework:
rate_limiter:
session_prediction:
policy: 'fixed_window'
limit: 250
interval: '1 hour'
lock_factory: null
reset_password:
policy: 'fixed_window'
limit: 10
interval: '1 hour'
lock_factory: null
when@test:
framework:
rate_limiter:
reset_password:
policy: 'fixed_window'
limit: 100
interval: '1 minute'
lock_factory: null

View File

@@ -8,9 +8,10 @@ scheb_two_factor:
totp:
enabled: true
template: security/2fa.html.twig # Overwritten template
window: 1 # How many codes before/after the current one would be accepted as valid
leeway: 29 # How many seconds the code is valid
issuer: Kimai # Issuer name used in QR code
two_factor_condition: App\Security\TwoFactorCondition
# FIXME add backup codes - https://symfony.com/bundles/SchebTwoFactorBundle/current/backup_codes.html
# TODO add: backup codes - https://symfony.com/bundles/SchebTwoFactorBundle/current/backup_codes.html
# TODO add: Trusted device feature - https://github.com/scheb/2fa/blob/6.x/doc/configuration.rst

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
@@ -110,7 +109,7 @@ tabler:
profile: fas fa-user-edit
profile-stats: far fa-chart-bar
project: fas fa-briefcase
repeat: fas fa-redo-alt
repeat: fas fa-repeat
reporting: far fa-chart-bar
report: far fa-chart-bar
review: fas fa-user-check
@@ -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

@@ -5,7 +5,5 @@ twig:
- 'form/vertical.html.twig'
exception_controller: null
paths:
# used in templates/emails/layout.html.twig
'%kernel.project_dir%/assets/css': css
'%kernel.project_dir%/templates/bundles/TablerBundle': theme
'%kernel.project_dir%/vendor/kevinpapst/tabler-bundle/templates': theme

View File

@@ -2,20 +2,13 @@ controllers:
resource: ../src/Controller/
type: attribute
prefix: /{_locale}
requirements:
_locale: '%app_locales%'
defaults:
_locale: '%locale%'
api.swagger_ui:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
api.swagger:
path: /api/doc.json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger }
defaults: { _controller: nelmio_api_doc.controller.stoplight }
api:
resource: ../src/API/
@@ -31,8 +24,6 @@ security:
resource: ../src/Controller/Security/
type: attribute
prefix: /{_locale}
requirements:
_locale: '%app_locales%'
defaults:
_locale: '%locale%'
@@ -49,8 +40,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,8 +1,7 @@
parameters:
locale: en
# the original list of all locales is the next line, it can be used to regenerate the locale list in case new locales will be added
#app_locales: ar|cs|da|de|el|en|eo|es|eu|fa|fi|fo|fr|he|hr|hu|it|ja|ko|nb_NO|nl|pl|pt|ro|ru|sk|sv|tr|uk|vi|zh_CN|zh_Hant
app_locales: ar|cs|da|de|el|en|eo|es|eu|fa|fi|fo|fr|he|hr|hu|it|ja|ko|nb_NO|nl|pl|pt|pt_BR|ro|ru|sk|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_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_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_NL|en_NR|en_NU|en_NZ|en_PG|en_PH|en_PK|en_PN|en_PR|en_PW|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_EC|es_ES|es_GQ|es_GT|es_HN|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_KP|ko_KR|nl_AW|nl_BE|nl_BQ|nl_CW|nl_NL|nl_SR|nl_SX|pl_PL|pt_AO|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|vi_VN
# can be regenerated with: bin/console kimai:reset:locales
kimai_locales: ['ar', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'ta', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_CZ', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_ES', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_FR', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GS', 'en_GU', 'en_GY', 'en_HK', 'en_HU', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_IT', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NO', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PL', 'en_PN', 'en_PR', 'en_PT', 'en_PW', 'en_RH', 'en_RO', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SK', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN']
services:
# default configuration for services in *this* file
@@ -16,20 +15,24 @@ services:
$projectDirectory: '%kernel.project_dir%'
$kernelEnvironment: '%kernel.environment%'
# make classes in src/ available to be used as services
# creates one service per class whose id is the fully-qualified class name
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude:
- '../src/API/Model/'
- '../src/DependencyInjection/'
- '../src/Doctrine/Extensions/'
- '../src/Entity/'
- '../src/Event/'
- '../src/Export/Package/'
- '../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'
@@ -60,18 +63,6 @@ services:
arguments:
$cacheDirectory: '%kernel.cache_dir%'
App\Plugin\PluginManager:
arguments: [!tagged kimai.plugin]
App\Validator\Constraints\TimesheetValidator:
arguments: [!tagged timesheet.validator]
App\Validator\Constraints\ProjectValidator:
arguments: [!tagged project.validator]
App\Validator\Constraints\QuickEntryTimesheetValidator:
arguments: [!tagged timesheet.validator]
App\Utils\FileHelper:
arguments:
$dataDir: '%kimai.data_dir%'
@@ -83,35 +74,19 @@ services:
arguments:
$mailer: '@App\Mail\KimaiMailer'
# ================================================================================
# DATABASE
# ================================================================================
# updates timesheet records and apply configured rate & rounding rules
App\Doctrine\TimesheetSubscriber:
class: App\Doctrine\TimesheetSubscriber
arguments: [!tagged timesheet.calculator]
# updates timestampable columns (higher priority, so the TimesheetSubscriber will be executed later)
App\Doctrine\ModifiedSubscriber:
class: App\Doctrine\ModifiedSubscriber
# ================================================================================
# TIMESHEET RECORD CALCULATOR
# ================================================================================
App\Timesheet\RoundingService:
arguments:
$roundingModes: !tagged timesheet.rounding_mode
# this is currently required, as local.yaml allows to configure several rules,
# while the database system only allows one rounding rule
$rules: '%kimai.timesheet.rounding%'
App\Timesheet\RateService:
arguments: ['%kimai.timesheet.rates%']
App\Timesheet\TrackingModeService:
arguments:
$modes: !tagged timesheet.tracking_mode
# ================================================================================
# SECURITY & VOTER
# ================================================================================
@@ -129,7 +104,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
@@ -223,3 +200,20 @@ services:
class: App\Repository\WorkingTimeRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\WorkingTime']
App\Repository\AccessTokenRepository:
class: App\Repository\AccessTokenRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments: ['App\Entity\AccessToken']
monolog.formatter.kimai:
class: Monolog\Formatter\LineFormatter
arguments:
- "[%%datetime%%] [%%extra.channel%%] %%level_name%%: %%message%% %%context%% %%extra%%\n"
- "Y-m-d H:i:s"
monolog.formatter.deprecation:
class: Monolog\Formatter\LineFormatter
arguments:
- "[%%datetime%%] %%message%% %%context%%\n"
- "Y-m-d H:i:s"

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]

22
eslint.config.mjs Normal file
View File

@@ -0,0 +1,22 @@
import js from "@eslint/js";
import globals from "globals";
export default [
js.configs.recommended,
{
files: ["assets/js/**/*.js"],
ignores: ["assets/*.js"],
rules: {
eqeqeq: 1,
curly: "error",
semi: ["warn", "always"],
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
...globals.amd,
}
}
},
];

116
kimai.sh Executable file
View File

@@ -0,0 +1,116 @@
#!/bin/bash
# --------------------------------------------------------------------------------
# This script was added with 2.24.0 and is in BETA status.
#
# To improve this script across platforms I need your feedback!
# --------------------------------------------------------------------------------
function update_kimai() {
if [[ "$1" =~ ^([0-9]+\.){2,3}[0-9]+$ ]]; then
export VERSION=$1
else
echo "You need to supply a full Kimai version like: \"2.24.0\""
exit 1
fi
git checkout -- composer.json
git checkout -- composer.lock
git checkout -- symfony.lock
if [[ -n $(git status --porcelain) ]]; then
echo "Cannot update: file changes detected. Run \"git status\" for details."
exit 1
fi
rm -rf var/cache/* 2>&1
git fetch --tags
git checkout "$VERSION"
$KIMAI_PHP "$KIMAI_COMPOSER" install --no-dev --optimize-autoloader
$KIMAI_PHP bin/console kimai:install
install_plugins
if [[ -z "${KIMAI_NO_PERMS}" ]]; then
set_permission
fi
}
function install_plugins() {
# detect if there are additional plugins that we need to install
packages="$($PHP bin/console kimai:plugin --composer)"
export PACKAGES=$packages
if [ -n "$PACKAGES" ]; then
$KIMAI_PHP "$KIMAI_COMPOSER" require "$PACKAGES"
$KIMAI_PHP bin/console kimai:plugins --install
fi
}
function set_permission() {
chown -R "$KIMAI_USER":"$KIMAI_GROUP" .
chmod -R g+r .
chmod -R g+rw var/
}
if [[ -z "${KIMAI_USER}" ]]; then
export KIMAI_USER=""
fi
if [[ -z "${KIMAI_GROUP}" ]]; then
export KIMAI_GROUP="www-data"
fi
if [[ -z "${KIMAI_PHP}" ]]; then
export KIMAI_PHP="php"
fi
if [[ -z "${KIMAI_COMPOSER}" ]]; then
export KIMAI_COMPOSER="composer"
fi
cd "$(dirname "$0")" || { echo "Cannot change working directory."; exit 1; }
# we need a few commands installed in order for this script to complete
command -v $KIMAI_COMPOSER >/dev/null 2>&1 || { echo >&2 "Update requires 'composer' but it's not installed."; exit 1; }
command -v git >/dev/null 2>&1 || { echo >&2 "Update requires 'git' but it's not installed."; exit 1; }
command -v $KIMAI_PHP >/dev/null 2>&1 || { echo >&2 "Update requires 'php' but it's not installed."; exit 1; }
if [[ -n $1 ]]; then
if [ "$1" == 'update' ]; then
update_kimai "$2"
exit
elif [ "$1" == 'permission' ]; then
set_permission
exit
elif [ "$1" == 'plugins' ]; then
install_plugins
exit
else
echo ""
echo ">> Unknown command: $1"
fi
fi
echo ""
echo "This script has the following sub-commands:"
echo ""
echo "$0 update <version> - Install Kimai version <version>"
echo "$0 permission - Fix file permissions"
echo "$0 plugins - Install plugins from var/packages/*.zip"
echo ""
echo "Use the following environment variables to customize the runtime:"
echo ""
echo "KIMAI_USER - Username of the webserver/php process that needs write access"
echo "KIMAI_GROUP - Group of the webserver/php process that needs write access"
echo "KIMAI_PHP - Full path to PHP executable in the correct version"
echo "KIMAI_COMPOSER - Path to composer executable or .phar file"
echo "KIMAI_NO_PERMS - Skip changing permissions"
echo ""
echo "Examples:"
echo ""
echo "$0 2.24.0"
echo "KIMAI_PHP=/usr/bin/php8.3 $0 2.24.0"
echo "KIMAI_PHP=/usr/bin/php8.3 KIMAI_COMPOSER=/tmp/composer.phar $0 2.24.0"
echo "KIMAI_PHP=php8.3 KIMAI_GROUP=httpd $0 2.24.0"
echo "KIMAI_NO_PERMS=1 KIMAI_PHP=/usr/bin/php8.3 $0 2.24.0"
echo ""

View File

@@ -0,0 +1,38 @@
<?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.x
*/
final class <className> extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
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

@@ -23,9 +23,9 @@ final class Version20190219200020 extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql('DELETE FROM kimai2_user_preferences WHERE name = "theme.fixed_layout"');
$this->addSql('DELETE FROM kimai2_user_preferences WHERE name = "theme.boxed_layout"');
$this->addSql('DELETE FROM kimai2_user_preferences WHERE name = "theme.mini_sidebar"');
$this->addSql("DELETE FROM kimai2_user_preferences WHERE name = 'theme.fixed_layout'");
$this->addSql("DELETE FROM kimai2_user_preferences WHERE name = 'theme.boxed_layout'");
$this->addSql("DELETE FROM kimai2_user_preferences WHERE name = 'theme.mini_sidebar'");
}
public function down(Schema $schema): void

View File

@@ -42,7 +42,7 @@ final class Version20230126002049 extends AbstractMigration
$users = $schema->getTable('kimai2_users');
if (!$users->hasColumn('totp_secret')) {
$users->addColumn('totp_secret', 'string', ['notnull' => false, 'default' => null]);
$users->addColumn('totp_secret', 'string', ['length' => 255, 'notnull' => false, 'default' => null]);
$users->addColumn('totp_enabled', 'boolean', ['notnull' => true, 'default' => false]);
}
if (!$users->hasColumn('system_account')) {
@@ -85,7 +85,7 @@ final class Version20230126002049 extends AbstractMigration
$this->addSql("UPDATE kimai2_configuration SET `value` = '15' WHERE `name` = 'timesheet.time_increment' and `value` = '0'");
$this->addSql("UPDATE kimai2_configuration SET `value` = '5' WHERE `name` = 'timesheet.time_increment' and `value` IN ('1', '2', '3', '4')");
$this->addSql("UPDATE kimai2_configuration SET `value` = '0' WHERE `name` = 'user.registration'");
$this->addSql("UPDATE kimai2_roles SET `name` = UPPER(`name`)");
$this->addSql('UPDATE kimai2_roles SET `name` = UPPER(`name`)');
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'service-date' WHERE `renderer` = 'freelancer'");
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'invoice' WHERE `renderer` = 'default'");
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'default' WHERE `renderer` = 'default-pdf'");

View File

@@ -2,6 +2,13 @@
declare(strict_types=1);
/*
* 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;
@@ -44,9 +51,4 @@ final class Version20230327143628 extends AbstractMigration
$workingTimes->removeForeignKey('FK_F95E49334EA3CB3D');
$schema->dropTable('kimai2_working_times');
}
public function isTransactional(): bool
{
return false;
}
}

View File

@@ -2,6 +2,13 @@
declare(strict_types=1);
/*
* 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;
@@ -28,9 +35,4 @@ final class Version20230606125948 extends AbstractMigration
$tags = $schema->getTable('kimai2_tags');
$tags->dropColumn('visible');
}
public function isTransactional(): bool
{
return false;
}
}

View File

@@ -2,10 +2,17 @@
declare(strict_types=1);
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* @version 2.0.31
@@ -33,9 +40,4 @@ final class Version20230819090536 extends AbstractMigration
$table->dropIndex('IDX_B9AC5BCE19E9AC5F');
$table->dropColumn('supervisor_id');
}
public function isTransactional(): bool
{
return false;
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/*
* 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.5.0
*/
final class Version20231130000719 extends AbstractMigration
{
public function getDescription(): string
{
return 'Fix user preferences with dots in the name';
}
public function up(Schema $schema): void
{
// select u1.* from kimai2_user_preferences u1 where u1.name like '%.%' and exists(select 1 from kimai2_user_preferences u2 where u2.name = replace(u1.name, '.', '_') and u1.user_id = u2.user_id);
// select u1.* from kimai2_user_preferences u1 where u1.name like '%.%';
$this->addSql("delete u1 from kimai2_user_preferences u1 where u1.name like '%.%' and exists(select 1 from (SELECT name, user_id FROM kimai2_user_preferences WHERE name LIKE '%_%') u2 where u2.name = replace(u1.name, '.', '_') and u1.user_id = u2.user_id)");
$this->addSql("update kimai2_user_preferences set `name` = replace(`name`, '.', '_')");
}
public function down(Schema $schema): void
{
$this->preventEmptyMigrationWarning();
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* @version 2.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');
}
}

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

@@ -0,0 +1,40 @@
<?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.23
*/
final class Version20240926111739 extends AbstractMigration
{
public function getDescription(): string
{
return 'Adds created_at and break duration column';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE kimai2_activities ADD created_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE kimai2_customers ADD created_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE kimai2_projects ADD created_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE kimai2_timesheet ADD break INT DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE kimai2_timesheet DROP break');
$this->addSql('ALTER TABLE kimai2_projects DROP created_at');
$this->addSql('ALTER TABLE kimai2_customers DROP created_at');
$this->addSql('ALTER TABLE kimai2_activities DROP created_at');
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "kimai2",
"name": "kimai",
"homepage": "https://github.com/kimai/kimai",
"license": "MIT",
"license": "AGPL",
"maintainers": [
{
"name": "Kevin Papst",
@@ -13,42 +13,45 @@
"url": "git+https://github.com/kimai/kimai.git"
},
"scripts": {
"dev-server": "./node_modules/.bin/encore dev-server",
"dev": "./node_modules/.bin/encore dev",
"watch": "./node_modules/.bin/encore dev --watch",
"build": "./node_modules/.bin/encore production",
"prod": "node_modules/.bin/encore production"
"dev-server": "yarn encore dev-server",
"dev": "yarn encore dev",
"watch": "yarn encore dev --watch",
"lint": "yarn eslint assets/js/",
"build": "yarn encore production"
},
"browserslist": [
"defaults"
],
"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.2.0",
"@fullcalendar/bootstrap5": "^5.11",
"@fullcalendar/core": "^5.11",
"@fullcalendar/daygrid": "^5.11",
"@fullcalendar/google-calendar": "^5.11",
"@fullcalendar/icalendar": "^5.11",
"@fullcalendar/interaction": "^5.11",
"@fullcalendar/timegrid": "^5.11",
"@symfony/webpack-encore": "^4.0",
"@tabler/core": "1.0.0-beta18",
"chart.js": "^3.9",
"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-loader": "^13.2",
"tom-select": "^2.2",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-notifier": "^1.13"
}
"dependencies": {
"@babel/core": "^7",
"@babel/eslint-parser": "^7",
"@babel/plugin-syntax-dynamic-import": "^7",
"@babel/preset-env": "^7",
"@eslint/js": "^9",
"@fortawesome/fontawesome-free": "^6",
"@fullcalendar/bootstrap5": "^5",
"@fullcalendar/core": "^5",
"@fullcalendar/daygrid": "^5",
"@fullcalendar/google-calendar": "^5",
"@fullcalendar/icalendar": "^5",
"@fullcalendar/interaction": "^5",
"@fullcalendar/timegrid": "^5",
"@popperjs/core": "^2",
"@symfony/webpack-encore": "^5",
"@tabler/core": "1.0.0-beta21",
"bootstrap": "^5.3",
"chart.js": "^4",
"core-js": "^3",
"eslint": "^9",
"globals": "^15",
"gridstack": "^7",
"litepicker": "^2",
"luxon": "^3",
"sass": "^1",
"sass-loader": "^16",
"tom-select": "^2",
"webpack": "^5",
"webpack-cli": "^5"
},
"packageManager": "yarn@4.5.3"
}

42
php-cs-fixer.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/bash
# ----------------------------------------------------------------------
# Helper script to analyze plugins with PHP Coding Standards Fixer
# ----------------------------------------------------------------------
# Usage:
#
# ./php-cs-fixer.sh [plugin] [params]
# [plugin] - name of the plugin to analyze (without "Bundle")
# [params] - additional parameters for PHPCsFixer, e.g. --dry-run
# ----------------------------------------------------------------------
phpcsfixer() {
vendor/bin/php-cs-fixer fix --config var/plugins/$1Bundle/.php-cs-fixer.dist.php ${ARGS}
}
if [[ -n $2 ]]; then
export ARGS=$2
else
export ARGS=""
fi
if [[ -n $1 ]]; then
if [ -d "var/plugins/$1Bundle/" ]; then
phpcsfixer $1
exit
elif [ "$1" == 'core' ]; then
vendor/bin/php-cs-fixer fix --config .php-cs-fixer.dist.php ${ARGS}
exit
else
echo "Plugin $1 not found"
exit 1
fi
fi
vendor/bin/php-cs-fixer fix --config .php-cs-fixer.dist.php ${ARGS}
for dir in var/plugins/*Bundle/
do
echo ""
vendor/bin/php-cs-fixer fix --config ${dir}.php-cs-fixer.dist.php ${ARGS}
done

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