mirror of
https://github.com/kevinpapst/kimai2.git
synced 2025-04-08 23:10:18 +00:00
Merge branch 'master' into api-user-prefs
This commit is contained in:
commit
5f6fdee86a
514 changed files with 21095 additions and 19703 deletions
.env.distSECURITY.mdUPGRADING.md
assets
composer.jsonconfig
package.jsonpublic/build
app.3947eaef.jsapp.3947eaef.js.LICENSE.txtapp.3bc2b4d9.cssapp.da44b7f8.jsentrypoints.jsonmanifest.json
src
API
Command
Configuration
Controller
DependencyInjection
Entity
EventSubscriber
Form/Type
Invoice
Kernel.phpLdap
Project
Reporting/ProjectDateRange
Saml/User
Security
Twig
Utils
DateFormatConverter.phpLocaleFormats.phpLocaleFormatter.phpMPdfConverter.phpMomentFormatConverter.php
Validator/Constraints
templates
tests
API
Configuration
Controller
Entity
EventSubscriber
Export
Reporting/ProjectDateRange
Security
Twig
Utils
translations
34
.env.dist
34
.env.dist
|
@ -1,30 +1,16 @@
|
||||||
### DATABASE CONFIGURATION
|
# Configure your database connection and set the correct server version:
|
||||||
# Replace "user", "password" and "database" with your database connection.
|
# for MySQL "serverVersion=5.7" and for MariaDB "serverVersion=mariadb-10.5.8"
|
||||||
# Configure the server version, MariaDB requires the "mariadb-" prefix, eg:
|
|
||||||
# for MySQL "serverVersion=5.7" and for MariaDB "serverVersion=mariadb-10.5.8"
|
|
||||||
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7
|
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7
|
||||||
|
# Email will be sent with this address as sender
|
||||||
### EMAIL CONFIGURATION
|
|
||||||
# Emails will be sent "from":
|
|
||||||
MAILER_FROM=kimai@example.com
|
MAILER_FROM=kimai@example.com
|
||||||
|
# Email connection (disabled by default) more info at https://www.kimai.org/documentation/emails.html
|
||||||
# Email connection (disabled by default with MAILER_URL=null://null)
|
|
||||||
# SMTP: smtp://localhost:25?encryption=&auth_mode=
|
|
||||||
# Google: gmail://username:password@default
|
|
||||||
# Amazon: ses://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1
|
|
||||||
# Mailchimp: mandrill://KEY@default
|
|
||||||
# Mailgun: mailgun://KEY:DOMAIN@default
|
|
||||||
# Postmark: postmark://ID@default
|
|
||||||
# Sendgrid: sendgrid://KEY@default
|
|
||||||
# Disable emails: null://null
|
|
||||||
MAILER_URL=null://null
|
MAILER_URL=null://null
|
||||||
|
# do not change, unless you are developing for Kimai
|
||||||
### APPLICATION CONFIGURATION
|
|
||||||
APP_ENV=prod
|
APP_ENV=prod
|
||||||
|
# should be changed to a unique character sequence
|
||||||
APP_SECRET=change_this_to_something_unique
|
APP_SECRET=change_this_to_something_unique
|
||||||
|
# unlikely, that you need to change this one
|
||||||
# Running in a "special" environment, eg. behind reverse proxies?
|
|
||||||
# Check those:
|
|
||||||
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
|
|
||||||
# TRUSTED_HOSTS=localhost,example.com
|
|
||||||
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
|
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
|
||||||
|
# Running behind reverse proxies? Use those:
|
||||||
|
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
|
||||||
|
# TRUSTED_HOSTS=localhost,example.com
|
||||||
|
|
|
@ -12,12 +12,13 @@ As announced in the [README](README.md) I only support the latest available rele
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please report any security related vulnerability in the [advisories section at GitHub](https://github.com/kevinpapst/kimai2/security/advisories) or via email to info@keleo.de or kpapst@gmx.net.
|
Please read the [Bughunter](https://www.kimai.org/documentation/bughunter.html) documentation before posting,
|
||||||
|
and then you can report any security related vulnerability in the [advisories section at GitHub](https://github.com/kevinpapst/kimai2/security/advisories) or via email to kpapst@gmx.net.
|
||||||
|
|
||||||
I will work as fast as I can to fix the problem and publish a bugfix release / security update.
|
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.
|
Depending on the size of the required fixes, this might take a couple of hours or a couple of days.
|
||||||
|
|
||||||
You can expect that your message will be answered ASAP, but please take into account that I am living in the timezone CET.
|
You can expect that your message will be answered ASAP.
|
||||||
|
|
||||||
If your issue is valid and after I verified and fixed it, you will be mentioned in the release notes.
|
If your issue is valid and after I verified and fixed it, you will be mentioned in the release notes.
|
||||||
I am grateful for any (discrete) disclosure of vulnerabilities!
|
I am grateful for any (discrete) disclosure of vulnerabilities!
|
||||||
|
|
|
@ -8,6 +8,13 @@ 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.
|
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.
|
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.16](https://github.com/kevinpapst/kimai2/releases/tag/1.16)
|
||||||
|
|
||||||
|
**DEVELOPER**
|
||||||
|
|
||||||
|
- Removed `formDateTime` field from API model `I18nConfig`
|
||||||
|
|
||||||
## [1.15](https://github.com/kevinpapst/kimai2/releases/tag/1.15)
|
## [1.15](https://github.com/kevinpapst/kimai2/releases/tag/1.15)
|
||||||
|
|
||||||
**Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
**Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||||
|
|
|
@ -34,6 +34,7 @@ export default class KimaiDatePicker extends KimaiPlugin {
|
||||||
singleDatePicker: true,
|
singleDatePicker: true,
|
||||||
showDropdowns: true,
|
showDropdowns: true,
|
||||||
autoUpdateInput: false,
|
autoUpdateInput: false,
|
||||||
|
drops: 'down',
|
||||||
locale: {
|
locale: {
|
||||||
format: localeFormat,
|
format: localeFormat,
|
||||||
firstDay: firstDow,
|
firstDay: firstDow,
|
||||||
|
@ -47,7 +48,8 @@ export default class KimaiDatePicker extends KimaiPlugin {
|
||||||
|
|
||||||
jQuery(this).on('show.daterangepicker', function (ev, picker) {
|
jQuery(this).on('show.daterangepicker', function (ev, picker) {
|
||||||
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
|
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
|
||||||
picker.drops = 'up';
|
// "up" is not possible here, because the code is triggered on many mobile phones and the picker then appears out of window
|
||||||
|
picker.drops = 'auto';
|
||||||
picker.move();
|
picker.move();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,6 +52,7 @@ export default class KimaiDateRangePicker extends KimaiPlugin {
|
||||||
autoUpdateInput: false,
|
autoUpdateInput: false,
|
||||||
autoApply: false,
|
autoApply: false,
|
||||||
linkedCalendars: true,
|
linkedCalendars: true,
|
||||||
|
drops: 'down',
|
||||||
locale: {
|
locale: {
|
||||||
separator: separator,
|
separator: separator,
|
||||||
format: localeFormat,
|
format: localeFormat,
|
||||||
|
@ -68,7 +69,8 @@ export default class KimaiDateRangePicker extends KimaiPlugin {
|
||||||
|
|
||||||
jQuery(this).on('show.daterangepicker', function (ev, picker) {
|
jQuery(this).on('show.daterangepicker', function (ev, picker) {
|
||||||
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
|
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
|
||||||
picker.drops = 'up';
|
// "up" is not possible here, because the code is triggered on many mobile phones and the picker then appears out of window
|
||||||
|
picker.drops = 'auto';
|
||||||
picker.move();
|
picker.move();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ export default class KimaiDateTimePicker extends KimaiPlugin {
|
||||||
timePicker24Hour: is24hours,
|
timePicker24Hour: is24hours,
|
||||||
showDropdowns: true,
|
showDropdowns: true,
|
||||||
autoUpdateInput: false,
|
autoUpdateInput: false,
|
||||||
|
drops: 'down',
|
||||||
locale: {
|
locale: {
|
||||||
format: localeFormat,
|
format: localeFormat,
|
||||||
firstDay: firstDow,
|
firstDay: firstDow,
|
||||||
|
@ -50,7 +51,8 @@ export default class KimaiDateTimePicker extends KimaiPlugin {
|
||||||
|
|
||||||
jQuery(this).on('show.daterangepicker', function (ev, picker) {
|
jQuery(this).on('show.daterangepicker', function (ev, picker) {
|
||||||
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
|
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
|
||||||
picker.drops = 'up';
|
// "up" is not possible here, because the code is triggered on many mobile phones and the picker then appears out of window
|
||||||
|
picker.drops = 'auto';
|
||||||
picker.move();
|
picker.move();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,4 +12,44 @@ form.form-narrow {
|
||||||
.form-group {
|
.form-group {
|
||||||
margin: 0 0 5px 0;
|
margin: 0 0 5px 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bootstrap3 hack because the filter look plain ugly without it (see report: project month) */
|
||||||
|
.checkbox-menu li label {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 15px 5px 10px !important;
|
||||||
|
clear: both;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
color: #333;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin:0;
|
||||||
|
transition: background-color .4s ease;
|
||||||
|
}
|
||||||
|
.checkbox-menu li div.checkbox {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.checkbox-menu li div.radio {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.checkbox-menu li input {
|
||||||
|
margin: 0 5px !important;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-menu li.active label {
|
||||||
|
background-color: #cbcbff;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-menu li label:hover,
|
||||||
|
.checkbox-menu li label:focus {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-menu li.active label:hover,
|
||||||
|
.checkbox-menu li.active label:focus {
|
||||||
|
background-color: #b8b8ff;
|
||||||
}
|
}
|
|
@ -165,7 +165,8 @@
|
||||||
"bin/console lint:xliff translations",
|
"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"
|
||||||
],
|
],
|
||||||
"kimai:tests": "vendor/bin/phpunit tests/",
|
"tests": "vendor/bin/phpunit tests/",
|
||||||
|
"kimai:tests": "@tests",
|
||||||
"kimai:tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
|
"kimai:tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
|
||||||
"kimai:tests-integration": "vendor/bin/phpunit --group integration tests/",
|
"kimai:tests-integration": "vendor/bin/phpunit --group integration tests/",
|
||||||
"kimai:phpstan": "@phpstan",
|
"kimai:phpstan": "@phpstan",
|
||||||
|
|
|
@ -91,7 +91,8 @@ kimai:
|
||||||
CUSTOMERS_TEAMLEAD: ['view_teamlead_customer','budget_teamlead_customer','comments_teamlead_customer','comments_create_teamlead_customer','details_teamlead_customer']
|
CUSTOMERS_TEAMLEAD: ['view_teamlead_customer','budget_teamlead_customer','comments_teamlead_customer','comments_create_teamlead_customer','details_teamlead_customer']
|
||||||
INVOICE: ['view_invoice','create_invoice']
|
INVOICE: ['view_invoice','create_invoice']
|
||||||
INVOICE_ADMIN: ['manage_invoice_template']
|
INVOICE_ADMIN: ['manage_invoice_template']
|
||||||
TIMESHEET: ['view_own_timesheet','start_own_timesheet','stop_own_timesheet','create_own_timesheet','edit_own_timesheet','export_own_timesheet','delete_own_timesheet']
|
INVOICE_ALL: ['delete_invoice']
|
||||||
|
TIMESHEET: ['view_own_timesheet','start_own_timesheet','stop_own_timesheet','create_own_timesheet','edit_own_timesheet','export_own_timesheet','delete_own_timesheet','weekly_own_timesheet']
|
||||||
TIMESHEET_OTHER: ['view_other_timesheet','start_other_timesheet','stop_other_timesheet','create_other_timesheet','edit_other_timesheet','export_other_timesheet','delete_other_timesheet']
|
TIMESHEET_OTHER: ['view_other_timesheet','start_other_timesheet','stop_other_timesheet','create_other_timesheet','edit_other_timesheet','export_other_timesheet','delete_other_timesheet']
|
||||||
PROFILE: ['view_own_profile','edit_own_profile','password_own_profile','preferences_own_profile','api-token_own_profile']
|
PROFILE: ['view_own_profile','edit_own_profile','password_own_profile','preferences_own_profile','api-token_own_profile']
|
||||||
PROFILE_OTHER: ['view_other_profile','edit_other_profile','password_other_profile','roles_other_profile','preferences_other_profile','api-token_other_profile','teams_other_profile']
|
PROFILE_OTHER: ['view_other_profile','edit_other_profile','password_other_profile','roles_other_profile','preferences_other_profile','api-token_other_profile','teams_other_profile']
|
||||||
|
@ -120,7 +121,7 @@ kimai:
|
||||||
ROLE_ADMIN: ['ROLE_ADMIN']
|
ROLE_ADMIN: ['ROLE_ADMIN']
|
||||||
ROLE_SUPER_ADMIN: ['ROLE_SUPER_ADMIN']
|
ROLE_SUPER_ADMIN: ['ROLE_SUPER_ADMIN']
|
||||||
# only here to register the (partially) unused permissions in the UI
|
# only here to register the (partially) unused permissions in the UI
|
||||||
ROLE_FAKE: ['CUSTOMERS_ALL_TEAMLEAD','CUSTOMERS_ALL_TEAM','PROJECTS_ALL_TEAMLEAD','PROJECTS_ALL_TEAM','ACTIVITIES_ALL_TEAMLEAD','ACTIVITIES_ALL_TEAM']
|
ROLE_FAKE: ['CUSTOMERS_ALL_TEAMLEAD','CUSTOMERS_ALL_TEAM','PROJECTS_ALL_TEAMLEAD','PROJECTS_ALL_TEAM','ACTIVITIES_ALL_TEAMLEAD','ACTIVITIES_ALL_TEAM','INVOICE_ALL']
|
||||||
# add or remove single permissions
|
# add or remove single permissions
|
||||||
roles:
|
roles:
|
||||||
ROLE_USER: []
|
ROLE_USER: []
|
||||||
|
@ -214,91 +215,74 @@ kimai:
|
||||||
# --------------------------------------------------------------------------------
|
# --------------------------------------------------------------------------------
|
||||||
languages:
|
languages:
|
||||||
cs:
|
cs:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m H:i'
|
date_time: 'd.m H:i'
|
||||||
da:
|
da:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
de:
|
de:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
de_AT:
|
de_AT:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
de_CH:
|
de_CH:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
el:
|
el:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
en:
|
en:
|
||||||
date_time_type: 'yyyy-MM-dd HH:mm'
|
|
||||||
date_type: 'yyyy-MM-dd'
|
date_type: 'yyyy-MM-dd'
|
||||||
date: 'Y-m-d'
|
date: 'Y-m-d'
|
||||||
date_time: 'm-d H:i'
|
date_time: 'm-d H:i'
|
||||||
duration: '%%h:%%m h'
|
duration: '%%h:%%m h'
|
||||||
es:
|
es:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
fi:
|
fi:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
fr:
|
fr:
|
||||||
date_time_type: 'dd/MM/yyyy HH:mm'
|
|
||||||
date_type: 'dd/MM/yyyy'
|
date_type: 'dd/MM/yyyy'
|
||||||
date: 'd/m/Y'
|
date: 'd/m/Y'
|
||||||
date_time: 'd/m H:i'
|
date_time: 'd/m H:i'
|
||||||
duration: '%%h h %%m'
|
duration: '%%h h %%m'
|
||||||
he:
|
he:
|
||||||
date_time_type: 'dd/MM/yyyy HH:mm'
|
|
||||||
date_type: 'dd/MM/yyyy'
|
date_type: 'dd/MM/yyyy'
|
||||||
date: 'd/m/Y'
|
date: 'd/m/Y'
|
||||||
date_time: 'd/m H:i'
|
date_time: 'd/m H:i'
|
||||||
duration: '%%h:%%m'
|
duration: '%%h:%%m'
|
||||||
hu:
|
hu:
|
||||||
date_time_type: 'yyyy.MM.dd. HH:mm'
|
|
||||||
date_type: 'yyyy.MM.dd.'
|
date_type: 'yyyy.MM.dd.'
|
||||||
date: 'Y.m.d.'
|
date: 'Y.m.d.'
|
||||||
date_time: 'm.d. H:i'
|
date_time: 'm.d. H:i'
|
||||||
it:
|
it:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
nl:
|
nl:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
duration: '%%hu%%m'
|
duration: '%%hu%%m'
|
||||||
pt_BR:
|
pt_BR:
|
||||||
date_time_type: 'dd-MM-yyyy HH:mm'
|
|
||||||
date_type: 'dd-MM-yyyy'
|
date_type: 'dd-MM-yyyy'
|
||||||
date: 'd-m-Y'
|
date: 'd-m-Y'
|
||||||
date_time: 'd-m H:i'
|
date_time: 'd-m H:i'
|
||||||
ru:
|
ru:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
sk:
|
sk:
|
||||||
date_time_type: 'dd. MM. yyyy HH:mm'
|
|
||||||
date_type: 'dd. MM. yyyy'
|
date_type: 'dd. MM. yyyy'
|
||||||
date: 'd. m. Y'
|
date: 'd. m. Y'
|
||||||
date_time: 'd. m. H:i'
|
date_time: 'd. m. H:i'
|
||||||
|
@ -306,7 +290,6 @@ kimai:
|
||||||
duration: '%%h:%%m tim'
|
duration: '%%h:%%m tim'
|
||||||
date_time: 'd/m H:i'
|
date_time: 'd/m H:i'
|
||||||
pl:
|
pl:
|
||||||
date_time_type: 'dd. MM. yyyy HH:mm'
|
|
||||||
date_type: 'dd. MM. yyyy'
|
date_type: 'dd. MM. yyyy'
|
||||||
date: 'd. m. Y'
|
date: 'd. m. Y'
|
||||||
date_time: 'd. m. H:i'
|
date_time: 'd. m. H:i'
|
||||||
|
|
|
@ -65,6 +65,9 @@ services:
|
||||||
App\Validator\Constraints\TimesheetValidator:
|
App\Validator\Constraints\TimesheetValidator:
|
||||||
arguments: [!tagged timesheet.validator]
|
arguments: [!tagged timesheet.validator]
|
||||||
|
|
||||||
|
App\Validator\Constraints\ProjectValidator:
|
||||||
|
arguments: [!tagged project.validator]
|
||||||
|
|
||||||
App\Validator\Constraints\QuickEntryTimesheetValidator:
|
App\Validator\Constraints\QuickEntryTimesheetValidator:
|
||||||
arguments: [!tagged timesheet.validator]
|
arguments: [!tagged timesheet.validator]
|
||||||
|
|
||||||
|
|
|
@ -48,5 +48,6 @@
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"defaults"
|
"defaults"
|
||||||
]
|
],
|
||||||
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|
2
public/build/app.3947eaef.js
Normal file
2
public/build/app.3947eaef.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -3,10 +3,10 @@
|
||||||
"app": {
|
"app": {
|
||||||
"js": [
|
"js": [
|
||||||
"build/runtime.b8e7bb04.js",
|
"build/runtime.b8e7bb04.js",
|
||||||
"build/app.da44b7f8.js"
|
"build/app.3947eaef.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"build/app.d2b280dd.css"
|
"build/app.3bc2b4d9.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"invoice": {
|
"invoice": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"build/app.css": "build/app.d2b280dd.css",
|
"build/app.css": "build/app.3bc2b4d9.css",
|
||||||
"build/app.js": "build/app.da44b7f8.js",
|
"build/app.js": "build/app.3947eaef.js",
|
||||||
"build/invoice.css": "build/invoice.ff32661a.css",
|
"build/invoice.css": "build/invoice.ff32661a.css",
|
||||||
"build/invoice.js": "build/invoice.19f36eca.js",
|
"build/invoice.js": "build/invoice.19f36eca.js",
|
||||||
"build/invoice-pdf.css": "build/invoice-pdf.9a7468ef.css",
|
"build/invoice-pdf.css": "build/invoice-pdf.9a7468ef.css",
|
||||||
|
|
|
@ -64,13 +64,12 @@ final class ConfigurationController extends BaseApiController
|
||||||
|
|
||||||
$model = new I18nConfig();
|
$model = new I18nConfig();
|
||||||
$model
|
$model
|
||||||
->setFormDateTime($formats->getDateTimeTypeFormat($locale))
|
|
||||||
->setFormDate($formats->getDateTypeFormat($locale))
|
->setFormDate($formats->getDateTypeFormat($locale))
|
||||||
->setDateTime($formats->getDateTimeFormat($locale))
|
->setDateTime($formats->getDateTimeFormat($locale))
|
||||||
->setDate($formats->getDateFormat($locale))
|
->setDate($formats->getDateFormat($locale))
|
||||||
->setDuration($formats->getDurationFormat($locale))
|
->setDuration($formats->getDurationFormat($locale))
|
||||||
->setTime($formats->getTimeFormat($locale))
|
->setTime($formats->getTimeFormat($locale))
|
||||||
->setIs24hours($formats->isTwentyFourHours($locale))
|
->setIs24hours($user->is24Hour())
|
||||||
->setNow($this->getDateTimeFactory()->createDateTime())
|
->setNow($this->getDateTimeFactory()->createDateTime())
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
|
@ -18,17 +18,6 @@ use JMS\Serializer\Annotation as Serializer;
|
||||||
*/
|
*/
|
||||||
final class I18nConfig
|
final class I18nConfig
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Format used for 'begin' and 'end'
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @Serializer\Expose()
|
|
||||||
* @Serializer\Groups({"Default"})
|
|
||||||
* @Serializer\Type(name="string")
|
|
||||||
* @phpstan-ignore-next-line
|
|
||||||
*/
|
|
||||||
private $formDateTime = '';
|
|
||||||
/**
|
/**
|
||||||
* Format used for toolbar queries
|
* Format used for toolbar queries
|
||||||
*
|
*
|
||||||
|
@ -114,13 +103,6 @@ final class I18nConfig
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setFormDateTime(string $formDateTime): I18nConfig
|
|
||||||
{
|
|
||||||
$this->formDateTime = $formDateTime;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFormDate(string $formDate): I18nConfig
|
public function setFormDate(string $formDate): I18nConfig
|
||||||
{
|
{
|
||||||
$this->formDate = $formDate;
|
$this->formDate = $formDate;
|
||||||
|
|
|
@ -63,6 +63,7 @@ class InvoiceCreateCommand extends Command
|
||||||
* @var string|null
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
private $previewDirectory;
|
private $previewDirectory;
|
||||||
|
private $previewUniqueFile = false;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ServiceInvoice $serviceInvoice,
|
ServiceInvoice $serviceInvoice,
|
||||||
|
@ -104,6 +105,7 @@ class InvoiceCreateCommand extends Command
|
||||||
->addOption('search', null, InputOption::VALUE_OPTIONAL, 'Search term to filter invoice entries', null)
|
->addOption('search', null, InputOption::VALUE_OPTIONAL, 'Search term to filter invoice entries', null)
|
||||||
->addOption('exported', null, InputOption::VALUE_OPTIONAL, 'Exported filter for invoice entries (possible values: exported, all), by default only "not exported" items are fetched', null)
|
->addOption('exported', null, InputOption::VALUE_OPTIONAL, 'Exported filter for invoice entries (possible values: exported, all), by default only "not exported" items are fetched', null)
|
||||||
->addOption('preview', null, InputOption::VALUE_OPTIONAL, 'Absolute path for a rendered preview of the invoice, which will neither be saved nor the items be marked as exported.', null)
|
->addOption('preview', null, InputOption::VALUE_OPTIONAL, 'Absolute path for a rendered preview of the invoice, which will neither be saved nor the items be marked as exported.', null)
|
||||||
|
->addOption('preview-unique', null, InputOption::VALUE_NONE, 'Adds a unique part to the filename of the generated invoice preview file, so there is no chance that they get overwritten on same project name.')
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +227,7 @@ class InvoiceCreateCommand extends Command
|
||||||
|
|
||||||
$markAsExported = false;
|
$markAsExported = false;
|
||||||
if ($input->getOption('preview') !== null) {
|
if ($input->getOption('preview') !== null) {
|
||||||
|
$this->previewUniqueFile = $input->getOption('preview-unique');
|
||||||
$this->previewDirectory = rtrim($input->getOption('preview'), '/') . '/';
|
$this->previewDirectory = rtrim($input->getOption('preview'), '/') . '/';
|
||||||
if (!is_dir($this->previewDirectory) || !is_writable($this->previewDirectory)) {
|
if (!is_dir($this->previewDirectory) || !is_writable($this->previewDirectory)) {
|
||||||
$io->error('Invalid preview directory given');
|
$io->error('Invalid preview directory given');
|
||||||
|
@ -350,8 +353,9 @@ class InvoiceCreateCommand extends Command
|
||||||
$filename = $filename[1];
|
$filename = $filename[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// depending on your setup, this might be a good idea
|
if ($this->previewUniqueFile) {
|
||||||
// $filename = uniqid() . $filename;
|
$filename = uniqid('invoice_') . $filename;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($response instanceof BinaryFileResponse) {
|
if ($response instanceof BinaryFileResponse) {
|
||||||
|
|
|
@ -60,28 +60,6 @@ final class LanguageFormattings
|
||||||
return $this->momentFormatter->convert($this->getDateTypeFormat($locale));
|
return $this->momentFormatter->convert($this->getDateTypeFormat($locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the format which is used by the form component to handle datetime values.
|
|
||||||
*
|
|
||||||
* @param string $locale
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDateTimeTypeFormat(string $locale): string
|
|
||||||
{
|
|
||||||
return $this->getConfig('date_time_type', $locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the format which is used by the Javascript component to handle datetime values.
|
|
||||||
*
|
|
||||||
* @param string $locale
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDateTimePickerFormat(string $locale): string
|
|
||||||
{
|
|
||||||
return $this->momentFormatter->convert($this->getDateTimeTypeFormat($locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the locale specific date format, which should be used in combination with the twig filter "|date".
|
* Returns the locale specific date format, which should be used in combination with the twig filter "|date".
|
||||||
*
|
*
|
||||||
|
@ -126,17 +104,6 @@ final class LanguageFormattings
|
||||||
return $this->getConfig('duration', $locale);
|
return $this->getConfig('duration', $locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether this locale uses the 24 hour format.
|
|
||||||
*
|
|
||||||
* @param string $locale
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isTwentyFourHours(string $locale): bool
|
|
||||||
{
|
|
||||||
return (bool) $this->getConfig('24_hours', $locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @param string $locale
|
* @param string $locale
|
||||||
|
|
|
@ -15,6 +15,8 @@ use PackageVersions\Versions;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||||
|
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route(path="/doctor")
|
* @Route(path="/doctor")
|
||||||
|
@ -56,11 +58,19 @@ class DoctorController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route(path="/flush-log", name="doctor_flush_log", methods={"GET"})
|
* @Route(path="/flush-log/{token}", name="doctor_flush_log", methods={"GET"})
|
||||||
* @Security("is_granted('system_configuration')")
|
* @Security("is_granted('system_configuration')")
|
||||||
*/
|
*/
|
||||||
public function deleteLogfileAction(): Response
|
public function deleteLogfileAction(string $token, CsrfTokenManagerInterface $csrfTokenManager): Response
|
||||||
{
|
{
|
||||||
|
if (!$csrfTokenManager->isTokenValid(new CsrfToken('doctor.flush_log', $token))) {
|
||||||
|
$this->flashError('action.delete.error');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('doctor');
|
||||||
|
}
|
||||||
|
|
||||||
|
$csrfTokenManager->refreshToken($token);
|
||||||
|
|
||||||
$logfile = $this->getLogFilename();
|
$logfile = $this->getLogFilename();
|
||||||
|
|
||||||
if (file_exists($logfile)) {
|
if (file_exists($logfile)) {
|
||||||
|
|
|
@ -144,6 +144,7 @@ class ExportController extends AbstractController
|
||||||
/**
|
/**
|
||||||
* @param ExportQuery $query
|
* @param ExportQuery $query
|
||||||
* @return ExportItemInterface[]
|
* @return ExportItemInterface[]
|
||||||
|
* @throws TooManyItemsExportException
|
||||||
*/
|
*/
|
||||||
protected function getEntries(ExportQuery $query): array
|
protected function getEntries(ExportQuery $query): array
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,7 +25,7 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||||
* Controller used to enter times in weekly form.
|
* Controller used to enter times in weekly form.
|
||||||
*
|
*
|
||||||
* @Route(path="/quick_entry")
|
* @Route(path="/quick_entry")
|
||||||
* @Security("is_granted('edit_own_timesheet')")
|
* @Security("is_granted('weekly_own_timesheet') and is_granted('edit_own_timesheet')")
|
||||||
*/
|
*/
|
||||||
class QuickEntryController extends AbstractController
|
class QuickEntryController extends AbstractController
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,7 +33,7 @@ final class ProjectDateRangeController extends AbstractController
|
||||||
$form = $this->createForm(ProjectDateRangeForm::class, $query, [
|
$form = $this->createForm(ProjectDateRangeForm::class, $query, [
|
||||||
'timezone' => $user->getTimezone()
|
'timezone' => $user->getTimezone()
|
||||||
]);
|
]);
|
||||||
$form->submit($request->query->all(), false);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
$dateRange = new DateRange(true);
|
$dateRange = new DateRange(true);
|
||||||
$dateRange->setBegin($query->getMonth());
|
$dateRange->setBegin($query->getMonth());
|
||||||
|
|
|
@ -329,13 +329,19 @@ class Configuration implements ConfigurationInterface
|
||||||
->useAttributeAsKey('name', false) // see https://github.com/symfony/symfony/issues/18988
|
->useAttributeAsKey('name', false) // see https://github.com/symfony/symfony/issues/18988
|
||||||
->arrayPrototype()
|
->arrayPrototype()
|
||||||
->children()
|
->children()
|
||||||
->scalarNode('date_time_type')->defaultValue('yyyy-MM-dd HH:mm')->end() // for DateTimeType
|
->scalarNode('date_time_type') // for DateTimeType
|
||||||
|
->defaultValue('yyyy-MM-dd HH:mm')
|
||||||
|
->setDeprecated('date_time_type is deprecated since 1.16 and was replaced by the 24 user configuration')
|
||||||
|
->end()
|
||||||
->scalarNode('date_type')->defaultValue('yyyy-MM-dd')->end() // for DateType
|
->scalarNode('date_type')->defaultValue('yyyy-MM-dd')->end() // for DateType
|
||||||
->scalarNode('date')->defaultValue('Y-m-d')->end() // for display via twig
|
->scalarNode('date')->defaultValue('Y-m-d')->end() // for display via twig
|
||||||
->scalarNode('date_time')->defaultValue('m-d H:i')->end() // for display via twig
|
->scalarNode('date_time')->defaultValue('m-d H:i')->end() // for display via twig
|
||||||
->scalarNode('duration')->defaultValue('%%h:%%m h')->end() // for display via twig
|
->scalarNode('duration')->defaultValue('%%h:%%m h')->end() // for display via twig
|
||||||
->scalarNode('time')->defaultValue('H:i')->end() // for display via twig
|
->scalarNode('time')->defaultValue('H:i')->end() // for display via twig
|
||||||
->booleanNode('24_hours')->defaultTrue()->end() // for DateTimeType JS component
|
->booleanNode('24_hours') // for DateTimeType JS component
|
||||||
|
->defaultTrue()
|
||||||
|
->setDeprecated('24_hours is deprecated since 1.16 and a user configuration now')
|
||||||
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Invoice
|
||||||
{
|
{
|
||||||
public const STATUS_PENDING = 'pending';
|
public const STATUS_PENDING = 'pending';
|
||||||
public const STATUS_PAID = 'paid';
|
public const STATUS_PAID = 'paid';
|
||||||
|
public const STATUS_CANCELED = 'canceled';
|
||||||
public const STATUS_NEW = 'new';
|
public const STATUS_NEW = 'new';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -306,6 +307,16 @@ class Invoice
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isCanceled(): bool
|
||||||
|
{
|
||||||
|
return $this->status === self::STATUS_CANCELED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsCanceled(): void
|
||||||
|
{
|
||||||
|
$this->status = self::STATUS_CANCELED;
|
||||||
|
}
|
||||||
|
|
||||||
public function getDueDays(): int
|
public function getDueDays(): int
|
||||||
{
|
{
|
||||||
return $this->dueDays;
|
return $this->dueDays;
|
||||||
|
|
|
@ -455,6 +455,20 @@ class User implements UserInterface, EquatableInterface, \Serializable
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTimeFormat(): string
|
||||||
|
{
|
||||||
|
if ($this->is24Hour()) {
|
||||||
|
return 'H:i';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'h:i A';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function is24Hour(): bool
|
||||||
|
{
|
||||||
|
return (bool) $this->getPreferenceValue(UserPreference::HOUR_24, true);
|
||||||
|
}
|
||||||
|
|
||||||
public function getLocale(): string
|
public function getLocale(): string
|
||||||
{
|
{
|
||||||
return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE);
|
return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE);
|
||||||
|
|
|
@ -33,6 +33,7 @@ class UserPreference
|
||||||
public const INTERNAL_RATE = 'internal_rate';
|
public const INTERNAL_RATE = 'internal_rate';
|
||||||
public const SKIN = 'skin';
|
public const SKIN = 'skin';
|
||||||
public const LOCALE = 'language';
|
public const LOCALE = 'language';
|
||||||
|
public const HOUR_24 = 'hours_24';
|
||||||
public const TIMEZONE = 'timezone';
|
public const TIMEZONE = 'timezone';
|
||||||
public const FIRST_WEEKDAY = 'first_weekday';
|
public const FIRST_WEEKDAY = 'first_weekday';
|
||||||
|
|
||||||
|
|
|
@ -30,13 +30,24 @@ class InvoiceSubscriber extends AbstractActionsSubscriber
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($invoice->isNew() || $invoice->isPaid()) {
|
if (!$invoice->isPending()) {
|
||||||
$event->addAction('invoice.pending', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'pending'])]);
|
$event->addAction('invoice.pending', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'pending'])]);
|
||||||
} elseif ($invoice->isPending()) {
|
} else {
|
||||||
$event->addAction('invoice.paid', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'paid']), 'class' => 'modal-ajax-form']);
|
$event->addAction('invoice.paid', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'paid']), 'class' => 'modal-ajax-form']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allowDelete = $this->isGranted('delete_invoice');
|
||||||
|
if (!$invoice->isCanceled()) {
|
||||||
|
$id = $allowDelete ? 'invoice.cancel' : 'trash';
|
||||||
|
$event->addAction($id, ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'canceled']), 'title' => 'invoice.cancel', 'translation_domain' => 'actions']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->addDivider();
|
||||||
|
|
||||||
$event->addAction('download', ['url' => $this->path('admin_invoice_download', ['id' => $invoice->getId()]), 'target' => '_blank']);
|
$event->addAction('download', ['url' => $this->path('admin_invoice_download', ['id' => $invoice->getId()]), 'target' => '_blank']);
|
||||||
$event->addDelete($this->path('admin_invoice_delete', ['id' => $invoice->getId(), 'token' => $payload['token']]), false);
|
|
||||||
|
if ($this->isGranted('delete_invoice')) {
|
||||||
|
$event->addDelete($this->path('admin_invoice_delete', ['id' => $invoice->getId(), 'token' => $payload['token']]), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ final class MenuSubscriber implements EventSubscriberInterface
|
||||||
$timesheets->setChildRoutes(['timesheet_export', 'timesheet_edit', 'timesheet_create', 'timesheet_multi_update']);
|
$timesheets->setChildRoutes(['timesheet_export', 'timesheet_edit', 'timesheet_create', 'timesheet_multi_update']);
|
||||||
$menu->addItem($timesheets);
|
$menu->addItem($timesheets);
|
||||||
|
|
||||||
if ($auth->isGranted('edit_own_timesheet')) {
|
if ($auth->isGranted('weekly_own_timesheet') && $auth->isGranted('edit_own_timesheet')) {
|
||||||
$mode = $this->trackingModeService->getActiveMode();
|
$mode = $this->trackingModeService->getActiveMode();
|
||||||
if ($mode->canEditDuration() || $mode->canEditEnd()) {
|
if ($mode->canEditDuration() || $mode->canEditEnd()) {
|
||||||
$menu->addItem(
|
$menu->addItem(
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace App\EventSubscriber;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Entity\UserPreference;
|
use App\Entity\UserPreference;
|
||||||
|
use App\Form\Type\SkinType;
|
||||||
use KevinPapst\AdminLTEBundle\Helper\ContextHelper;
|
use KevinPapst\AdminLTEBundle\Helper\ContextHelper;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||||
|
@ -70,7 +71,7 @@ final class ThemeOptionsSubscriber implements EventSubscriberInterface
|
||||||
$name = $ref->getName();
|
$name = $ref->getName();
|
||||||
switch ($name) {
|
switch ($name) {
|
||||||
case UserPreference::SKIN:
|
case UserPreference::SKIN:
|
||||||
if (!empty($ref->getValue())) {
|
if (!empty($ref->getValue()) && \in_array($ref->getValue(), SkinType::THEMES)) {
|
||||||
$this->helper->setOption('skin', 'skin-' . $ref->getValue());
|
$this->helper->setOption('skin', 'skin-' . $ref->getValue());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -79,7 +80,7 @@ final class ThemeOptionsSubscriber implements EventSubscriberInterface
|
||||||
if ($ref->getValue() === 'boxed') {
|
if ($ref->getValue() === 'boxed') {
|
||||||
$this->helper->setOption('boxed_layout', true);
|
$this->helper->setOption('boxed_layout', true);
|
||||||
$this->helper->setOption('fixed_layout', false);
|
$this->helper->setOption('fixed_layout', false);
|
||||||
} elseif ($ref->getValue() === 'fixed') {
|
} else {
|
||||||
$this->helper->setOption('boxed_layout', false);
|
$this->helper->setOption('boxed_layout', false);
|
||||||
$this->helper->setOption('fixed_layout', true);
|
$this->helper->setOption('fixed_layout', true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,13 @@ final class UserPreferenceSubscriber implements EventSubscriberInterface
|
||||||
->setSection('locale')
|
->setSection('locale')
|
||||||
->setType(FirstWeekDayType::class),
|
->setType(FirstWeekDayType::class),
|
||||||
|
|
||||||
|
(new UserPreference())
|
||||||
|
->setName(UserPreference::HOUR_24)
|
||||||
|
->setValue(true)
|
||||||
|
->setOrder(305)
|
||||||
|
->setSection('locale')
|
||||||
|
->setType(CheckboxType::class),
|
||||||
|
|
||||||
(new UserPreference())
|
(new UserPreference())
|
||||||
->setName(UserPreference::SKIN)
|
->setName(UserPreference::SKIN)
|
||||||
->setValue($this->configuration->getUserDefaultTheme())
|
->setValue($this->configuration->getUserDefaultTheme())
|
||||||
|
|
|
@ -18,6 +18,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
*/
|
*/
|
||||||
class BudgetType extends AbstractType
|
class BudgetType extends AbstractType
|
||||||
{
|
{
|
||||||
|
public const TYPE_MONTH = 'month';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -25,9 +27,12 @@ class BudgetType extends AbstractType
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'label' => 'label.budgetType',
|
'label' => 'label.budgetType',
|
||||||
|
// not yet translated in enough languages
|
||||||
|
//'placeholder' => 'label.budgetType_full',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
'search' => false,
|
||||||
'choices' => [
|
'choices' => [
|
||||||
'label.budgetType_month' => 'month',
|
'label.budgetType_month' => self::TYPE_MONTH,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,15 @@
|
||||||
namespace App\Form\Type;
|
namespace App\Form\Type;
|
||||||
|
|
||||||
use App\API\BaseApiController;
|
use App\API\BaseApiController;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Utils\DateFormatConverter;
|
||||||
use App\Utils\LocaleSettings;
|
use App\Utils\LocaleSettings;
|
||||||
|
use App\Utils\MomentFormatConverter;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\Form\FormView;
|
use Symfony\Component\Form\FormView;
|
||||||
|
use Symfony\Component\OptionsResolver\Options;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,9 +38,6 @@ class DateTimePickerType extends AbstractType
|
||||||
*/
|
*/
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
$dateTimePicker = $this->localeSettings->getDateTimePickerFormat();
|
|
||||||
$dateTimeFormat = $this->localeSettings->getDateTimeTypeFormat();
|
|
||||||
|
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'documentation' => [
|
'documentation' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
|
@ -46,8 +47,18 @@ class DateTimePickerType extends AbstractType
|
||||||
'label' => 'label.begin',
|
'label' => 'label.begin',
|
||||||
'widget' => 'single_text',
|
'widget' => 'single_text',
|
||||||
'html5' => false,
|
'html5' => false,
|
||||||
'format' => $dateTimeFormat,
|
'format' => function (Options $options) {
|
||||||
'format_picker' => $dateTimePicker,
|
/** @var User $user */
|
||||||
|
$user = $options['user'];
|
||||||
|
$converter = new DateFormatConverter();
|
||||||
|
|
||||||
|
return $this->localeSettings->getDateTypeFormat() . ' ' . $converter->convert($user->getTimeFormat()); // PHP
|
||||||
|
},
|
||||||
|
'format_picker' => function (Options $options) {
|
||||||
|
$converter = new MomentFormatConverter();
|
||||||
|
|
||||||
|
return $converter->convert($options['format']); // JS
|
||||||
|
},
|
||||||
'with_seconds' => false,
|
'with_seconds' => false,
|
||||||
'time_increment' => 1,
|
'time_increment' => 1,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -18,6 +18,21 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
*/
|
*/
|
||||||
class SkinType extends AbstractType
|
class SkinType extends AbstractType
|
||||||
{
|
{
|
||||||
|
public const THEMES = [
|
||||||
|
'blue' => 'blue',
|
||||||
|
'black' => 'black',
|
||||||
|
'green' => 'green',
|
||||||
|
'purple' => 'purple',
|
||||||
|
'red' => 'red',
|
||||||
|
'yellow' => 'yellow',
|
||||||
|
'blue-light' => 'blue-light',
|
||||||
|
'black-light' => 'black-light',
|
||||||
|
'green-light' => 'green-light',
|
||||||
|
'purple-light' => 'purple-light',
|
||||||
|
'red-light' => 'red-light',
|
||||||
|
'yellow-light' => 'yellow-light',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -25,20 +40,7 @@ class SkinType extends AbstractType
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'choices' => [
|
'choices' => self::THEMES,
|
||||||
'blue' => 'blue',
|
|
||||||
'black' => 'black',
|
|
||||||
'green' => 'green',
|
|
||||||
'purple' => 'purple',
|
|
||||||
'red' => 'red',
|
|
||||||
'yellow' => 'yellow',
|
|
||||||
'blue-light' => 'blue-light',
|
|
||||||
'black-light' => 'black-light',
|
|
||||||
'green-light' => 'green-light',
|
|
||||||
'purple-light' => 'purple-light',
|
|
||||||
'red-light' => 'red-light',
|
|
||||||
'yellow-light' => 'yellow-light',
|
|
||||||
]
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/Invoice/DuplicateInvoiceNumberException.php
Normal file
18
src/Invoice/DuplicateInvoiceNumberException.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Invoice;
|
||||||
|
|
||||||
|
final class DuplicateInvoiceNumberException extends \Exception
|
||||||
|
{
|
||||||
|
public function __construct(string $invoiceNumber)
|
||||||
|
{
|
||||||
|
parent::__construct('Invoice number "' . $invoiceNumber . '" already existing');
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,21 +58,29 @@ final class ConfigurableNumberGenerator implements NumberGeneratorInterface
|
||||||
{
|
{
|
||||||
$format = $this->configuration->find('invoice.number_format');
|
$format = $this->configuration->find('invoice.number_format');
|
||||||
$invoiceDate = $this->model->getInvoiceDate();
|
$invoiceDate = $this->model->getInvoiceDate();
|
||||||
$result = $format;
|
|
||||||
|
|
||||||
preg_match_all('/{[^}]*?}/', $format, $matches);
|
$loops = 0;
|
||||||
foreach ($matches[0] as $part) {
|
$increaseBy = 0;
|
||||||
$partialResult = $this->parseReplacer($invoiceDate, $part);
|
|
||||||
$result = str_replace($part, $partialResult, $result);
|
do {
|
||||||
}
|
$result = $format;
|
||||||
|
|
||||||
|
preg_match_all('/{[^}]*?}/', $format, $matches);
|
||||||
|
|
||||||
|
foreach ($matches[0] as $part) {
|
||||||
|
$partialResult = $this->parseReplacer($invoiceDate, $part, $increaseBy);
|
||||||
|
$result = str_replace($part, $partialResult, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
$increaseBy++;
|
||||||
|
} while ($this->repository->hasInvoice($result) && $loops++ < 99);
|
||||||
|
|
||||||
return (string) $result;
|
return (string) $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseReplacer(\DateTime $invoiceDate, string $originalFormat): string
|
private function parseReplacer(\DateTime $invoiceDate, string $originalFormat, int $increaseBy): string
|
||||||
{
|
{
|
||||||
$formatterLength = null;
|
$formatterLength = null;
|
||||||
$increaseBy = 0;
|
|
||||||
$formatPattern = str_replace(['{', '}'], '', $originalFormat);
|
$formatPattern = str_replace(['{', '}'], '', $originalFormat);
|
||||||
|
|
||||||
$parts = preg_split('/([+\-,])+/', $formatPattern, -1, PREG_SPLIT_DELIM_CAPTURE);
|
$parts = preg_split('/([+\-,])+/', $formatPattern, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||||
|
|
|
@ -239,10 +239,6 @@ final class ServiceInvoice
|
||||||
|
|
||||||
public function changeInvoiceStatus(Invoice $invoice, string $status)
|
public function changeInvoiceStatus(Invoice $invoice, string $status)
|
||||||
{
|
{
|
||||||
if (!\in_array($status, [Invoice::STATUS_NEW, Invoice::STATUS_PENDING, Invoice::STATUS_PAID])) {
|
|
||||||
throw new \InvalidArgumentException('Unknown invoice status');
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($status) {
|
switch ($status) {
|
||||||
case Invoice::STATUS_NEW:
|
case Invoice::STATUS_NEW:
|
||||||
$invoice->setIsNew();
|
$invoice->setIsNew();
|
||||||
|
@ -255,6 +251,13 @@ final class ServiceInvoice
|
||||||
case Invoice::STATUS_PAID:
|
case Invoice::STATUS_PAID:
|
||||||
$invoice->setIsPaid();
|
$invoice->setIsPaid();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Invoice::STATUS_CANCELED:
|
||||||
|
$invoice->setIsCanceled();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \InvalidArgumentException('Unknown invoice status');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->invoiceRepository->saveInvoice($invoice);
|
$this->invoiceRepository->saveInvoice($invoice);
|
||||||
|
@ -363,12 +366,12 @@ final class ServiceInvoice
|
||||||
if ($renderer->supports($document)) {
|
if ($renderer->supports($document)) {
|
||||||
$dispatcher->dispatch(new InvoicePreRenderEvent($model, $document, $renderer));
|
$dispatcher->dispatch(new InvoicePreRenderEvent($model, $document, $renderer));
|
||||||
|
|
||||||
$response = $renderer->render($document, $model);
|
if ($this->invoiceRepository->hasInvoice($model->getInvoiceNumber())) {
|
||||||
|
throw new DuplicateInvoiceNumberException($model->getInvoiceNumber());
|
||||||
if ($model->getQuery()->isMarkAsExported()) {
|
|
||||||
$this->markEntriesAsExported($model->getEntries());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$response = $renderer->render($document, $model);
|
||||||
|
|
||||||
$event = new InvoicePostRenderEvent($model, $document, $renderer, $response);
|
$event = new InvoicePostRenderEvent($model, $document, $renderer, $response);
|
||||||
$dispatcher->dispatch($event);
|
$dispatcher->dispatch($event);
|
||||||
|
|
||||||
|
@ -379,6 +382,10 @@ final class ServiceInvoice
|
||||||
$invoice->setFilename($invoiceFilename);
|
$invoice->setFilename($invoiceFilename);
|
||||||
$this->invoiceRepository->saveInvoice($invoice);
|
$this->invoiceRepository->saveInvoice($invoice);
|
||||||
|
|
||||||
|
if ($model->getQuery()->isMarkAsExported()) {
|
||||||
|
$this->markEntriesAsExported($model->getEntries());
|
||||||
|
}
|
||||||
|
|
||||||
$dispatcher->dispatch(new InvoiceCreatedEvent($invoice));
|
$dispatcher->dispatch(new InvoiceCreatedEvent($invoice));
|
||||||
|
|
||||||
return $invoice;
|
return $invoice;
|
||||||
|
|
|
@ -27,6 +27,7 @@ use App\Saml\Security\SamlFactory;
|
||||||
use App\Timesheet\CalculatorInterface as TimesheetCalculator;
|
use App\Timesheet\CalculatorInterface as TimesheetCalculator;
|
||||||
use App\Timesheet\Rounding\RoundingInterface;
|
use App\Timesheet\Rounding\RoundingInterface;
|
||||||
use App\Timesheet\TrackingMode\TrackingModeInterface;
|
use App\Timesheet\TrackingMode\TrackingModeInterface;
|
||||||
|
use App\Validator\Constraints\ProjectConstraint;
|
||||||
use App\Validator\Constraints\TimesheetConstraint;
|
use App\Validator\Constraints\TimesheetConstraint;
|
||||||
use App\Widget\WidgetInterface;
|
use App\Widget\WidgetInterface;
|
||||||
use App\Widget\WidgetRendererInterface;
|
use App\Widget\WidgetRendererInterface;
|
||||||
|
@ -60,6 +61,7 @@ class Kernel extends BaseKernel
|
||||||
public const TAG_TIMESHEET_EXPORTER = 'timesheet.exporter';
|
public const TAG_TIMESHEET_EXPORTER = 'timesheet.exporter';
|
||||||
public const TAG_TIMESHEET_TRACKING_MODE = 'timesheet.tracking_mode';
|
public const TAG_TIMESHEET_TRACKING_MODE = 'timesheet.tracking_mode';
|
||||||
public const TAG_TIMESHEET_ROUNDING_MODE = 'timesheet.rounding_mode';
|
public const TAG_TIMESHEET_ROUNDING_MODE = 'timesheet.rounding_mode';
|
||||||
|
public const TAG_PROJECT_VALIDATOR = 'project.validator';
|
||||||
|
|
||||||
public function getCacheDir()
|
public function getCacheDir()
|
||||||
{
|
{
|
||||||
|
@ -87,6 +89,7 @@ class Kernel extends BaseKernel
|
||||||
$container->registerForAutoconfiguration(TrackingModeInterface::class)->addTag(self::TAG_TIMESHEET_TRACKING_MODE);
|
$container->registerForAutoconfiguration(TrackingModeInterface::class)->addTag(self::TAG_TIMESHEET_TRACKING_MODE);
|
||||||
$container->registerForAutoconfiguration(RoundingInterface::class)->addTag(self::TAG_TIMESHEET_ROUNDING_MODE);
|
$container->registerForAutoconfiguration(RoundingInterface::class)->addTag(self::TAG_TIMESHEET_ROUNDING_MODE);
|
||||||
$container->registerForAutoconfiguration(TimesheetConstraint::class)->addTag(self::TAG_TIMESHEET_VALIDATOR);
|
$container->registerForAutoconfiguration(TimesheetConstraint::class)->addTag(self::TAG_TIMESHEET_VALIDATOR);
|
||||||
|
$container->registerForAutoconfiguration(ProjectConstraint::class)->addTag(self::TAG_PROJECT_VALIDATOR);
|
||||||
|
|
||||||
/** @var SecurityExtension $extension */
|
/** @var SecurityExtension $extension */
|
||||||
$extension = $container->getExtension('security');
|
$extension = $container->getExtension('security');
|
||||||
|
|
|
@ -75,9 +75,11 @@ class LdapUserHydrator
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill them after hydrating account, so they can't be overwritten
|
// fill them after hydrating account, so they can't be overwritten
|
||||||
$user->setPassword('');
|
// by the mapping attributes
|
||||||
|
if ($user->getId() === null) {
|
||||||
|
$user->setPassword('');
|
||||||
|
}
|
||||||
$user->setAuth(User::AUTH_LDAP);
|
$user->setAuth(User::AUTH_LDAP);
|
||||||
|
|
||||||
$user->setPreferenceValue('ldap.dn', $ldapEntry['dn']);
|
$user->setPreferenceValue('ldap.dn', $ldapEntry['dn']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ class ProjectStatisticService
|
||||||
->setParameter('end', $end, Types::DATETIME_MUTABLE)
|
->setParameter('end', $end, Types::DATETIME_MUTABLE)
|
||||||
;
|
;
|
||||||
|
|
||||||
if ($query->isOnlyWithRecords()) {
|
if (!$query->isIncludeNoWork()) {
|
||||||
$qb2 = $this->repository->createQueryBuilder('t1');
|
$qb2 = $this->repository->createQueryBuilder('t1');
|
||||||
$qb2
|
$qb2
|
||||||
->select('1')
|
->select('1')
|
||||||
|
@ -163,15 +163,28 @@ class ProjectStatisticService
|
||||||
$qb->andWhere($qb->expr()->exists($qb2));
|
$qb->andWhere($qb->expr()->exists($qb2));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$query->isIncludeNoBudget()) {
|
if ($query->isIncludeNoBudget()) {
|
||||||
$qb
|
$qb->andWhere(
|
||||||
->andWhere(
|
$qb->expr()->eq('p.budget', 0.0),
|
||||||
$qb->expr()->orX(
|
$qb->expr()->eq('p.timeBudget', 0)
|
||||||
$qb->expr()->gt('p.budget', 0.0),
|
);
|
||||||
$qb->expr()->gt('p.timeBudget', 0)
|
} else {
|
||||||
)
|
$qb->andWhere(
|
||||||
|
$qb->expr()->orX(
|
||||||
|
$qb->expr()->gt('p.budget', 0.0),
|
||||||
|
$qb->expr()->gt('p.timeBudget', 0)
|
||||||
)
|
)
|
||||||
;
|
);
|
||||||
|
if ($query->isBudgetTypeMonthly()) {
|
||||||
|
$qb->andWhere(
|
||||||
|
$qb->expr()->eq('p.budgetType', ':typeMonth')
|
||||||
|
);
|
||||||
|
$qb->setParameter('typeMonth', 'month');
|
||||||
|
} else {
|
||||||
|
$qb->andWhere(
|
||||||
|
$qb->expr()->isNull('p.budgetType')
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($query->getCustomer() !== null) {
|
if ($query->getCustomer() !== null) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ use App\Form\Type\CustomerType;
|
||||||
use App\Form\Type\MonthPickerType;
|
use App\Form\Type\MonthPickerType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
@ -45,9 +46,20 @@ class ProjectDateRangeForm extends AbstractType
|
||||||
'model_timezone' => $options['timezone'],
|
'model_timezone' => $options['timezone'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->add('includeNoBudget', CheckboxType::class, [
|
$builder->add('includeNoWork', CheckboxType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'label' => 'label.includeNoBudget',
|
'label' => 'label.includeNoWork',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$builder->add('budgetType', ChoiceType::class, [
|
||||||
|
'required' => true,
|
||||||
|
'multiple' => false,
|
||||||
|
'expanded' => true,
|
||||||
|
'choices' => [
|
||||||
|
'label.includeNoBudget' => 'none',
|
||||||
|
'label.includeBudgetType_full' => 'full',
|
||||||
|
'label.includeBudgetType_month' => 'month',
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ final class ProjectDateRangeQuery
|
||||||
*/
|
*/
|
||||||
private $customer;
|
private $customer;
|
||||||
|
|
||||||
private $includeNoBudget = false;
|
private $includeNoWork = true;
|
||||||
private $onlyWithRecords = false;
|
private $budgetType = 'month';
|
||||||
|
|
||||||
public function __construct(\DateTime $month, User $user)
|
public function __construct(\DateTime $month, User $user)
|
||||||
{
|
{
|
||||||
|
@ -38,22 +38,17 @@ final class ProjectDateRangeQuery
|
||||||
|
|
||||||
public function isIncludeNoBudget(): bool
|
public function isIncludeNoBudget(): bool
|
||||||
{
|
{
|
||||||
return $this->includeNoBudget;
|
return $this->budgetType === 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setIncludeNoBudget(bool $includeNoBudget): void
|
public function isIncludeNoWork(): bool
|
||||||
{
|
{
|
||||||
$this->includeNoBudget = $includeNoBudget;
|
return $this->includeNoWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isOnlyWithRecords(): bool
|
public function setIncludeNoWork(bool $includeNoWork): void
|
||||||
{
|
{
|
||||||
return $this->onlyWithRecords;
|
$this->includeNoWork = $includeNoWork;
|
||||||
}
|
|
||||||
|
|
||||||
public function setOnlyWithRecords(bool $onlyWithRecords): void
|
|
||||||
{
|
|
||||||
$this->onlyWithRecords = $onlyWithRecords;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUser(): ?User
|
public function getUser(): ?User
|
||||||
|
@ -61,12 +56,12 @@ final class ProjectDateRangeQuery
|
||||||
return $this->user;
|
return $this->user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMonth(): \DateTime
|
public function getMonth(): ?\DateTime
|
||||||
{
|
{
|
||||||
return $this->month;
|
return $this->month;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMonth(\DateTime $month): void
|
public function setMonth(?\DateTime $month): void
|
||||||
{
|
{
|
||||||
$this->month = $month;
|
$this->month = $month;
|
||||||
}
|
}
|
||||||
|
@ -80,4 +75,19 @@ final class ProjectDateRangeQuery
|
||||||
{
|
{
|
||||||
$this->customer = $customer;
|
$this->customer = $customer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isBudgetTypeMonthly(): bool
|
||||||
|
{
|
||||||
|
return $this->budgetType === 'month';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBudgetType(): ?string
|
||||||
|
{
|
||||||
|
return $this->budgetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBudgetType(?string $budgetType): void
|
||||||
|
{
|
||||||
|
$this->budgetType = $budgetType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ final class SamlUserFactory
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setEnabled(true);
|
$user->setEnabled(true);
|
||||||
$user->setUsername($token->getUsername());
|
$user->setUsername($token->getUsername());
|
||||||
|
$user->setPassword('');
|
||||||
|
|
||||||
$this->hydrateUser($user, $token);
|
$this->hydrateUser($user, $token);
|
||||||
|
|
||||||
|
@ -73,8 +74,11 @@ final class SamlUserFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill them after hydrating account, so they can't be overwritten
|
// fill them after hydrating account, so they can't be overwritten
|
||||||
|
// by the mapping attributes
|
||||||
|
if ($user->getId() === null) {
|
||||||
|
$user->setPassword('');
|
||||||
|
}
|
||||||
$user->setUsername($token->getUsername());
|
$user->setUsername($token->getUsername());
|
||||||
$user->setPassword('');
|
|
||||||
$user->setAuth(User::AUTH_SAML);
|
$user->setAuth(User::AUTH_SAML);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,8 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
|
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
|
||||||
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
|
|
||||||
|
|
||||||
class TokenAuthenticator extends AbstractGuardAuthenticator implements PasswordAuthenticatedInterface
|
class TokenAuthenticator extends AbstractGuardAuthenticator
|
||||||
{
|
{
|
||||||
public const HEADER_USERNAME = 'X-AUTH-USER';
|
public const HEADER_USERNAME = 'X-AUTH-USER';
|
||||||
public const HEADER_TOKEN = 'X-AUTH-TOKEN';
|
public const HEADER_TOKEN = 'X-AUTH-TOKEN';
|
||||||
|
@ -158,13 +157,4 @@ class TokenAuthenticator extends AbstractGuardAuthenticator implements PasswordA
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPassword($credentials): ?string
|
|
||||||
{
|
|
||||||
if (!\is_array($credentials) || !\array_key_exists('token', $credentials) || empty($credentials['token'])) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $credentials['token'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use App\Entity\User;
|
||||||
use App\Utils\LocaleFormats;
|
use App\Utils\LocaleFormats;
|
||||||
use App\Utils\LocaleFormatter;
|
use App\Utils\LocaleFormatter;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Twig\Extension\AbstractExtension;
|
use Twig\Extension\AbstractExtension;
|
||||||
use Twig\TwigFilter;
|
use Twig\TwigFilter;
|
||||||
use Twig\TwigFunction;
|
use Twig\TwigFunction;
|
||||||
|
@ -22,10 +23,9 @@ use Twig\TwigTest;
|
||||||
|
|
||||||
final class LocaleFormatExtensions extends AbstractExtension
|
final class LocaleFormatExtensions extends AbstractExtension
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var LanguageFormattings|null
|
|
||||||
*/
|
|
||||||
private $formats;
|
private $formats;
|
||||||
|
private $security;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var LocaleFormats|null
|
* @var LocaleFormats|null
|
||||||
*/
|
*/
|
||||||
|
@ -38,10 +38,12 @@ final class LocaleFormatExtensions extends AbstractExtension
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $locale;
|
private $locale;
|
||||||
|
private $userFormat;
|
||||||
|
|
||||||
public function __construct(LanguageFormattings $formats)
|
public function __construct(LanguageFormattings $formats, Security $security)
|
||||||
{
|
{
|
||||||
$this->formats = $formats;
|
$this->formats = $formats;
|
||||||
|
$this->security = $security;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,11 +166,23 @@ final class LocaleFormatExtensions extends AbstractExtension
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DateTime|string $date
|
* @param DateTime|string $date
|
||||||
|
* @param bool $stripMidnight
|
||||||
* @return bool|false|string
|
* @return bool|false|string
|
||||||
*/
|
*/
|
||||||
public function dateTimeFull($date)
|
public function dateTimeFull($date, bool $stripMidnight = false)
|
||||||
{
|
{
|
||||||
return $this->getFormatter()->dateTimeFull($date);
|
return $this->getFormatter()->dateTimeFull($date, $this->getUserTimeFormat(), $stripMidnight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUserTimeFormat(): string
|
||||||
|
{
|
||||||
|
if ($this->userFormat === null) {
|
||||||
|
/** @var User|null $user */
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
$this->userFormat = $user !== null ? $user->getTimeFormat() : 'H:i';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->userFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createDate(string $date, ?User $user = null): \DateTime
|
public function createDate(string $date, ?User $user = null): \DateTime
|
||||||
|
@ -200,7 +214,7 @@ final class LocaleFormatExtensions extends AbstractExtension
|
||||||
*/
|
*/
|
||||||
public function time($date)
|
public function time($date)
|
||||||
{
|
{
|
||||||
return $this->getFormatter()->time($date);
|
return $this->getFormatter()->time($date, $this->getUserTimeFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -239,7 +253,16 @@ final class LocaleFormatExtensions extends AbstractExtension
|
||||||
*/
|
*/
|
||||||
public function hour24($twentyFour, $twelveHour)
|
public function hour24($twentyFour, $twelveHour)
|
||||||
{
|
{
|
||||||
return $this->getFormatter()->hour24($twentyFour, $twelveHour);
|
@trigger_error('Twig filter "hour24" is deprecated, use app.user.is24Hour() instead', E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
/** @var User|null $user */
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if (null === $user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user->is24Hour();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDurationFormat(): string
|
public function getDurationFormat(): string
|
||||||
|
|
33
src/Utils/DateFormatConverter.php
Normal file
33
src/Utils/DateFormatConverter.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Utils;
|
||||||
|
|
||||||
|
class DateFormatConverter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This defines the mapping between PHP date format (key) and ICU date format (value).
|
||||||
|
* https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $formatConvertRules = [
|
||||||
|
// hours
|
||||||
|
'h' => 'hh', 'H' => 'HH',
|
||||||
|
// minutes
|
||||||
|
'i' => 'mm',
|
||||||
|
// am/pm to AM/PM
|
||||||
|
'A' => 'a'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function convert(string $format): string
|
||||||
|
{
|
||||||
|
return strtr($format, self::$formatConvertRules);
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,21 +75,12 @@ class LocaleFormats
|
||||||
/**
|
/**
|
||||||
* Returns the format which is used by the form component to handle datetime values.
|
* Returns the format which is used by the form component to handle datetime values.
|
||||||
*
|
*
|
||||||
|
* @deprecated since 1.16
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getDateTimeTypeFormat(): string
|
public function getDateTimeTypeFormat(): string
|
||||||
{
|
{
|
||||||
return $this->formats->getDateTimeTypeFormat($this->getLocale());
|
return $this->formats->getDateTypeFormat($this->getLocale()) . ' HH:mm';
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the format which is used by the Javascript component to handle datetime values.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDateTimePickerFormat(): string
|
|
||||||
{
|
|
||||||
return $this->formats->getDateTimePickerFormat($this->getLocale());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,14 +122,4 @@ class LocaleFormats
|
||||||
{
|
{
|
||||||
return $this->formats->getDurationFormat($this->getLocale());
|
return $this->formats->getDurationFormat($this->getLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether this locale uses the 24 hour format.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isTwentyFourHours(): bool
|
|
||||||
{
|
|
||||||
return $this->formats->isTwentyFourHours($this->getLocale());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,10 @@ final class LocaleFormatter
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $dateTimeFormat = null;
|
private $dateTimeFormat = null;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $dateTypeFormat = null;
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
@ -54,10 +58,6 @@ final class LocaleFormatter
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $timeFormat = null;
|
private $timeFormat = null;
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $isTwentyFourHour = null;
|
|
||||||
|
|
||||||
public function __construct(LanguageFormattings $formats, string $locale)
|
public function __construct(LanguageFormattings $formats, string $locale)
|
||||||
{
|
{
|
||||||
|
@ -216,6 +216,15 @@ final class LocaleFormatter
|
||||||
return $date->format($this->dateFormat);
|
return $date->format($this->dateFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getDateTypeFormat(): string
|
||||||
|
{
|
||||||
|
if (null === $this->dateTypeFormat) {
|
||||||
|
$this->dateTypeFormat = $this->localeFormats->getDateTypeFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->dateTypeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DateTime|string $date
|
* @param DateTime|string $date
|
||||||
* @return string
|
* @return string
|
||||||
|
@ -239,12 +248,15 @@ final class LocaleFormatter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DateTime|string $date
|
* @param DateTime|string $date
|
||||||
|
* @param string $timeFormat
|
||||||
|
* @param bool $stripMidnight
|
||||||
* @return bool|false|string
|
* @return bool|false|string
|
||||||
*/
|
*/
|
||||||
public function dateTimeFull($date)
|
public function dateTimeFull($date, string $timeFormat, bool $stripMidnight = false)
|
||||||
{
|
{
|
||||||
if (null === $this->dateTimeTypeFormat) {
|
if (null === $this->dateTimeTypeFormat) {
|
||||||
$this->dateTimeTypeFormat = $this->localeFormats->getDateTimeTypeFormat();
|
$converter = new DateFormatConverter();
|
||||||
|
$this->dateTimeTypeFormat = $this->getDateTypeFormat() . ' ' . $converter->convert($timeFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$date instanceof DateTime) {
|
if (!$date instanceof DateTime) {
|
||||||
|
@ -255,13 +267,19 @@ final class LocaleFormatter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$format = $this->dateTimeTypeFormat;
|
||||||
|
|
||||||
|
if ($stripMidnight && $date->format('H') == '00' && $date->format('i') == '00') {
|
||||||
|
$format = $this->localeFormats->getDateTypeFormat();
|
||||||
|
}
|
||||||
|
|
||||||
$formatter = new IntlDateFormatter(
|
$formatter = new IntlDateFormatter(
|
||||||
$this->locale,
|
$this->locale,
|
||||||
IntlDateFormatter::MEDIUM,
|
IntlDateFormatter::MEDIUM,
|
||||||
IntlDateFormatter::MEDIUM,
|
IntlDateFormatter::MEDIUM,
|
||||||
date_default_timezone_get(),
|
date_default_timezone_get(),
|
||||||
IntlDateFormatter::GREGORIAN,
|
IntlDateFormatter::GREGORIAN,
|
||||||
$this->dateTimeTypeFormat
|
$format
|
||||||
);
|
);
|
||||||
|
|
||||||
return $formatter->format($date);
|
return $formatter->format($date);
|
||||||
|
@ -291,7 +309,7 @@ final class LocaleFormatter
|
||||||
* @return string
|
* @return string
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function time($date)
|
public function time($date, string $format = null)
|
||||||
{
|
{
|
||||||
if (null === $this->timeFormat) {
|
if (null === $this->timeFormat) {
|
||||||
$this->timeFormat = $this->localeFormats->getTimeFormat();
|
$this->timeFormat = $this->localeFormats->getTimeFormat();
|
||||||
|
@ -301,7 +319,7 @@ final class LocaleFormatter
|
||||||
$date = new DateTime($date);
|
$date = new DateTime($date);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $date->format($this->timeFormat);
|
return $date->format($format ?? $this->timeFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -335,22 +353,4 @@ final class LocaleFormatter
|
||||||
{
|
{
|
||||||
return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE'));
|
return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $twentyFour
|
|
||||||
* @param mixed $twelveHour
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function hour24($twentyFour, $twelveHour)
|
|
||||||
{
|
|
||||||
if (null === $this->isTwentyFourHour) {
|
|
||||||
$this->isTwentyFourHour = $this->localeFormats->isTwentyFourHours();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (true === $this->isTwentyFourHour) {
|
|
||||||
return $twentyFour;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $twelveHour;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,11 +74,23 @@ class MPdfConverter implements HtmlToPdfConverter
|
||||||
@ini_set('pcre.backtrack_limit', '1000000');
|
@ini_set('pcre.backtrack_limit', '1000000');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// large amount of data take time
|
||||||
|
@ini_set('max_execution_time', '120');
|
||||||
|
|
||||||
// reduce the size of content parts that are passed to MPDF, to prevent
|
// reduce the size of content parts that are passed to MPDF, to prevent
|
||||||
// https://mpdf.github.io/troubleshooting/known-issues.html#blank-pages-or-some-sections-missing
|
// https://mpdf.github.io/troubleshooting/known-issues.html#blank-pages-or-some-sections-missing
|
||||||
$parts = explode('<pagebreak>', $html);
|
$parts = explode('<pagebreak>', $html);
|
||||||
for ($i = 0; $i < \count($parts); $i++) {
|
for ($i = 0; $i < \count($parts); $i++) {
|
||||||
$mpdf->WriteHTML($parts[$i]);
|
if (stripos($parts[$i], '<!-- CONTENT_PART -->') !== false) {
|
||||||
|
$subParts = explode('<!-- CONTENT_PART -->', $parts[$i]);
|
||||||
|
$run = 0;
|
||||||
|
foreach ($subParts as $subPart) {
|
||||||
|
$mpdf->WriteHTML($subPart);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$mpdf->WriteHTML($parts[$i]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($i < \count($parts) - 1) {
|
if ($i < \count($parts) - 1) {
|
||||||
$mpdf->WriteHTML('<pagebreak>');
|
$mpdf->WriteHTML('<pagebreak>');
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ class MomentFormatConverter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This defines the mapping between PHP ICU date format (key) and moment.js date format (value)
|
* This defines the mapping between PHP ICU date format (key) and moment.js date format (value)
|
||||||
* For ICU formats see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
|
* For ICU formats see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
|
||||||
* For Moment formats see http://momentjs.com/docs/#/displaying/format/.
|
* For Moment formats see http://momentjs.com/docs/#/displaying/format/
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
|
@ -34,6 +34,8 @@ class MomentFormatConverter
|
||||||
'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ',
|
'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ',
|
||||||
// letter 'T'
|
// letter 'T'
|
||||||
'\'T\'' => 'T',
|
'\'T\'' => 'T',
|
||||||
|
// am/pm to AM/PM
|
||||||
|
'a' => 'A',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
19
src/Validator/Constraints/ProjectConstraint.php
Normal file
19
src/Validator/Constraints/ProjectConstraint.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Validator\Constraints;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend this class if you want to add dynamic project validation (eg. via a bundle).
|
||||||
|
*/
|
||||||
|
abstract class ProjectConstraint extends Constraint
|
||||||
|
{
|
||||||
|
}
|
|
@ -18,6 +18,19 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||||
|
|
||||||
class ProjectValidator extends ConstraintValidator
|
class ProjectValidator extends ConstraintValidator
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var Constraint[]
|
||||||
|
*/
|
||||||
|
private $constraints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Constraint[] $constraints
|
||||||
|
*/
|
||||||
|
public function __construct(iterable $constraints = [])
|
||||||
|
{
|
||||||
|
$this->constraints = $constraints;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Project|mixed $value
|
* @param Project|mixed $value
|
||||||
* @param Constraint $constraint
|
* @param Constraint $constraint
|
||||||
|
@ -33,6 +46,13 @@ class ProjectValidator extends ConstraintValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->validateProject($value, $this->context);
|
$this->validateProject($value, $this->context);
|
||||||
|
|
||||||
|
foreach ($this->constraints as $constraint) {
|
||||||
|
$this->context
|
||||||
|
->getValidator()
|
||||||
|
->inContext($this->context)
|
||||||
|
->validate($value, $constraint, [Constraint::DEFAULT_GROUP]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function validateProject(Project $project, ExecutionContextInterface $context)
|
protected function validateProject(Project $project, ExecutionContextInterface $context)
|
||||||
|
|
|
@ -296,7 +296,7 @@
|
||||||
login: '{{ path('fos_user_security_login') }}',
|
login: '{{ path('fos_user_security_login') }}',
|
||||||
locale: '{{ app.request.locale }}',
|
locale: '{{ app.request.locale }}',
|
||||||
first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }},
|
first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }},
|
||||||
twentyFourHours: {{ 'true'|hour24('false') }},
|
twentyFourHours: {{ app.user.is24Hour() ? 'true' : 'false' }},
|
||||||
autoComplete: {{ kimai_config.themeAutocompleteCharacters }},
|
autoComplete: {{ kimai_config.themeAutocompleteCharacters }},
|
||||||
defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}',
|
defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}',
|
||||||
updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %}
|
updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %}
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
{% block box_title %}Logfile (max. {{ logLines }} last lines){% endblock %}
|
{% block box_title %}Logfile (max. {{ logLines }} last lines){% endblock %}
|
||||||
{% block box_tools %}
|
{% block box_tools %}
|
||||||
{% if log_delete %}
|
{% if log_delete %}
|
||||||
<a class="btn-box-tool confirmation-link" href="{{ path('doctor_flush_log') }}" data-question="confirm.delete"><i class="{{ 'delete'|icon }}"></i></a>
|
<a class="btn-box-tool confirmation-link" href="{{ path('doctor_flush_log', {'token': csrf_token('doctor.flush_log')}) }}" data-question="confirm.delete"><i class="{{ 'delete'|icon }}"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block box_body %}
|
{% block box_body %}
|
||||||
|
|
|
@ -140,6 +140,7 @@ mpdf-->
|
||||||
{% set totalInternalRate = 0 %}
|
{% set totalInternalRate = 0 %}
|
||||||
{% set totalRate = 0 %}
|
{% set totalRate = 0 %}
|
||||||
{% for id, summary in summaries %}
|
{% for id, summary in summaries %}
|
||||||
|
<!-- CONTENT_PART -->
|
||||||
{% set totalDuration = totalDuration + summary.duration %}
|
{% set totalDuration = totalDuration + summary.duration %}
|
||||||
{% set totalInternalRate = totalInternalRate + summary.rate_internal %}
|
{% set totalInternalRate = totalInternalRate + summary.rate_internal %}
|
||||||
{% set totalRate = totalRate + summary.rate %}
|
{% set totalRate = totalRate + summary.rate %}
|
||||||
|
@ -274,6 +275,7 @@ mpdf-->
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
|
<!-- CONTENT_PART -->
|
||||||
{% set duration = duration + entry.duration %}
|
{% set duration = duration + entry.duration %}
|
||||||
{% if currency is same as(false) %}
|
{% if currency is same as(false) %}
|
||||||
{% set currency = entry.project.customer.currency %}
|
{% set currency = entry.project.customer.currency %}
|
||||||
|
|
|
@ -309,7 +309,7 @@
|
||||||
|
|
|
|
||||||
<label class="control-label" for="begin-format">
|
<label class="control-label" for="begin-format">
|
||||||
{{ 'label.begin'|trans }}:
|
{{ 'label.begin'|trans }}:
|
||||||
{% set demo_date = create_date('2020-01-01 13:00:00') %}
|
{% set demo_date = create_date('2020-01-01 11:00:00') %}
|
||||||
<select id="begin-format" name="begin-format">
|
<select id="begin-format" name="begin-format">
|
||||||
<option value="plain">{{ demo_date|date_format('H:i') }}</option>
|
<option value="plain">{{ demo_date|date_format('H:i') }}</option>
|
||||||
<option value="time">{{ demo_date|date_time }}</option>
|
<option value="time">{{ demo_date|date_time }}</option>
|
||||||
|
|
|
@ -235,6 +235,12 @@
|
||||||
|
|
||||||
document.addEventListener('kimai.initialized', function() {
|
document.addEventListener('kimai.initialized', function() {
|
||||||
KimaiReloadPageWidget.create('kimai.systemConfigUpdate', true);
|
KimaiReloadPageWidget.create('kimai.systemConfigUpdate', true);
|
||||||
|
|
||||||
|
{% if models|length > 0 %}
|
||||||
|
jQuery('body').on('change', '#{{ form.template.vars.id }}', function(event) {
|
||||||
|
document.getElementById('{{ form.vars.attr.id }}').submit();
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<tr class="alternative-link open-edit" data-href="{{ path('admin_invoice_download', {'id': entry.id}) }}">
|
<tr class="alternative-link open-edit{% if entry.canceled %} warning text-muted{% endif %}" data-href="{{ path('admin_invoice_download', {'id': entry.id}) }}">
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.createdAt|date_short }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.createdAt|date_short }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.user_avatar(entry.user) }} {{ widgets.username(entry.user) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.user_avatar(entry.user) }} {{ widgets.username(entry.user) }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
||||||
|
|
|
@ -12,12 +12,16 @@
|
||||||
{{ widgets.label('status.pending'|trans, 'warning') }}
|
{{ widgets.label('status.pending'|trans, 'warning') }}
|
||||||
{% elseif invoice.paid %}
|
{% elseif invoice.paid %}
|
||||||
{{ widgets.label('status.paid'|trans, 'success') }}
|
{{ widgets.label('status.paid'|trans, 'success') }}
|
||||||
|
{% elseif invoice.canceled %}
|
||||||
|
{{ widgets.label('status.canceled'|trans, 'gray') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro invoice_due_date(invoice) %}
|
{% macro invoice_due_date(invoice) %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% if invoice.overdue and not invoice.paid %}
|
{% if invoice.canceled %}
|
||||||
|
{{ widgets.label('status.canceled'|trans, 'gray') }}
|
||||||
|
{% elseif invoice.overdue and not invoice.paid %}
|
||||||
{{ widgets.label(invoice.dueDate|date_short, 'danger') }}
|
{{ widgets.label(invoice.dueDate|date_short, 'danger') }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ widgets.label(invoice.dueDate|date_short, 'primary') }}
|
{{ widgets.label(invoice.dueDate|date_short, 'primary') }}
|
||||||
|
|
|
@ -102,6 +102,7 @@ mpdf-->
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for id, entry in model.calculator.entries %}
|
{% for id, entry in model.calculator.entries %}
|
||||||
|
<!-- CONTENT_PART -->
|
||||||
{% set duration = entry.duration|duration(isDecimal) %}
|
{% set duration = entry.duration|duration(isDecimal) %}
|
||||||
{% if entry.fixedRate %}
|
{% if entry.fixedRate %}
|
||||||
{% set rate = entry.fixedRate %}
|
{% set rate = entry.fixedRate %}
|
||||||
|
|
|
@ -109,6 +109,7 @@ mpdf-->
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for entry in model.calculator.entries %}
|
{% for entry in model.calculator.entries %}
|
||||||
|
<!-- CONTENT_PART -->
|
||||||
{% set duration = entry.duration|duration(isDecimal) %}
|
{% set duration = entry.duration|duration(isDecimal) %}
|
||||||
{% if entry.fixedRate is not null %}
|
{% if entry.fixedRate is not null %}
|
||||||
{% set rate = entry.fixedRate %}
|
{% set rate = entry.fixedRate %}
|
||||||
|
|
|
@ -478,15 +478,16 @@
|
||||||
{% elseif '\\LanguageType' in type %}
|
{% elseif '\\LanguageType' in type %}
|
||||||
{{ value|language }}
|
{{ value|language }}
|
||||||
{% elseif '\\MoneyType' in type %}
|
{% elseif '\\MoneyType' in type %}
|
||||||
|
{% set classname = class_name(entity) %}
|
||||||
{% if entity is null %}
|
{% if entity is null %}
|
||||||
{{ value }}
|
{{ value }}
|
||||||
{% elseif class_name(entity) == 'App\\Entity\\Timesheet' %}
|
{% elseif classname == 'App\\Entity\\Timesheet' %}
|
||||||
{{ value|money(entity.project.customer.currency) }}
|
{{ value|money(entity.project.customer.currency) }}
|
||||||
{% elseif class_name(entity) == 'App\\Entity\\Customer' %}
|
{% elseif classname == 'App\\Entity\\Customer' %}
|
||||||
{{ value|money(entity.currency) }}
|
{{ value|money(entity.currency) }}
|
||||||
{% elseif class_name(entity) == 'App\\Entity\\Project' %}
|
{% elseif classname == 'App\\Entity\\Project' %}
|
||||||
{{ value|money(entity.customer.currency) }}
|
{{ value|money(entity.customer.currency) }}
|
||||||
{% elseif class_name(entity) == 'App\\Entity\\Activity' and entity.project is not null %}
|
{% elseif classname == 'App\\Entity\\Activity' and entity.project is not null %}
|
||||||
{{ value|money(entity.project.customer.currency) }}
|
{{ value|money(entity.project.customer.currency) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ value }}
|
{{ value }}
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'label.orderDate'|trans }}</th>
|
<th>{{ 'label.orderDate'|trans }}</th>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
{{ project.orderDate|date_full }}
|
{{ project.orderDate|date_full(true) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'label.project_start'|trans }}</th>
|
<th>{{ 'label.project_start'|trans }}</th>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
{{ project.start|date_full }}
|
{{ project.start|date_full(true) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'label.project_end'|trans }}</th>
|
<th>{{ 'label.project_end'|trans }}</th>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
{{ project.end|date_full }}
|
{{ project.end|date_full(true) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -51,9 +51,9 @@
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderNumber') }}">{{ entry.orderNumber }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderNumber') }}">{{ entry.orderNumber }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderDate') }}">{% if entry.orderDate is not null %}{{ entry.orderDate|date_full }}{% endif %}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderDate') }}">{% if entry.orderDate is not null %}{{ entry.orderDate|date_full(true) }}{% endif %}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_start') }}">{% if entry.start is not null %}{{ entry.start|date_full }}{% endif %}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_start') }}">{% if entry.start is not null %}{{ entry.start|date_full(true) }}{% endif %}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_end') }}">{% if entry.end is not null %}{{ entry.end|date_full }}{% endif %}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_end') }}">{% if entry.end is not null %}{{ entry.end|date_full(true) }}{% endif %}</td>
|
||||||
{% for field in metaColumns %}
|
{% for field in metaColumns %}
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}">
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}">
|
||||||
{{ tables.datatable_meta_column(entry, field) }}
|
{{ tables.datatable_meta_column(entry, field) }}
|
||||||
|
|
|
@ -38,7 +38,26 @@
|
||||||
{{ widgets.action_button('visibility', {'modal': ('#modal_' ~ tableName), 'class': 'btn-sm'}) }}
|
{{ widgets.action_button('visibility', {'modal': ('#modal_' ~ tableName), 'class': 'btn-sm'}) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block box_title %}
|
{% block box_title %}
|
||||||
{{ form_widget(form) }}
|
{% if form.customer is defined %}
|
||||||
|
{{ form_widget(form.customer) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.month is defined %}
|
||||||
|
{{ form_widget(form.month) }}
|
||||||
|
{% endif %}
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="{{ 'filter'|icon }}"></i> <span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu checkbox-menu">
|
||||||
|
<li>
|
||||||
|
{{ form_widget(form.includeNoWork) }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ form_widget(form.budgetType) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{ form_rest(form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block box_body %}
|
{% block box_body %}
|
||||||
{% if not hasData %}
|
{% if not hasData %}
|
||||||
|
|
|
@ -52,7 +52,32 @@
|
||||||
{{ widgets.action_button('visibility', {'modal': ('#modal_' ~ tableName), 'class': 'btn-sm'}) }}
|
{{ widgets.action_button('visibility', {'modal': ('#modal_' ~ tableName), 'class': 'btn-sm'}) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block box_title %}
|
{% block box_title %}
|
||||||
{{ form_widget(form) }}
|
{% if form.customer is defined %}
|
||||||
|
{{ form_widget(form.customer) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.month is defined %}
|
||||||
|
{{ form_widget(form.month) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.includeNoBudget is defined or form.includeNoWork is defined %}
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="{{ 'filter'|icon }}"></i> <span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu checkbox-menu">
|
||||||
|
{% if form.includeNoBudget is defined %}
|
||||||
|
<li>
|
||||||
|
{{ form_widget(form.includeNoBudget) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if form.includeNoWork is defined %}
|
||||||
|
<li>
|
||||||
|
{{ form_widget(form.includeNoWork) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ form_rest(form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block box_body %}
|
{% block box_body %}
|
||||||
{% if not hasData %}
|
{% if not hasData %}
|
||||||
|
|
|
@ -29,13 +29,13 @@ class ConfigurationControllerTest extends APIControllerBaseTest
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$this->assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
$this->assertEquals(8, \count($result));
|
$this->assertEquals(7, \count($result));
|
||||||
$this->assertI18nStructure($result);
|
$this->assertI18nStructure($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertI18nStructure(array $result)
|
protected function assertI18nStructure(array $result)
|
||||||
{
|
{
|
||||||
$expectedKeys = ['date', 'dateTime', 'duration', 'formDate', 'formDateTime', 'is24hours', 'time', 'now'];
|
$expectedKeys = ['date', 'dateTime', 'duration', 'formDate', 'is24hours', 'time', 'now'];
|
||||||
$actual = array_keys($result);
|
$actual = array_keys($result);
|
||||||
sort($actual);
|
sort($actual);
|
||||||
sort($expectedKeys);
|
sort($expectedKeys);
|
||||||
|
|
|
@ -26,7 +26,6 @@ class I18nConfigTest extends TestCase
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setDate('bar'));
|
$this->assertInstanceOf(I18nConfig::class, $sut->setDate('bar'));
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setDateTime('hello'));
|
$this->assertInstanceOf(I18nConfig::class, $sut->setDateTime('hello'));
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDate('world'));
|
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDate('world'));
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDateTime('testing'));
|
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setTime('fun'));
|
$this->assertInstanceOf(I18nConfig::class, $sut->setTime('fun'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,61 +26,50 @@ class LanguageFormattingsTest extends TestCase
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'de' => [
|
'de' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'date_time' => 'd.m. H:i',
|
'date_time' => 'd.m. H:i',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
'time' => 'H:i',
|
'time' => 'H:i',
|
||||||
'24_hours' => true,
|
|
||||||
],
|
],
|
||||||
'en' => [
|
'en' => [
|
||||||
'date_time_type' => 'yyyy-MM-dd HH:mm',
|
|
||||||
'date_type' => 'yyyy-MM-dd',
|
'date_type' => 'yyyy-MM-dd',
|
||||||
'date' => 'Y-m-d',
|
'date' => 'Y-m-d',
|
||||||
'date_time' => 'm-d H:i',
|
'date_time' => 'm-d H:i',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
'time' => 'H:i:s',
|
'time' => 'H:i:s',
|
||||||
'24_hours' => false,
|
|
||||||
],
|
],
|
||||||
'pt_BR' => [
|
'pt_BR' => [
|
||||||
'date_time_type' => 'dd-MM-yyyy HH:mm',
|
|
||||||
'date_type' => 'dd-MM-yyyy',
|
'date_type' => 'dd-MM-yyyy',
|
||||||
'date' => 'd-m-Y',
|
'date' => 'd-m-Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'it' => [
|
'it' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'fr' => [
|
'fr' => [
|
||||||
'date_time_type' => 'dd/MM/yyyy HH:mm',
|
|
||||||
'date_type' => 'dd/MM/yyyy',
|
'date_type' => 'dd/MM/yyyy',
|
||||||
'date' => 'd/m/Y',
|
'date' => 'd/m/Y',
|
||||||
'duration' => '%h h %m',
|
'duration' => '%h h %m',
|
||||||
],
|
],
|
||||||
'es' => [
|
'es' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'ru' => [
|
'ru' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'ar' => [
|
'ar' => [
|
||||||
'date_time_type' => 'yyyy-MM-dd HH:mm',
|
|
||||||
'date_type' => 'yyyy-MM-dd',
|
'date_type' => 'yyyy-MM-dd',
|
||||||
'date' => 'Y-m-d',
|
'date' => 'Y-m-d',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'hu' => [
|
'hu' => [
|
||||||
'date_time_type' => 'yyyy.MM.dd HH:mm',
|
|
||||||
'date_type' => 'yyyy.MM.dd',
|
'date_type' => 'yyyy.MM.dd',
|
||||||
'date' => 'Y.m.d.',
|
'date' => 'Y.m.d.',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
|
@ -136,40 +125,10 @@ class LanguageFormattingsTest extends TestCase
|
||||||
$this->assertEquals('DD.MM.YYYY', $sut->getDatePickerFormat('de'));
|
$this->assertEquals('DD.MM.YYYY', $sut->getDatePickerFormat('de'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetDateTimeTypeFormat()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut($this->getDefaultSettings());
|
|
||||||
$this->assertEquals('dd.MM.yyyy HH:mm', $sut->getDateTimeTypeFormat('de'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetDateTimePickerFormat()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut($this->getDefaultSettings());
|
|
||||||
$this->assertEquals('DD.MM.YYYY HH:mm', $sut->getDateTimePickerFormat('de'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testIs24Hours()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut($this->getDefaultSettings());
|
|
||||||
$this->assertTrue($sut->isTwentyFourHours('de'));
|
|
||||||
$this->assertFalse($sut->isTwentyFourHours('en'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetTimeFormat()
|
public function testGetTimeFormat()
|
||||||
{
|
{
|
||||||
$sut = $this->getSut($this->getDefaultSettings());
|
$sut = $this->getSut($this->getDefaultSettings());
|
||||||
$this->assertEquals('H:i', $sut->getTimeFormat('de'));
|
$this->assertEquals('H:i', $sut->getTimeFormat('de'));
|
||||||
$this->assertEquals('H:i:s', $sut->getTimeFormat('en'));
|
$this->assertEquals('H:i:s', $sut->getTimeFormat('en'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnknownSetting()
|
|
||||||
{
|
|
||||||
$this->expectException(\InvalidArgumentException::class);
|
|
||||||
$this->expectExceptionMessage('Unknown setting for locale en: date_time_type');
|
|
||||||
|
|
||||||
$sut = $this->getSut(['en' => [
|
|
||||||
'xxx' => 'dd.MM.yyyy HH:mm',
|
|
||||||
]]);
|
|
||||||
$sut->getDateTimePickerFormat('en');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ class PermissionControllerTest extends ControllerBaseTest
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||||
$this->assertAccessIsGranted($client, '/admin/permissions');
|
$this->assertAccessIsGranted($client, '/admin/permissions');
|
||||||
$this->assertHasDataTable($client);
|
$this->assertHasDataTable($client);
|
||||||
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 119);
|
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 121);
|
||||||
$this->assertPageActions($client, [
|
$this->assertPageActions($client, [
|
||||||
//'back' => $this->createUrl('/admin/user/'),
|
//'back' => $this->createUrl('/admin/user/'),
|
||||||
'create modal-ajax-form' => $this->createUrl('/admin/permissions/roles/create'),
|
'create modal-ajax-form' => $this->createUrl('/admin/permissions/roles/create'),
|
||||||
|
|
|
@ -430,7 +430,7 @@ class ProfileControllerTest extends ControllerBaseTest
|
||||||
2 => ['name' => UserPreference::TIMEZONE, 'value' => 'America/Creston'],
|
2 => ['name' => UserPreference::TIMEZONE, 'value' => 'America/Creston'],
|
||||||
3 => ['name' => UserPreference::LOCALE, 'value' => 'ar'],
|
3 => ['name' => UserPreference::LOCALE, 'value' => 'ar'],
|
||||||
4 => ['name' => UserPreference::FIRST_WEEKDAY, 'value' => 'sunday'],
|
4 => ['name' => UserPreference::FIRST_WEEKDAY, 'value' => 'sunday'],
|
||||||
5 => ['name' => UserPreference::SKIN, 'value' => 'blue'],
|
6 => ['name' => UserPreference::SKIN, 'value' => 'blue'],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
namespace App\Tests\Controller\Reporting;
|
namespace App\Tests\Controller\Reporting;
|
||||||
|
|
||||||
|
use App\Entity\Project;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Tests\Controller\ControllerBaseTest;
|
use App\Tests\Controller\ControllerBaseTest;
|
||||||
use App\Tests\DataFixtures\ActivityFixtures;
|
use App\Tests\DataFixtures\ActivityFixtures;
|
||||||
|
@ -39,6 +40,9 @@ class ProjectDateRangeControllerTest extends ControllerBaseTest
|
||||||
$projects->setCustomers($customers);
|
$projects->setCustomers($customers);
|
||||||
$projects->setAmount(2);
|
$projects->setAmount(2);
|
||||||
$projects->setIsVisible(true);
|
$projects->setIsVisible(true);
|
||||||
|
$projects->setCallback(function (Project $project) {
|
||||||
|
$project->setIsMonthlyBudget();
|
||||||
|
});
|
||||||
$this->importFixture($projects);
|
$this->importFixture($projects);
|
||||||
|
|
||||||
$activities = new ActivityFixtures();
|
$activities = new ActivityFixtures();
|
||||||
|
|
|
@ -49,6 +49,7 @@ class UserTest extends TestCase
|
||||||
self::assertFalse($user->canSeeAllData());
|
self::assertFalse($user->canSeeAllData());
|
||||||
self::assertFalse($user->isSmallLayout());
|
self::assertFalse($user->isSmallLayout());
|
||||||
self::assertFalse($user->isExportDecimal());
|
self::assertFalse($user->isExportDecimal());
|
||||||
|
self::assertTrue($user->is24Hour());
|
||||||
|
|
||||||
$user->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y');
|
$user->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y');
|
||||||
self::assertEquals('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', $user->getAvatar());
|
self::assertEquals('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', $user->getAvatar());
|
||||||
|
@ -481,4 +482,15 @@ class UserTest extends TestCase
|
||||||
$member->setUser(new User());
|
$member->setUser(new User());
|
||||||
$sut->addMembership($member);
|
$sut->addMembership($member);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test24Hour()
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
self::assertTrue($user->is24Hour());
|
||||||
|
self::assertEquals('H:i', $user->getTimeFormat());
|
||||||
|
|
||||||
|
$user->setPreferenceValue(UserPreference::HOUR_24, false);
|
||||||
|
self::assertFalse($user->is24Hour());
|
||||||
|
self::assertEquals('h:i A', $user->getTimeFormat());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ class UserPreferenceSubscriberTest extends TestCase
|
||||||
'language',
|
'language',
|
||||||
'first_weekday',
|
'first_weekday',
|
||||||
'skin',
|
'skin',
|
||||||
|
'hours_24',
|
||||||
'theme.layout',
|
'theme.layout',
|
||||||
'theme.collapsed_sidebar',
|
'theme.collapsed_sidebar',
|
||||||
'theme.update_browser_title',
|
'theme.update_browser_title',
|
||||||
|
|
|
@ -34,6 +34,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
abstract class AbstractRendererTest extends KernelTestCase
|
abstract class AbstractRendererTest extends KernelTestCase
|
||||||
|
@ -52,8 +53,11 @@ abstract class AbstractRendererTest extends KernelTestCase
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->expects($this->any())->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
$translator = $this->createMock(TranslatorInterface::class);
|
$translator = $this->createMock(TranslatorInterface::class);
|
||||||
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages));
|
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages), $security);
|
||||||
|
|
||||||
$dispatcher = new EventDispatcher();
|
$dispatcher = new EventDispatcher();
|
||||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
||||||
|
|
|
@ -10,10 +10,14 @@
|
||||||
namespace App\Tests\Export\Renderer;
|
namespace App\Tests\Export\Renderer;
|
||||||
|
|
||||||
use App\Activity\ActivityStatisticService;
|
use App\Activity\ActivityStatisticService;
|
||||||
|
use App\Entity\User;
|
||||||
use App\Export\Renderer\HtmlRenderer;
|
use App\Export\Renderer\HtmlRenderer;
|
||||||
use App\Project\ProjectStatisticService;
|
use App\Project\ProjectStatisticService;
|
||||||
|
use Symfony\Bridge\Twig\AppVariable;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +47,16 @@ class HtmlRendererTest extends AbstractRendererTest
|
||||||
$kernel = self::bootKernel();
|
$kernel = self::bootKernel();
|
||||||
/** @var Environment $twig */
|
/** @var Environment $twig */
|
||||||
$twig = $kernel->getContainer()->get('twig');
|
$twig = $kernel->getContainer()->get('twig');
|
||||||
|
|
||||||
|
$token = $this->createMock(TokenInterface::class);
|
||||||
|
$token->expects($this->any())->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$tokenStorage = new TokenStorage();
|
||||||
|
$tokenStorage->setToken($token);
|
||||||
|
/** @var AppVariable $app */
|
||||||
|
$app = $twig->getGlobals()['app'];
|
||||||
|
$twig->addGlobal('app', $app);
|
||||||
|
$app->setTokenStorage($tokenStorage);
|
||||||
$stack = $kernel->getContainer()->get('request_stack');
|
$stack = $kernel->getContainer()->get('request_stack');
|
||||||
$request = new Request();
|
$request = new Request();
|
||||||
$request->setLocale('en');
|
$request->setLocale('en');
|
||||||
|
|
|
@ -33,6 +33,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
abstract class AbstractRendererTest extends KernelTestCase
|
abstract class AbstractRendererTest extends KernelTestCase
|
||||||
|
@ -51,8 +52,11 @@ abstract class AbstractRendererTest extends KernelTestCase
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->expects($this->any())->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
|
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
|
||||||
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages));
|
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages), $security);
|
||||||
|
|
||||||
$dispatcher = new EventDispatcher();
|
$dispatcher = new EventDispatcher();
|
||||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
||||||
|
|
|
@ -28,8 +28,11 @@ class ProjectDateRangeQueryTest extends TestCase
|
||||||
self::assertEquals($date->getTimestamp(), $sut->getMonth()->getTimestamp());
|
self::assertEquals($date->getTimestamp(), $sut->getMonth()->getTimestamp());
|
||||||
self::assertSame($user, $sut->getUser());
|
self::assertSame($user, $sut->getUser());
|
||||||
self::assertNull($sut->getCustomer());
|
self::assertNull($sut->getCustomer());
|
||||||
self::assertFalse($sut->isOnlyWithRecords());
|
self::assertTrue($sut->isIncludeNoWork());
|
||||||
|
|
||||||
|
self::assertEquals('month', $sut->getBudgetType());
|
||||||
self::assertFalse($sut->isIncludeNoBudget());
|
self::assertFalse($sut->isIncludeNoBudget());
|
||||||
|
self::assertTrue($sut->isBudgetTypeMonthly());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetterGetter()
|
public function testSetterGetter()
|
||||||
|
@ -41,12 +44,20 @@ class ProjectDateRangeQueryTest extends TestCase
|
||||||
|
|
||||||
$sut->setMonth($date);
|
$sut->setMonth($date);
|
||||||
$sut->setCustomer($customer);
|
$sut->setCustomer($customer);
|
||||||
$sut->setIncludeNoBudget(true);
|
$sut->setIncludeNoWork(false);
|
||||||
$sut->setOnlyWithRecords(true);
|
|
||||||
|
|
||||||
self::assertEquals($date->getTimestamp(), $sut->getMonth()->getTimestamp());
|
self::assertEquals($date->getTimestamp(), $sut->getMonth()->getTimestamp());
|
||||||
self::assertSame($customer, $sut->getCustomer());
|
self::assertSame($customer, $sut->getCustomer());
|
||||||
self::assertTrue($sut->isOnlyWithRecords());
|
self::assertFalse($sut->isIncludeNoWork());
|
||||||
|
|
||||||
|
$sut->setBudgetType('none');
|
||||||
|
self::assertEquals('none', $sut->getBudgetType());
|
||||||
self::assertTrue($sut->isIncludeNoBudget());
|
self::assertTrue($sut->isIncludeNoBudget());
|
||||||
|
self::assertFalse($sut->isBudgetTypeMonthly());
|
||||||
|
|
||||||
|
$sut->setBudgetType('full');
|
||||||
|
self::assertEquals('full', $sut->getBudgetType());
|
||||||
|
self::assertFalse($sut->isBudgetTypeMonthly());
|
||||||
|
self::assertFalse($sut->isIncludeNoBudget());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,22 +42,6 @@ class TokenAuthenticatorTest extends TestCase
|
||||||
self::assertFalse($sut->supports($request));
|
self::assertFalse($sut->supports($request));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetPassword()
|
|
||||||
{
|
|
||||||
$factory = $this->createMock(EncoderFactoryInterface::class);
|
|
||||||
$sut = new TokenAuthenticator($factory);
|
|
||||||
|
|
||||||
self::assertNull($sut->getPassword('asdfgh'));
|
|
||||||
self::assertNull($sut->getPassword(null));
|
|
||||||
self::assertNull($sut->getPassword([]));
|
|
||||||
self::assertNull($sut->getPassword(['password' => '1234567890']));
|
|
||||||
self::assertNull($sut->getPassword(['token' => null]));
|
|
||||||
self::assertNull($sut->getPassword(['token' => 0]));
|
|
||||||
self::assertNull($sut->getPassword(['token' => '']));
|
|
||||||
self::assertNull($sut->getPassword(['token' => false]));
|
|
||||||
self::assertEquals('foo-bar', $sut->getPassword(['token' => 'foo-bar']));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetCredentials()
|
public function testGetCredentials()
|
||||||
{
|
{
|
||||||
$factory = $this->createMock(EncoderFactoryInterface::class);
|
$factory = $this->createMock(EncoderFactoryInterface::class);
|
||||||
|
|
|
@ -15,6 +15,7 @@ use App\Entity\User;
|
||||||
use App\Twig\LocaleFormatExtensions;
|
use App\Twig\LocaleFormatExtensions;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Twig\TwigFilter;
|
use Twig\TwigFilter;
|
||||||
use Twig\TwigFunction;
|
use Twig\TwigFunction;
|
||||||
use Twig\TwigTest;
|
use Twig\TwigTest;
|
||||||
|
@ -43,7 +44,11 @@ class LocaleFormatExtensionsTest extends TestCase
|
||||||
$language = $dateSettings;
|
$language = $dateSettings;
|
||||||
$dateSettings = $locale;
|
$dateSettings = $locale;
|
||||||
}
|
}
|
||||||
$sut = new LocaleFormatExtensions(new LanguageFormattings($dateSettings));
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->expects($this->any())->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$sut = new LocaleFormatExtensions(new LanguageFormattings($dateSettings), $security);
|
||||||
$sut->setLocale($language);
|
$sut->setLocale($language);
|
||||||
|
|
||||||
return $sut;
|
return $sut;
|
||||||
|
@ -220,31 +225,21 @@ class LocaleFormatExtensionsTest extends TestCase
|
||||||
$this->assertEquals('17:53', $sut->time('2016-06-23 17:53'));
|
$this->assertEquals('17:53', $sut->time('2016-06-23 17:53'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHour24()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut('en', [
|
|
||||||
'en' => ['24_hours' => false],
|
|
||||||
]);
|
|
||||||
$this->assertEquals('bar', $sut->hour24('foo', 'bar'));
|
|
||||||
|
|
||||||
$sut = $this->getSut('de', [
|
|
||||||
'de' => ['24_hours' => true],
|
|
||||||
]);
|
|
||||||
$this->assertEquals('foo', $sut->hour24('foo', 'bar'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testDateTimeFull()
|
public function testDateTimeFull()
|
||||||
{
|
{
|
||||||
$sut = $this->getSut('en', [
|
$sut = $this->getSut('en', [
|
||||||
'en' => ['date_time_type' => 'yyyy-MM-dd HH:mm:ss'],
|
'en' => ['date_type' => 'dd-yyyy-MM-'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dateTime = new \DateTime('2019-08-17 12:29:47', new \DateTimeZone(date_default_timezone_get()));
|
$dateTime = new \DateTime('2019-08-17 12:29:47', new \DateTimeZone(date_default_timezone_get()));
|
||||||
$dateTime->setDate(2019, 8, 17);
|
$dateTime->setDate(2019, 8, 17);
|
||||||
$dateTime->setTime(12, 29, 47);
|
$dateTime->setTime(12, 29, 47);
|
||||||
|
|
||||||
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull($dateTime));
|
$this->assertEquals('17-2019-08- 12:29', $sut->dateTimeFull($dateTime));
|
||||||
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull('2019-08-17 12:29:47'));
|
$this->assertEquals('17-2019-08- 12:29', $sut->dateTimeFull('2019-08-17 12:29:47'));
|
||||||
|
|
||||||
|
$dateTime = new \DateTime('2019-08-17 00:00:00');
|
||||||
|
$this->assertEquals('17-2019-08-', $sut->dateTimeFull($dateTime, true));
|
||||||
|
|
||||||
// next test checks the fallback for errors while converting the date
|
// next test checks the fallback for errors while converting the date
|
||||||
/* @phpstan-ignore-next-line */
|
/* @phpstan-ignore-next-line */
|
||||||
|
|
|
@ -28,61 +28,50 @@ class LocaleFormatsTest extends TestCase
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'de' => [
|
'de' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'date_time' => 'd.m. H:i',
|
'date_time' => 'd.m. H:i',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
'time' => 'H:i',
|
'time' => 'H:i',
|
||||||
'24_hours' => true,
|
|
||||||
],
|
],
|
||||||
'en' => [
|
'en' => [
|
||||||
'date_time_type' => 'yyyy-MM-dd HH:mm',
|
|
||||||
'date_type' => 'yyyy-MM-dd',
|
'date_type' => 'yyyy-MM-dd',
|
||||||
'date' => 'Y-m-d',
|
'date' => 'Y-m-d',
|
||||||
'date_time' => 'm-d H:i',
|
'date_time' => 'm-d H:i',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
'time' => 'H:i:s',
|
'time' => 'H:i:s',
|
||||||
'24_hours' => false,
|
|
||||||
],
|
],
|
||||||
'pt_BR' => [
|
'pt_BR' => [
|
||||||
'date_time_type' => 'dd-MM-yyyy HH:mm',
|
|
||||||
'date_type' => 'dd-MM-yyyy',
|
'date_type' => 'dd-MM-yyyy',
|
||||||
'date' => 'd-m-Y',
|
'date' => 'd-m-Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'it' => [
|
'it' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'fr' => [
|
'fr' => [
|
||||||
'date_time_type' => 'dd/MM/yyyy HH:mm',
|
|
||||||
'date_type' => 'dd/MM/yyyy',
|
'date_type' => 'dd/MM/yyyy',
|
||||||
'date' => 'd/m/Y',
|
'date' => 'd/m/Y',
|
||||||
'duration' => '%h h %m',
|
'duration' => '%h h %m',
|
||||||
],
|
],
|
||||||
'es' => [
|
'es' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'ru' => [
|
'ru' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'ar' => [
|
'ar' => [
|
||||||
'date_time_type' => 'yyyy-MM-dd HH:mm',
|
|
||||||
'date_type' => 'yyyy-MM-dd',
|
'date_type' => 'yyyy-MM-dd',
|
||||||
'date' => 'Y-m-d',
|
'date' => 'Y-m-d',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'hu' => [
|
'hu' => [
|
||||||
'date_time_type' => 'yyyy.MM.dd HH:mm',
|
|
||||||
'date_type' => 'yyyy.MM.dd',
|
'date_type' => 'yyyy.MM.dd',
|
||||||
'date' => 'Y.m.d.',
|
'date' => 'Y.m.d.',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
|
@ -160,32 +149,9 @@ class LocaleFormatsTest extends TestCase
|
||||||
$this->assertEquals('yyyy-MM-dd HH:mm', $sut->getDateTimeTypeFormat());
|
$this->assertEquals('yyyy-MM-dd HH:mm', $sut->getDateTimeTypeFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetDateTimePickerFormat()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut('en', $this->getDefaultSettings());
|
|
||||||
$this->assertEquals('YYYY-MM-DD HH:mm', $sut->getDateTimePickerFormat());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testIs24Hours()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut('en', $this->getDefaultSettings());
|
|
||||||
$this->assertFalse($sut->isTwentyFourHours());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetTimeFormat()
|
public function testGetTimeFormat()
|
||||||
{
|
{
|
||||||
$sut = $this->getSut('en', $this->getDefaultSettings());
|
$sut = $this->getSut('en', $this->getDefaultSettings());
|
||||||
$this->assertEquals('H:i:s', $sut->getTimeFormat());
|
$this->assertEquals('H:i:s', $sut->getTimeFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnknownSetting()
|
|
||||||
{
|
|
||||||
$this->expectException(\InvalidArgumentException::class);
|
|
||||||
$this->expectExceptionMessage('Unknown setting for locale en: date_time_type');
|
|
||||||
|
|
||||||
$sut = $this->getSut('en', ['en' => [
|
|
||||||
'xxx' => 'dd.MM.yyyy HH:mm',
|
|
||||||
]]);
|
|
||||||
$sut->getDateTimePickerFormat();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="ar" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="ar" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>مساعدة</target>
|
<target>مساعدة</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>شكر خاص يذهب الى…</target>
|
<target>شكر خاص يذهب الى…</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>تبرع لمستقبل KIMAI</target>
|
<target>تبرع لمستقبل KIMAI</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>الصفحة الرئيسية</target>
|
<target>الصفحة الرئيسية</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>الدعم</target>
|
<target>الدعم</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>حول Kimai</target>
|
<target>حول Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>تم نشره حسب %kimai%</target>
|
<target>تم نشره حسب %kimai%</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… مؤلفو مكتبات البرامج الحالية ،، لم يكون kimai قادراً على إكمالها بدونك 👍</target>
|
<target>… مؤلفو مكتبات البرامج الحالية ،، لم يكون kimai قادراً على إكمالها بدونك 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="cs" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="cs" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>O Kimai</target>
|
<target>O Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Podpora</target>
|
<target>Podpora</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Úvodní stránka</target>
|
<target>Úvodní stránka</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Dokumentace</target>
|
<target>Dokumentace</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Podpořte budoucnost Kimai</target>
|
<target>Podpořte budoucnost Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% je zveřejněno pod</target>
|
<target>%kimai% je zveřejněno pod</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Zvláští poděkování patří …</target>
|
<target>Zvláští poděkování patří …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… autoři následujících softwarových knihoven, Kimai, by bez tebe nebyo možné 👍</target>
|
<target>… autoři následujících softwarových knihoven, Kimai, by bez tebe nebyo možné 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="da" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="da" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Om Kimai</target>
|
<target>Om Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Support</target>
|
<target>Support</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Hjemmeside</target>
|
<target>Hjemmeside</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Dokumentation</target>
|
<target>Dokumentation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Støt Kimai's fremtid</target>
|
<target>Støt Kimai's fremtid</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% er udgivet under</target>
|
<target>%kimai% er udgivet under</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Tak til …</target>
|
<target>Tak til …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… udviklerne af følgende softwarebiblioteker, Kimai ville ikke være muligt uden jer 👍</target>
|
<target>… udviklerne af følgende softwarebiblioteker, Kimai ville ikke være muligt uden jer 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="de" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="de" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Über Kimai</target>
|
<target>Über Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Support</target>
|
<target>Support</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website" approved="yes">
|
<trans-unit id="dHqPOYO" approved="yes" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Homepage</target>
|
<target>Homepage</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Dokumentation</target>
|
<target>Dokumentation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Spende für die Zukunft von Kimai</target>
|
<target>Spende für die Zukunft von Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% wird veröffentlicht unter</target>
|
<target>%kimai% wird veröffentlicht unter</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Besonderer Dank geht an …</target>
|
<target>Besonderer Dank geht an …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… die Autoren der folgenden Software-Bibliotheken, ohne Euch wäre Kimai nicht möglich 👍</target>
|
<target>… die Autoren der folgenden Software-Bibliotheken, ohne Euch wäre Kimai nicht möglich 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="el" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="el" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Σχετικά με το Kimai</target>
|
<target>Σχετικά με το Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Υποστήριξη</target>
|
<target>Υποστήριξη</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Αρχική σελίδα</target>
|
<target>Αρχική σελίδα</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Τεκμηρίωση</target>
|
<target>Τεκμηρίωση</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Δωρίστε για το μέλλον του Kimai</target>
|
<target>Δωρίστε για το μέλλον του Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% δημοσιεύτηκε κάτω από</target>
|
<target>%kimai% δημοσιεύτηκε κάτω από</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Ιδιαίτερες ευχαριστίες πηγαίνουν …</target>
|
<target>Ιδιαίτερες ευχαριστίες πηγαίνουν …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… στους συγγραφείς των παρακάτω βιβλιοθηκών-λογισμικού, δεν θα ήταν πραγματοποιήσιμο το Kimai χωρίς εσάς 👍</target>
|
<target>… στους συγγραφείς των παρακάτω βιβλιοθηκών-λογισμικού, δεν θα ήταν πραγματοποιήσιμο το Kimai χωρίς εσάς 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="en" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="en" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>About Kimai</target>
|
<target>About Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Support</target>
|
<target>Support</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Homepage</target>
|
<target>Homepage</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Documentation</target>
|
<target>Documentation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Donate for the future of Kimai</target>
|
<target>Donate for the future of Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% is published under</target>
|
<target>%kimai% is published under</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Special thanks go to …</target>
|
<target>Special thanks go to …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… the authors of the following software-libraries, Kimai wouldn't be possible without you 👍</target>
|
<target>… the authors of the following software-libraries, Kimai wouldn't be possible without you 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="eo" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="eo" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Pri Kimai</target>
|
<target>Pri Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Subteno</target>
|
<target>Subteno</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Hejmpaĝo</target>
|
<target>Hejmpaĝo</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Dokumentado</target>
|
<target>Dokumentado</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Donacu por la estonteco de Kimai</target>
|
<target>Donacu por la estonteco de Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% estas publikigita sub</target>
|
<target>%kimai% estas publikigita sub</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Speciala danko al …</target>
|
<target>Speciala danko al …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… la aŭtoroj de la sekvaj programaraj bibliotekoj, Kimai ne estus ebla sen vi 👍</target>
|
<target>… la aŭtoroj de la sekvaj programaraj bibliotekoj, Kimai ne estus ebla sen vi 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="es" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="es" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Acerca de Kimai</target>
|
<target>Acerca de Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Asistencia</target>
|
<target>Asistencia</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Sitio web</target>
|
<target>Sitio web</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Documentación</target>
|
<target>Documentación</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Haga una donación por el futuro de Kimai</target>
|
<target>Haga una donación por el futuro de Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% está publicado en virtud de la</target>
|
<target>%kimai% está publicado en virtud de la</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Agradecimientos especiales para…</target>
|
<target>Agradecimientos especiales para…</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… los autores de las bibliotecas de software siguientes; Kimai no sería posible sin vosotros 👍</target>
|
<target>… los autores de las bibliotecas de software siguientes; Kimai no sería posible sin vosotros 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="eu" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="eu" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Kimairi buruz</target>
|
<target>Kimairi buruz</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Mantenua</target>
|
<target>Mantenua</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Web orrialdea</target>
|
<target>Web orrialdea</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Laguntza</target>
|
<target>Laguntza</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Egin dohaintza bat Kimai-en etorkizunerako</target>
|
<target>Egin dohaintza bat Kimai-en etorkizunerako</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% lizentzia</target>
|
<target>%kimai% lizentzia</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Mila esker …</target>
|
<target>Mila esker …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… Kimai ez lebilke ondorengo kideen laguntzarik gabe: 👍</target>
|
<target>… Kimai ez lebilke ondorengo kideen laguntzarik gabe: 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="fa" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="fa" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>درباره ی کیمیایْ</target>
|
<target>درباره ی کیمیایْ</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>پشتیبانی</target>
|
<target>پشتیبانی</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>خانه</target>
|
<target>خانه</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>مستندات</target>
|
<target>مستندات</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>سپاس ویژه از…</target>
|
<target>سپاس ویژه از…</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="fi" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="fi" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target state="translated">Tietoa Kimaista</target>
|
<target state="translated">Tietoa Kimaista</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target state="translated">Tuki</target>
|
<target state="translated">Tuki</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target state="translated">Kotisivu</target>
|
<target state="translated">Kotisivu</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target state="translated">Dokumentit</target>
|
<target state="translated">Dokumentit</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target state="translated">Tee lahjoitus Kimain tulevaisuudelle</target>
|
<target state="translated">Tee lahjoitus Kimain tulevaisuudelle</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target state="translated">%kimai% julkaisu on</target>
|
<target state="translated">%kimai% julkaisu on</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target state="translated">Erityiskiitokset menee …</target>
|
<target state="translated">Erityiskiitokset menee …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target state="translated">… seuraavien ohjelmistokirjastojen tekijöille, Kimai ei olisi mahdollinen ilman teitä 👍</target>
|
<target state="translated">… seuraavien ohjelmistokirjastojen tekijöille, Kimai ei olisi mahdollinen ilman teitä 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="fr" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="fr" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>À propos de Kimai</target>
|
<target>À propos de Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Assistance</target>
|
<target>Assistance</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Accueil</target>
|
<target>Accueil</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Documentation</target>
|
<target>Documentation</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Faites un don pour l'avenir de Kimai</target>
|
<target>Faites un don pour l'avenir de Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% est publié sous</target>
|
<target>%kimai% est publié sous</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Des remerciements particuliers vont à …</target>
|
<target>Des remerciements particuliers vont à …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>…l'ensemble des auteurs des logiciels et bibliothèques suivants, Kimai n'existerait pas sans vous 👍</target>
|
<target>…l'ensemble des auteurs des logiciels et bibliothèques suivants, Kimai n'existerait pas sans vous 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,39 +1,36 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="he" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="he" datatype="plaintext" original="about.en.xlf">
|
||||||
<header>
|
|
||||||
<tool tool-id="symfony" tool-name="Symfony"/>
|
|
||||||
</header>
|
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>על אודות קימאי</target>
|
<target>על אודות קימאי</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>תמיכה</target>
|
<target>תמיכה</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>דף הבית</target>
|
<target>דף הבית</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>תיעוד</target>
|
<target>תיעוד</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>תרומה לעתיד של קימאי</target>
|
<target>תרומה לעתיד של קימאי</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% מופץ בכפוף</target>
|
<target>%kimai% מופץ בכפוף</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>תודה מיוחדת מגיעה ל…</target>
|
<target>תודה מיוחדת מגיעה ל…</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… המפתחים של רכיבי התכנה האלה, קימאי לא הייתה קיימת בלעדיכם 👍</target>
|
<target>… המפתחים של רכיבי התכנה האלה, קימאי לא הייתה קיימת בלעדיכם 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="hr" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="hr" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Kimai informacije</target>
|
<target>Kimai informacije</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Podrška</target>
|
<target>Podrška</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Web-stranica</target>
|
<target>Web-stranica</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Dokumentacija</target>
|
<target>Dokumentacija</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Doniraj za budućnost Kimaija</target>
|
<target>Doniraj za budućnost Kimaija</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% je izdan pod</target>
|
<target>%kimai% je izdan pod</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Posebno zahvaljujemo …</target>
|
<target>Posebno zahvaljujemo …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… autorima sljedećih softverskih biblioteka. Kimai ne bi bio moguć bez vas 👍</target>
|
<target>… autorima sljedećih softverskih biblioteka. Kimai ne bi bio moguć bez vas 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="hu" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="hu" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>A Kimai névjegye</target>
|
<target>A Kimai névjegye</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Támogatás</target>
|
<target>Támogatás</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Weboldal</target>
|
<target>Weboldal</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Súgó</target>
|
<target>Súgó</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Támogasd a Kimai jövőjét</target>
|
<target>Támogasd a Kimai jövőjét</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% licensz</target>
|
<target>%kimai% licensz</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Külön köszönet …</target>
|
<target>Külön köszönet …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… a következő szoftver-könyvtárak fejlesztőinek, Kimai nem jöhetett volna létre nélkületek 👍</target>
|
<target>… a következő szoftver-könyvtárak fejlesztőinek, Kimai nem jöhetett volna létre nélkületek 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="it" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="it" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Approposito di Kimai</target>
|
<target>Approposito di Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>Supporto</target>
|
<target>Supporto</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>Sito web</target>
|
<target>Sito web</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>Aiuto</target>
|
<target>Aiuto</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Donazione per lo sviluppo Kimai</target>
|
<target>Donazione per lo sviluppo Kimai</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% è pubblicato su</target>
|
<target>%kimai% è pubblicato su</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Ringraziamenti speciali vanno a …</target>
|
<target>Ringraziamenti speciali vanno a …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… gli autori delle seguenti librerie software, Kimai non esisterebbe senza loro 👍</target>
|
<target>… gli autori delle seguenti librerie software, Kimai non esisterebbe senza loro 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
<file source-language="en" target-language="ja" datatype="plaintext" original="about.en.xlf">
|
<file source-language="en" target-language="ja" datatype="plaintext" original="about.en.xlf">
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="about.title">
|
<trans-unit id="3Clo55j" resname="about.title">
|
||||||
<source>about.title</source>
|
<source>about.title</source>
|
||||||
<target>Kimai について</target>
|
<target>Kimai について</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="support">
|
<trans-unit id="oYYDCG5" resname="support">
|
||||||
<source>support</source>
|
<source>support</source>
|
||||||
<target>サポート</target>
|
<target>サポート</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="website">
|
<trans-unit id="dHqPOYO" resname="website">
|
||||||
<source>website</source>
|
<source>website</source>
|
||||||
<target>ホームページ</target>
|
<target>ホームページ</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="help">
|
<trans-unit id="EGpYQvx" resname="help">
|
||||||
<source>help</source>
|
<source>help</source>
|
||||||
<target>ドキュメント</target>
|
<target>ドキュメント</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="donate">
|
<trans-unit id="mz4ieCN" resname="donate">
|
||||||
<source>donate</source>
|
<source>donate</source>
|
||||||
<target>Kimai の発展のために寄付を行う</target>
|
<target>Kimai の発展のために寄付を行う</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="published_under">
|
<trans-unit id="DwbowBh" resname="published_under">
|
||||||
<source>published_under</source>
|
<source>published_under</source>
|
||||||
<target>%kimai% は次のもとに公開されています</target>
|
<target>%kimai% は次のもとに公開されています</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="special_thanks">
|
<trans-unit id="A0nWoXZ" resname="special_thanks">
|
||||||
<source>special_thanks</source>
|
<source>special_thanks</source>
|
||||||
<target>Special thanks go to …</target>
|
<target>Special thanks go to …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="library_authors">
|
<trans-unit id="L7cff3Q" resname="library_authors">
|
||||||
<source>library_authors</source>
|
<source>library_authors</source>
|
||||||
<target>… これらのソフトウェア・ライブラリなくして Kimai は実現できませんでした。作成者の方々に感謝します 👍</target>
|
<target>… これらのソフトウェア・ライブラリなくして Kimai は実現できませんでした。作成者の方々に感謝します 👍</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue