Release 2.25 (#5109)
This commit is contained in:
@@ -15,7 +15,7 @@ export default class KimaiMultiUpdateTable extends KimaiPlugin {
|
||||
|
||||
init()
|
||||
{
|
||||
if (document.getElementById('multi_update_all') === null) {
|
||||
if (document.getElementsByClassName('multi_update_all').length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,64 +23,64 @@ export default class KimaiMultiUpdateTable extends KimaiPlugin {
|
||||
// via KimaiDatable and everything inside will be removed, including event listeners
|
||||
const element = document.querySelector('div.page-body');
|
||||
element.addEventListener('change', (event) => {
|
||||
if (event.target.matches('#multi_update_all')) {
|
||||
if (event.target.matches('.multi_update_all')) {
|
||||
// the "check all" checkbox in the upper start corner of the table
|
||||
const checked = event.target.checked;
|
||||
for (const element of document.querySelectorAll('.multi_update_single')) {
|
||||
const table = event.target.closest('table');
|
||||
for (const element of table.querySelectorAll('.multi_update_single')) {
|
||||
element.checked = checked;
|
||||
}
|
||||
this._toggleForm();
|
||||
this._toggleForm(table);
|
||||
event.stopPropagation();
|
||||
} else if (event.target.matches('.multi_update_single')) {
|
||||
// single checkboxes in front of each row
|
||||
this._toggleForm();
|
||||
this._toggleForm(event.target.closest('table'));
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
element.addEventListener('click', (event) => {
|
||||
if (event.target.matches('.multi_update_table_action')) {
|
||||
const selectedItem = event.target;
|
||||
const ids = this._getSelectedIds();
|
||||
const form = document.getElementById('multi_update_form');
|
||||
const question = form.dataset['question'].replace(/%action%/, selectedItem.textContent).replace(/%count%/, ids.length.toString());
|
||||
const selectedButton = event.target;
|
||||
const form = selectedButton.form;
|
||||
const ids = form.querySelector('.multi_update_ids').value.split(',');
|
||||
const question = form.dataset['question'].replace(/%action%/, selectedButton.textContent).replace(/%count%/, ids.length.toString());
|
||||
|
||||
/** @type {KimaiAlert} ALERT */
|
||||
const ALERT = this.getPlugin('alert');
|
||||
ALERT.question(question, function(value) {
|
||||
if (value) {
|
||||
const form = document.getElementById('multi_update_form');
|
||||
form.action = selectedItem.dataset['href'];
|
||||
form.action = selectedButton.dataset['href'];
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_getSelectedIds()
|
||||
|
||||
/**
|
||||
* @param {HTMLTableElement} table
|
||||
* @private
|
||||
*/
|
||||
_toggleForm(table)
|
||||
{
|
||||
const card = table.closest('div.card.data_table')
|
||||
|
||||
let ids = [];
|
||||
for (const box of document.querySelectorAll('input.multi_update_single:checked')) {
|
||||
for (const box of table.querySelectorAll('input.multi_update_single:checked')) {
|
||||
ids.push(box.value);
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
_toggleForm()
|
||||
{
|
||||
const ids = this._getSelectedIds();
|
||||
document.getElementById('multi_update_table_entities').value = ids.join(',');
|
||||
card.querySelector('.multi_update_ids').value = ids.join(',');
|
||||
|
||||
if (ids.length > 0) {
|
||||
for (const element of document.getElementsByClassName('multi_update_form_hide')) {
|
||||
for (const element of card.querySelectorAll('.multi_update_form_hide')) {
|
||||
element.style.setProperty('display', 'none', 'important');
|
||||
}
|
||||
document.getElementById('multi_update_form').style.display = null;//'block';
|
||||
card.querySelector('form.multi_update_form').style.display = null;//'block';
|
||||
} else {
|
||||
document.getElementById('multi_update_form').style.setProperty('display', 'none', 'important');
|
||||
for (const element of document.getElementsByClassName('multi_update_form_hide')) {
|
||||
card.querySelector('form.multi_update_form').style.setProperty('display', 'none', 'important');
|
||||
for (const element of card.querySelectorAll('.multi_update_form_hide')) {
|
||||
element.style.display = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,111 +5,30 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
font-size: 10pt;
|
||||
margin: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
table, tr, td, th, p, h1, h2, h3, h4, h5 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
th {
|
||||
border: none;
|
||||
text-align: left;
|
||||
}
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.text-small {
|
||||
font-size: 7pt;
|
||||
}
|
||||
.padding-left {
|
||||
padding-left: 15px;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-left, .text-begin {
|
||||
text-align: left;
|
||||
}
|
||||
.text-right, .text-end {
|
||||
text-align: right;
|
||||
}
|
||||
.text-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.date {
|
||||
font-size: 80%;
|
||||
text-align: right;
|
||||
padding-top: 6px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
}
|
||||
table.addresses, p {
|
||||
line-height: 14pt;
|
||||
}
|
||||
/* The address table */
|
||||
table.addresses {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
/* The PDF header */
|
||||
table.header {
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
table.header .title {
|
||||
font-weight: normal;
|
||||
font-size: 140%;
|
||||
}
|
||||
/* The PDF footer */
|
||||
table.footer {
|
||||
border-top: 1px solid #999;
|
||||
font-size: 80%;
|
||||
padding-top: 8px;
|
||||
line-height: 14px;
|
||||
}
|
||||
table.footer td {
|
||||
vertical-align: top;
|
||||
}
|
||||
/* The invoice items list */
|
||||
table.items {
|
||||
border-collapse: collapse;
|
||||
margin-top: 50px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
table.items thead th,
|
||||
table.items tbody td {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
table.items thead th.first,
|
||||
table.items tbody td.first {
|
||||
padding-left: 0;
|
||||
}
|
||||
table.items thead th.last,
|
||||
table.items tbody td.last,
|
||||
table.items tfoot td.last {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
table.items td,
|
||||
table.items th {
|
||||
padding: 8px 6px;
|
||||
}
|
||||
table.items tfoot td,
|
||||
table.items tfoot th {
|
||||
padding: 20px 0 0 0;
|
||||
line-height: 1px;
|
||||
}
|
||||
table.items tfoot td.last {
|
||||
padding-left: 10px;
|
||||
}
|
||||
table.items tr.odd {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
body { font-family: sans-serif; font-size: 10pt; margin: 4px; padding: 0; }
|
||||
table, tr, td, th, p, h1, h2, h3, h4, h5 { padding: 0; margin: 0;}
|
||||
th { border: none; text-align: left; }
|
||||
td { vertical-align: top; }
|
||||
p { margin-bottom: 12px; }
|
||||
.text-small { font-size: 7pt; }
|
||||
.padding-left { padding-left: 15px; }
|
||||
.text-center { text-align: center; }
|
||||
.text-left, .text-begin { text-align: left; }
|
||||
.text-right, .text-end { text-align: right; }
|
||||
.text-nowrap { white-space: nowrap; }
|
||||
.date { font-size: 80%; text-align: right; padding-top: 6px; }
|
||||
table { width: 100%; border-spacing: 0; }
|
||||
table.addresses, p { line-height: 14pt; }
|
||||
table.addresses { margin-bottom: 30px; }
|
||||
table.header { padding-bottom: 4px; border-bottom: 1px solid #999; }
|
||||
table.header .title { font-weight: normal; font-size: 140%; }
|
||||
table.footer { border-top: 1px solid #999; font-size: 80%; padding-top: 8px; line-height: 14px; }
|
||||
table.footer td { vertical-align: top; }
|
||||
table.items { border-collapse: collapse; margin-top: 50px; margin-bottom: 60px; }
|
||||
table.items thead th, table.items tbody td { border-bottom: 1px solid #ccc; }
|
||||
table.items thead th.first, table.items tbody td.first { padding-left: 0; }
|
||||
table.items thead th.last, table.items tbody td.last, table.items tfoot td.last { padding-right: 0; }
|
||||
table.items td, table.items th { padding: 8px 6px; }
|
||||
table.items tfoot td, table.items tfoot th { padding: 20px 0 0 0; line-height: 1px; }
|
||||
table.items tfoot td.last { padding-left: 10px; }
|
||||
table.items tr.odd { background-color: #f5f5f5; }
|
||||
|
||||
@@ -49,3 +49,14 @@ fieldset:empty {
|
||||
--litepicker-is-in-range-color: var(--tblr-primary-text-emphasis);
|
||||
}
|
||||
}
|
||||
|
||||
/* fixes contrast of batch-update checkboxes - https://github.com/kimai/kimai/issues/5146 */
|
||||
.multiupdater[type=checkbox]
|
||||
{
|
||||
--tblr-border-color-translucent: rgba(4, 32, 69, .2);
|
||||
}
|
||||
|
||||
/* fixes height for long tags https://github.com/kimai/kimai/issues/5169 */
|
||||
.tag {
|
||||
--tblr-tag-height: unset;
|
||||
}
|
||||
@@ -98,7 +98,7 @@ table.dataTable {
|
||||
width: 15px;
|
||||
}
|
||||
&.overlapping {
|
||||
border-top: 2px solid rgba(214,57,57,.1);
|
||||
border-top: 2px solid rgba(214,57,57,.2);
|
||||
}
|
||||
&.exported {
|
||||
opacity: 0.7;
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"symfony/mailer": "^6.0",
|
||||
"symfony/mime": "^6.0",
|
||||
"symfony/monolog-bundle": "^3.4",
|
||||
"symfony/process": "^6.0",
|
||||
"symfony/rate-limiter": "^6.0",
|
||||
"symfony/runtime": "^6.0",
|
||||
"symfony/security-bundle": "^6.0",
|
||||
@@ -83,12 +84,12 @@
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.2",
|
||||
"fakerphp/faker": "^1.15",
|
||||
"friendsofphp/php-cs-fixer": "^3.3",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0",
|
||||
"phpstan/phpstan-doctrine": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpstan/phpstan-symfony": "^1.0",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||
"phpstan/phpstan-doctrine": "^2.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"phpstan/phpstan-symfony": "^2.0",
|
||||
"phpunit/phpunit": "9.5.*",
|
||||
"symfony/browser-kit": "^6.0",
|
||||
"symfony/css-selector": "^6.0",
|
||||
@@ -134,6 +135,10 @@
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-mbstring": "*",
|
||||
"symfony/polyfill-intl": "*",
|
||||
"symfony/polyfill-intl-icu": "*",
|
||||
"symfony/polyfill-intl-grapheme": "*",
|
||||
"symfony/polyfill-intl-idn": "*",
|
||||
"symfony/polyfill-intl-normalizer": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
|
||||
1698
composer.lock
generated
1698
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -24,9 +24,9 @@
|
||||
],
|
||||
"packageManager": "yarn@3.2.1",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/eslint-parser": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@fortawesome/fontawesome-free": "^6.5",
|
||||
"@fullcalendar/bootstrap5": "^5.11",
|
||||
"@fullcalendar/core": "^5.11",
|
||||
@@ -47,7 +47,10 @@
|
||||
"sass": "^1.62",
|
||||
"sass-loader": "^13.2",
|
||||
"tom-select": "^2.2",
|
||||
"webpack": "^5.91",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^4.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
66
phpstan.neon
66
phpstan.neon
@@ -21,12 +21,10 @@ parameters:
|
||||
booleansInConditions: false
|
||||
uselessCast: true
|
||||
requireParentConstructorCall: true
|
||||
disallowedConstructs: false
|
||||
overwriteVariablesWithLoop: true
|
||||
closureUsesThis: true
|
||||
matchingInheritedMethodNames: true
|
||||
numericOperandsInArithmeticOperators: true
|
||||
strictCalls: false
|
||||
switchConditionsMatchingType: true
|
||||
noVariableVariables: false
|
||||
paths:
|
||||
@@ -290,7 +288,7 @@ parameters:
|
||||
path: src/Command/ExportCreateCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$locale of static method Symfony\\\\Polyfill\\\\Intl\\\\Icu\\\\Locale\\:\\:setDefault\\(\\) expects string, mixed given\\.$#"
|
||||
message: "#^Parameter \\#1 \\$locale of static method Locale\\:\\:setDefault\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/ExportCreateCommand.php
|
||||
|
||||
@@ -315,7 +313,7 @@ parameters:
|
||||
path: src/Command/ExportCreateCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$name of method Symfony\\\\Component\\\\HttpFoundation\\\\File\\\\File\\:\\:move\\(\\) expects string\\|null, array\\<int, string\\>\\|string given\\.$#"
|
||||
message: "#^Parameter \\#2 \\$name of method Symfony\\\\Component\\\\HttpFoundation\\\\File\\\\File\\:\\:move\\(\\) expects string\\|null, list\\<string\\>\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/ExportCreateCommand.php
|
||||
|
||||
@@ -465,7 +463,7 @@ parameters:
|
||||
path: src/Command/TranslationCommand.php
|
||||
|
||||
-
|
||||
message: "#^Argument of an invalid type array\\<int, string\\>\\|false supplied for foreach, only iterables are supported\\.$#"
|
||||
message: "#^Argument of an invalid type list\\<string\\>\\|false supplied for foreach, only iterables are supported\\.$#"
|
||||
count: 4
|
||||
path: src/Command/TranslationCommand.php
|
||||
|
||||
@@ -1294,16 +1292,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Entity/Team.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\Team\\:\\:getTeamleads\\(\\) should return array\\<App\\\\Entity\\\\User\\> but returns array\\<int, App\\\\Entity\\\\User\\|null\\>\\.$#"
|
||||
count: 1
|
||||
path: src/Entity/Team.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Entity\\\\Team\\:\\:getUsers\\(\\) should return array\\<App\\\\Entity\\\\User\\> but returns array\\<int, App\\\\Entity\\\\User\\|null\\>\\.$#"
|
||||
count: 1
|
||||
path: src/Entity/Team.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Entity\\\\Team\\:\\:\\$activities with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
|
||||
count: 1
|
||||
@@ -1465,7 +1453,7 @@ parameters:
|
||||
path: src/Entity/UserPreference.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#"
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\<string\\>\\|string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Entity/UserPreference.php
|
||||
|
||||
@@ -1479,11 +1467,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Entity/UserPreference.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Entity\\\\UserPreference\\:\\:\\$value \\(string\\|null\\) does not accept mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Entity/UserPreference.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Event\\\\AbstractTimesheetMultipleEvent\\:\\:getTimesheets\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -1809,11 +1792,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Export/Spreadsheet/EntityWithMetaFieldsExporter.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Export\\\\Spreadsheet\\\\Extractor\\\\AnnotationExtractor\\:\\:extract\\(\\) should return array\\<App\\\\Export\\\\Spreadsheet\\\\ColumnDefinition\\> but returns array\\<int, App\\\\Export\\\\Spreadsheet\\\\ColumnDefinition\\|null\\>\\.$#"
|
||||
count: 1
|
||||
path: src/Export/Spreadsheet/Extractor/AnnotationExtractor.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\<T of object\\>\\|T of object, string given\\.$#"
|
||||
count: 1
|
||||
@@ -2005,7 +1983,7 @@ parameters:
|
||||
path: src/Form/Helper/ActivityHelper.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\<string\\>\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Helper/ActivityHelper.php
|
||||
|
||||
@@ -2020,7 +1998,7 @@ parameters:
|
||||
path: src/Form/Helper/CustomerHelper.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\<string\\>\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Helper/CustomerHelper.php
|
||||
|
||||
@@ -2035,7 +2013,7 @@ parameters:
|
||||
path: src/Form/Helper/ProjectHelper.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\<string\\>\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Form/Helper/ProjectHelper.php
|
||||
|
||||
@@ -3365,7 +3343,7 @@ parameters:
|
||||
path: src/Invoice/Renderer/AbstractSpreadsheetRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#"
|
||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\<string\\>\\|string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Invoice/Renderer/AbstractSpreadsheetRenderer.php
|
||||
|
||||
@@ -3395,7 +3373,7 @@ parameters:
|
||||
path: src/Invoice/Renderer/DocxRenderer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, mixed given\\.$#"
|
||||
message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\<float\\|int\\|string\\>\\|string, mixed given\\.$#"
|
||||
count: 2
|
||||
path: src/Invoice/Renderer/DocxRenderer.php
|
||||
|
||||
@@ -3559,11 +3537,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Ldap/LdapUserProvider.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\.\\.\\.\\$addresses of method Symfony\\\\Component\\\\Mime\\\\Email\\:\\:from\\(\\) expects string\\|Symfony\\\\Component\\\\Mime\\\\Address, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Mail/KimaiMailer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\.\\.\\.\\$arrays of function array_merge expects array, mixed given\\.$#"
|
||||
count: 1
|
||||
@@ -3579,11 +3552,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Model/MonthlyStatistic.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Model\\\\MonthlyStatistic\\:\\:getYears\\(\\) should return array\\<string\\> but returns array\\<int, int\\|string\\>\\.$#"
|
||||
count: 1
|
||||
path: src/Model/MonthlyStatistic.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Model\\\\Statistic\\\\Day\\:\\:getDetails\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -4450,7 +4418,7 @@ parameters:
|
||||
path: src/Utils/LocaleFormatter.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\LocaleFormatter\\:\\:durationDecimal\\(\\) should return string but returns bool\\|string\\.$#"
|
||||
message: "#^Method App\\\\Utils\\\\LocaleFormatter\\:\\:durationDecimal\\(\\) should return string but returns string\\|false\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/LocaleFormatter.php
|
||||
|
||||
@@ -4460,17 +4428,17 @@ parameters:
|
||||
path: src/Utils/LocaleFormatter.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Utils\\\\LocaleFormatter\\:\\:money\\(\\) should return string but returns bool\\|string\\.$#"
|
||||
message: "#^Method App\\\\Utils\\\\LocaleFormatter\\:\\:money\\(\\) should return string but returns string\\|false\\.$#"
|
||||
count: 2
|
||||
path: src/Utils/LocaleFormatter.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$num of method NumberFormatter\\:\\:format\\(\\) expects float\\|int, float\\|int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/LocaleFormatter.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$num of method Symfony\\\\Polyfill\\\\Intl\\\\Icu\\\\NumberFormatter\\:\\:format\\(\\) expects float\\|int, float\\|int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/LocaleFormatter.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$currency of method Symfony\\\\Polyfill\\\\Intl\\\\Icu\\\\NumberFormatter\\:\\:formatCurrency\\(\\) expects string, string\\|null given\\.$#"
|
||||
message: "#^Parameter \\#2 \\$currency of method NumberFormatter\\:\\:formatCurrency\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Utils/LocaleFormatter.php
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
public/build/app.06423747.js
Normal file
2
public/build/app.06423747.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": {
|
||||
"js": [
|
||||
"/build/runtime.74179306.js",
|
||||
"/build/app.6cdb49cb.js"
|
||||
"/build/app.06423747.js"
|
||||
],
|
||||
"css": [
|
||||
"/build/app.1ed18844.css"
|
||||
"/build/app.6bd98062.css"
|
||||
]
|
||||
},
|
||||
"app-rtl": {
|
||||
@@ -15,7 +15,7 @@
|
||||
"/build/app-rtl.97153087.js"
|
||||
],
|
||||
"css": [
|
||||
"/build/app-rtl.d38ad12c.css"
|
||||
"/build/app-rtl.6a14a027.css"
|
||||
]
|
||||
},
|
||||
"export-pdf": {
|
||||
@@ -72,10 +72,10 @@
|
||||
},
|
||||
"integrity": {
|
||||
"/build/runtime.74179306.js": "sha384-OC1hTNUXUalKJcvmzrZ0TMCOIwnhxCxgG9dQkbcwR7WcBBCkl2H8bs3giiT2pAwG",
|
||||
"/build/app.6cdb49cb.js": "sha384-psXCV8SG68O80ZJHHeSbCJPQ8RoepaMeI1pEypTHu0Q8ySLrhlclhKHIL2dZ0ZQJ",
|
||||
"/build/app.1ed18844.css": "sha384-fEjS/tjc8m7jrw28/eWZFOTO6bugR9ILfksKvBrF46wtmqlage1s/P4+ZeYuqgpq",
|
||||
"/build/app.06423747.js": "sha384-9bVxz1eOxdhs+XNwJ6Ug4/crO0CrQlWs1FDYQ1xF14LpFIZjuVO65YohHT3KWreU",
|
||||
"/build/app.6bd98062.css": "sha384-BB9TrvQK/jlfhbdISEdBH5ZaZvbrPnZ+AcocXs1uQNh2yIQFboslUY9Up3iqnY4h",
|
||||
"/build/app-rtl.97153087.js": "sha384-jX7jRUAa8rH29Eg8jLIUKGfGcOT6RBz/P90plXmZPadf2CXKUBdcNGrspaejCHkr",
|
||||
"/build/app-rtl.d38ad12c.css": "sha384-eFvMRueGVAxDxpr3s4y1EhHnVb8FQOr+KQgOuTzbW11ILydpLIvObiiuqNpf4mWO",
|
||||
"/build/app-rtl.6a14a027.css": "sha384-ZmUAtO0Z/WfjYxJNleP3rBJpDmAnbaTlFMtejV/YpsshxkqmyNUneCIos9uMX7kH",
|
||||
"/build/export-pdf.1442bee7.js": "sha384-C6agvjJnQUsMCxaZ/J7dUZU3cpC7uHKI9ZtnfGpRqYbjyw91YLy1oV3iNYgwYyCF",
|
||||
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
|
||||
"/build/invoice.7ef8a0c8.js": "sha384-z4lZ1Ig3+NPigrRyGPZoff0gG3n5PnCjaDJ73ATnzdUYk0lOCEtNV9fg35VRD0vG",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"build/app.css": "/build/app.1ed18844.css",
|
||||
"build/app.js": "/build/app.6cdb49cb.js",
|
||||
"build/app-rtl.css": "/build/app-rtl.d38ad12c.css",
|
||||
"build/app.css": "/build/app.6bd98062.css",
|
||||
"build/app.js": "/build/app.06423747.js",
|
||||
"build/app-rtl.css": "/build/app-rtl.6a14a027.css",
|
||||
"build/app-rtl.js": "/build/app-rtl.97153087.js",
|
||||
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
|
||||
"build/export-pdf.js": "/build/export-pdf.1442bee7.js",
|
||||
|
||||
@@ -4,9 +4,6 @@ use App\Kernel;
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
// TODO remove once PARTIAL usage was replaced entirely
|
||||
\Doctrine\Deprecations\Deprecation::ignoreDeprecations('https://github.com/doctrine/orm/issues/8471');
|
||||
|
||||
return function (array $context) {
|
||||
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ use App\Entity\Timesheet;
|
||||
use App\Event\PageActionsEvent;
|
||||
use FOS\RestBundle\View\View;
|
||||
use FOS\RestBundle\View\ViewHandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\API\Model\TimesheetConfig;
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use FOS\RestBundle\View\View;
|
||||
use FOS\RestBundle\View\ViewHandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\API\Model\Version;
|
||||
use App\Plugin\PluginManager;
|
||||
use FOS\RestBundle\View\View;
|
||||
use FOS\RestBundle\View\ViewHandlerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
@@ -15,11 +15,11 @@ use App\Plugin\Plugin;
|
||||
use App\Plugin\PluginManager;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Process\PhpSubprocess;
|
||||
|
||||
#[AsCommand(name: 'kimai:plugins', description: 'Manage Kimai plugins')]
|
||||
final class PluginCommand extends Command
|
||||
@@ -69,14 +69,28 @@ final class PluginCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
$command = $this->getApplication()?->find('doctrine:migrations:migrate');
|
||||
if ($command === null) {
|
||||
throw new \RuntimeException('Failed finding doctrine migrations command');
|
||||
}
|
||||
$cmdInput = new ArrayInput(['--allow-no-migration' => true, '--configuration' => $config]);
|
||||
$cmdInput->setInteractive(false);
|
||||
if (0 !== $command->run($cmdInput, $output)) {
|
||||
$io->error('Failed to install bundle database: ' . $config);
|
||||
// using getApplication()->find('doctrine:migrations:migrate') does NOT work here
|
||||
// because the Doctrine command can only be executed once
|
||||
// if run more than once it fails with a "Container is frozen" exception
|
||||
|
||||
$process = new PhpSubprocess([
|
||||
'bin/console',
|
||||
'doctrine:migrations:migrate',
|
||||
'--allow-no-migration',
|
||||
'--no-interaction',
|
||||
'--configuration=' . $config
|
||||
]);
|
||||
$process->run();
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
$io->error('Failed to install bundle database: ' . PHP_EOL . $config);
|
||||
$io->error($process->getErrorOutput());
|
||||
} else {
|
||||
if ($io->isVerbose()) {
|
||||
$io->write($process->getOutput());
|
||||
} else {
|
||||
$io->success('Successfully installed: ' . $plugin->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,15 @@ final class RegenerateLocalesCommand extends Command
|
||||
$shortTime = new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, \IntlDateFormatter::SHORT);
|
||||
|
||||
$settings['date'] = $shortDate->getPattern();
|
||||
if ($settings['date'] === false) {
|
||||
$io->error('Invalid date pattern for locale: ' . $locale);
|
||||
continue;
|
||||
}
|
||||
$settings['time'] = $shortTime->getPattern();
|
||||
if ($settings['time'] === false) {
|
||||
$io->error('Invalid time pattern for locale: ' . $locale);
|
||||
continue;
|
||||
}
|
||||
|
||||
// see https://github.com/kimai/kimai/issues/4402 - Korean time format failed parsing
|
||||
// special case when time pattern starts with A / a => this will lead to an error
|
||||
|
||||
@@ -48,7 +48,9 @@ final class TranslationCommand extends Command
|
||||
->addOption('fill-empty', null, InputOption::VALUE_NONE, 'Pre-fills empty translations with the english version')
|
||||
->addOption('delete-empty', null, InputOption::VALUE_NONE, 'Delete all empty keys and files which have no translated key at all')
|
||||
->addOption('move-resname', null, InputOption::VALUE_REQUIRED, 'Move a resname from one file to another (needs "source" and "target" options)')
|
||||
->addOption('move-all', null, InputOption::VALUE_NONE, 'Move all keys from one file to another (needs "source" and "target" options)')
|
||||
->addOption('source', null, InputOption::VALUE_REQUIRED, 'Single source file to use')
|
||||
->addOption('only-core', null, InputOption::VALUE_NONE, 'Do not include plugin and theme directories')
|
||||
->addOption('target', null, InputOption::VALUE_REQUIRED, 'Single target file to use')
|
||||
// DEEPL TRANSLATION FEATURE - UNTESTED
|
||||
->addOption('translate-locale', null, InputOption::VALUE_REQUIRED, 'Translate into the given locale with Deepl')
|
||||
@@ -68,10 +70,13 @@ final class TranslationCommand extends Command
|
||||
|
||||
$bases = [
|
||||
'core' => $this->projectDirectory . '/translations/*.xlf',
|
||||
'plugins' => $this->projectDirectory . Kernel::PLUGIN_DIRECTORY . '/*/Resources/translations/*.xlf',
|
||||
'theme' => $this->projectDirectory . '/vendor/kevinpapst/tabler-bundle/translations/*.xlf',
|
||||
];
|
||||
|
||||
if (!$input->getOption('only-core')) {
|
||||
$bases['plugins'] = $this->projectDirectory . Kernel::PLUGIN_DIRECTORY . '/*/Resources/translations/*.xlf';
|
||||
$bases['theme'] = $this->projectDirectory . '/vendor/kevinpapst/tabler-bundle/translations/*.xlf';
|
||||
}
|
||||
|
||||
$sources = [];
|
||||
if ($input->getOption('source') !== null) {
|
||||
/** @var string $tmp */
|
||||
@@ -146,6 +151,19 @@ final class TranslationCommand extends Command
|
||||
return $this->moveResname($io, $moveResname, $sources, $targets);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Move all keys from source to target
|
||||
// ==========================================================================
|
||||
if ($input->getOption('move-all')) {
|
||||
if (\count($sources) === 0 || \count($targets) === 0) {
|
||||
$io->error('Moving all keys only works with one source and one target file');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return $this->moveAll($io, $sources, $targets);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Fill empty translations with english version
|
||||
// ==========================================================================
|
||||
@@ -634,4 +652,66 @@ final class TranslationCommand extends Command
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $sources
|
||||
* @param array<string> $targets
|
||||
*/
|
||||
private function moveAll(SymfonyStyle $io, array $sources, array $targets): int
|
||||
{
|
||||
foreach ($sources as $source) {
|
||||
$tmp = basename($source);
|
||||
$pos = strpos($tmp, '.');
|
||||
if ($pos === false) {
|
||||
$io->error('Unexpected filename: ' . $source);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$suffix = substr($tmp, $pos);
|
||||
$target = null;
|
||||
foreach ($targets as $t) {
|
||||
if (str_ends_with($t, $suffix)) {
|
||||
$target = $t;
|
||||
}
|
||||
}
|
||||
|
||||
if ($target === null) {
|
||||
$io->error('Cannot find translation target file for source: ' . $source);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$sourceDocument = new \DOMDocument('1.0');
|
||||
$sourceDocument->load($source);
|
||||
|
||||
$targetDocument = new \DOMDocument('1.0');
|
||||
$targetDocument->load($target);
|
||||
$removeNodes = [];
|
||||
|
||||
/** @var \DOMElement $element */
|
||||
foreach ($sourceDocument->getElementsByTagName('trans-unit') as $element) {
|
||||
$newNode = $targetDocument->importNode($element, true);
|
||||
$targetDocument->documentElement->firstElementChild->firstElementChild->appendChild($newNode); // @phpstan-ignore-line
|
||||
$removeNodes[] = $element;
|
||||
}
|
||||
|
||||
foreach ($removeNodes as $node) {
|
||||
$sourceDocument->documentElement->firstElementChild->firstElementChild->removeChild($node); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
$xmlDocument = new \DOMDocument('1.0');
|
||||
$xmlDocument->preserveWhiteSpace = false;
|
||||
$xmlDocument->formatOutput = true;
|
||||
$xmlDocument->loadXML($sourceDocument->saveXML()); // @phpstan-ignore-line
|
||||
file_put_contents($source, $xmlDocument->saveXML());
|
||||
|
||||
$xmlDocument = new \DOMDocument('1.0');
|
||||
$xmlDocument->preserveWhiteSpace = false;
|
||||
$xmlDocument->formatOutput = true;
|
||||
$xmlDocument->loadXML($targetDocument->saveXML()); // @phpstan-ignore-line
|
||||
file_put_contents($target, $xmlDocument->saveXML());
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.24.0';
|
||||
public const VERSION = '2.25.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 22400;
|
||||
public const VERSION_ID = 22500;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -111,6 +111,7 @@ final class TimesheetTeamController extends TimesheetAbstractController
|
||||
$tags[] = $tag;
|
||||
}
|
||||
|
||||
$newTimesheets = [];
|
||||
foreach ($allUsers as $user) {
|
||||
$newTimesheet = $entry->createCopy();
|
||||
$newTimesheet->setUser($user);
|
||||
@@ -118,6 +119,11 @@ final class TimesheetTeamController extends TimesheetAbstractController
|
||||
$newTimesheet->addTag($tag);
|
||||
}
|
||||
$this->service->prepareNewTimesheet($newTimesheet, $request);
|
||||
$this->service->validateTimesheet($newTimesheet);
|
||||
$newTimesheets[] = $newTimesheet;
|
||||
}
|
||||
|
||||
foreach ($newTimesheets as $newTimesheet) {
|
||||
$this->service->saveNewTimesheet($newTimesheet);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ final class UTCDateTimeImmutableType extends DateTimeImmutableType
|
||||
/**
|
||||
* @param T $value
|
||||
* @return (T is null ? null : string)
|
||||
* @template T<\DateTimeImmutable>
|
||||
* @template T
|
||||
* @throws ConversionException
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
|
||||
|
||||
@@ -25,7 +25,7 @@ final class UTCDateTimeType extends DateTimeType
|
||||
* @param T $value
|
||||
* @param AbstractPlatform $platform
|
||||
* @return (T is null ? null : string)
|
||||
* @template T<\DateTime>
|
||||
* @template T
|
||||
* @throws ConversionException
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
|
||||
|
||||
@@ -109,16 +109,14 @@ trait MetaTableTypeTrait
|
||||
|
||||
// unchecked checkboxes / false bool would save an empty string in the database
|
||||
// those cannot be searched in the database
|
||||
if (null !== $value) {
|
||||
switch ($this->type) {
|
||||
case YesNoType::class:
|
||||
case CheckboxType::class:
|
||||
if (!\is_int($value) && !\is_bool($value) && !\is_string($value)) {
|
||||
throw new \InvalidArgumentException('Failed converting meta-field bool value');
|
||||
} else {
|
||||
$value = (string) $value;
|
||||
}
|
||||
}
|
||||
switch ($this->type) {
|
||||
case YesNoType::class:
|
||||
case CheckboxType::class:
|
||||
if ($value === false || $value === '' || !\is_scalar($value)) {
|
||||
$value = 0;
|
||||
} else {
|
||||
$value = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
|
||||
@@ -178,13 +178,13 @@ class Team
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User[]
|
||||
* @return list<User>
|
||||
*/
|
||||
public function getTeamleads(): array
|
||||
{
|
||||
$leads = [];
|
||||
foreach ($this->members as $member) {
|
||||
if ($member->isTeamlead()) {
|
||||
if ($member->isTeamlead() && $member->getUser() !== null) {
|
||||
$leads[] = $member->getUser();
|
||||
}
|
||||
}
|
||||
@@ -273,13 +273,15 @@ class Team
|
||||
/**
|
||||
* Returns all users in the team, both teamlead and normal member.
|
||||
*
|
||||
* @return User[]
|
||||
* @return list<User>
|
||||
*/
|
||||
public function getUsers(): array
|
||||
{
|
||||
$users = [];
|
||||
foreach ($this->members as $member) {
|
||||
$users[] = $member->getUser();
|
||||
if ($member->getUser() !== null) {
|
||||
$users[] = $member->getUser();
|
||||
}
|
||||
}
|
||||
|
||||
return $users;
|
||||
|
||||
@@ -624,11 +624,9 @@ class Timesheet implements EntityWithMetaFields, ExportableItem, ModifiedAt
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createCopy(?Timesheet $timesheet = null): Timesheet
|
||||
public function createCopy(): Timesheet
|
||||
{
|
||||
if (null === $timesheet) {
|
||||
$timesheet = new Timesheet();
|
||||
}
|
||||
$timesheet = new Timesheet();
|
||||
|
||||
$values = get_object_vars($this);
|
||||
foreach ($values as $k => $v) {
|
||||
@@ -639,7 +637,9 @@ class Timesheet implements EntityWithMetaFields, ExportableItem, ModifiedAt
|
||||
|
||||
/** @var TimesheetMeta $meta */
|
||||
foreach ($this->meta as $meta) {
|
||||
$timesheet->setMetaField(clone $meta);
|
||||
$tmp = clone $meta;
|
||||
$tmp->setEntity($timesheet);
|
||||
$timesheet->setMetaField($tmp);
|
||||
}
|
||||
|
||||
$timesheet->tags = new ArrayCollection();
|
||||
|
||||
@@ -752,9 +752,6 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
|
||||
* This method should not be called by plugins and returns true on success or false on a failure.
|
||||
*
|
||||
* @internal immutable property that cannot be set by plugins
|
||||
* @param bool $canSeeAllData
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function initCanSeeAllData(bool $canSeeAllData): bool
|
||||
{
|
||||
|
||||
@@ -150,11 +150,26 @@ class UserPreference
|
||||
* integer, float, string, boolean or null
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return UserPreference
|
||||
*/
|
||||
public function setValue($value): UserPreference
|
||||
{
|
||||
$this->value = $value;
|
||||
// unchecked checkboxes / false bool would save an empty string in the database
|
||||
// those cannot be searched in the database
|
||||
switch ($this->type) {
|
||||
case YesNoType::class:
|
||||
case CheckboxType::class:
|
||||
if ($value === false || $value === '' || !\is_scalar($value)) {
|
||||
$value = 0;
|
||||
} else {
|
||||
$value = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$this->value = $value;
|
||||
} elseif (\is_scalar($value)) {
|
||||
$this->value = (string) $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ class PageActionsEvent extends ThemeEvent
|
||||
|
||||
public function addQuickImport(string $url): void
|
||||
{
|
||||
$this->addAction('import', ['url' => $url, 'class' => 'toolbar-action', 'title' => 'import', 'icon' => 'upload', 'translation_domain' => 'actions']);
|
||||
$this->addAction('import', ['url' => $url, 'class' => 'toolbar-action', 'title' => 'import', 'icon' => 'upload']);
|
||||
}
|
||||
|
||||
public function addQuickExport(string $url): void
|
||||
@@ -172,7 +172,7 @@ class PageActionsEvent extends ThemeEvent
|
||||
|
||||
public function addEdit(string $url, bool $modal = true, string $class = ''): void
|
||||
{
|
||||
$this->addAction('edit', ['url' => $url, 'class' => ($modal ? 'modal-ajax-form' . ($class === '' ? '' : ' ' . $class) : $class), 'translation_domain' => 'actions', 'title' => 'edit']);
|
||||
$this->addAction('edit', ['url' => $url, 'class' => ($modal ? 'modal-ajax-form' . ($class === '' ? '' : ' ' . $class) : $class), 'title' => 'edit']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,20 +180,20 @@ class PageActionsEvent extends ThemeEvent
|
||||
*/
|
||||
public function addSettings(string $url): void
|
||||
{
|
||||
$this->addAction('settings', ['url' => $url, 'class' => 'modal-ajax-form', 'title' => 'settings', 'translation_domain' => 'actions', 'accesskey' => 'h']);
|
||||
$this->addAction('settings', ['url' => $url, 'class' => 'modal-ajax-form', 'title' => 'settings', 'accesskey' => 'h']);
|
||||
}
|
||||
|
||||
public function addConfig(string $url): void
|
||||
{
|
||||
$this->addAction('settings', ['url' => $url, 'title' => 'settings', 'translation_domain' => 'actions']);
|
||||
$this->addAction('settings', ['url' => $url, 'title' => 'settings']);
|
||||
}
|
||||
|
||||
public function addDelete(string $url, bool $remoteConfirm = true): void
|
||||
{
|
||||
if ($remoteConfirm) {
|
||||
$this->addAction('trash', ['url' => $url, 'class' => 'modal-ajax-form text-red', 'translation_domain' => 'actions', 'title' => 'trash']);
|
||||
$this->addAction('trash', ['url' => $url, 'class' => 'modal-ajax-form text-red', 'title' => 'trash']);
|
||||
} else {
|
||||
$this->addAction('trash', ['url' => $url, 'class' => 'confirmation-link text-red', 'attr' => ['data-question' => 'confirm.delete'], 'translation_domain' => 'actions', 'title' => 'trash']);
|
||||
$this->addAction('trash', ['url' => $url, 'class' => 'confirmation-link text-red', 'attr' => ['data-question' => 'confirm.delete'], 'title' => 'trash']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ abstract class AbstractActionsSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
private ?string $locale = null;
|
||||
|
||||
public function __construct(private AuthorizationCheckerInterface $auth, private UrlGeneratorInterface $urlGenerator)
|
||||
public function __construct(private readonly AuthorizationCheckerInterface $auth, private readonly UrlGeneratorInterface $urlGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ abstract class AbstractTimesheetSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if (!$timesheet->isRunning() && $this->isGranted('start', $timesheet)) {
|
||||
$event->addAction('repeat', ['title' => 'repeat', 'translation_domain' => 'actions', 'url' => $this->path('restart_timesheet', ['id' => $timesheet->getId()]), 'class' => 'api-link dd-ts-repeat', 'attr' => ['data-payload' => '{"copy": "all"}', 'data-event' => 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method' => 'PATCH', 'data-msg-error' => 'timesheet.start.error', 'data-msg-success' => 'timesheet.start.success']]);
|
||||
$event->addAction('repeat', ['title' => 'repeat', 'url' => $this->path('restart_timesheet', ['id' => $timesheet->getId()]), 'class' => 'api-link dd-ts-repeat', 'attr' => ['data-payload' => '{"copy": "all"}', 'data-event' => 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method' => 'PATCH', 'data-msg-error' => 'timesheet.start.error', 'data-msg-success' => 'timesheet.start.success']]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('edit', $timesheet)) {
|
||||
@@ -38,7 +38,7 @@ abstract class AbstractTimesheetSubscriber extends AbstractActionsSubscriber
|
||||
|
||||
if ($this->isGranted('duplicate', $timesheet)) {
|
||||
$class = $event->isView('edit') ? '' : 'modal-ajax-form';
|
||||
$event->addAction('copy', ['title' => 'copy', 'translation_domain' => 'actions', 'url' => $this->path($routeDuplicate, ['id' => $timesheet->getId()]), 'class' => $class . ' dd-ts-duplicate']);
|
||||
$event->addAction('copy', ['title' => 'copy', 'url' => $this->path($routeDuplicate, ['id' => $timesheet->getId()]), 'class' => $class . ' dd-ts-duplicate']);
|
||||
}
|
||||
|
||||
if ($event->countActions() > 0) {
|
||||
@@ -49,7 +49,6 @@ abstract class AbstractTimesheetSubscriber extends AbstractActionsSubscriber
|
||||
$event->addAction('trash', [
|
||||
'url' => $this->path('delete_timesheet', ['id' => $timesheet->getId()]),
|
||||
'class' => 'api-link text-red dd-ts-trash',
|
||||
'translation_domain' => 'actions',
|
||||
'attr' => [
|
||||
'data-event' => 'kimai.timesheetDelete',
|
||||
'data-method' => 'DELETE',
|
||||
|
||||
@@ -31,7 +31,7 @@ final class ActivitySubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if (!$event->isView('activity_details') && $this->isGranted('view', $activity)) {
|
||||
$event->addAction('details', ['title' => 'details', 'translation_domain' => 'actions', 'url' => $this->path('activity_details', ['id' => $activity->getId()])]);
|
||||
$event->addAction('details', ['title' => 'details', 'url' => $this->path('activity_details', ['id' => $activity->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('edit', $activity)) {
|
||||
@@ -40,7 +40,7 @@ final class ActivitySubscriber extends AbstractActionsSubscriber
|
||||
|
||||
if ($this->isGranted('permissions', $activity)) {
|
||||
$class = $event->isView('permissions') ? '' : 'modal-ajax-form';
|
||||
$event->addAction('permissions', ['title' => 'permissions', 'translation_domain' => 'actions', 'url' => $this->path('admin_activity_permissions', ['id' => $activity->getId()]), 'class' => $class]);
|
||||
$event->addAction('permissions', ['title' => 'permissions', 'url' => $this->path('admin_activity_permissions', ['id' => $activity->getId()]), 'class' => $class]);
|
||||
}
|
||||
|
||||
if ($event->countActions() > 0) {
|
||||
@@ -53,7 +53,7 @@ final class ActivitySubscriber extends AbstractActionsSubscriber
|
||||
$parameters['customers[]'] = $activity->getProject()->getCustomer()->getId();
|
||||
$parameters['projects[]'] = $activity->getProject()->getId();
|
||||
}
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet.filter', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet', $parameters)]);
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet.filter', 'url' => $this->path('admin_timesheet', $parameters)]);
|
||||
}
|
||||
|
||||
if ($event->hasSubmenu('filter')) {
|
||||
@@ -65,7 +65,7 @@ final class ActivitySubscriber extends AbstractActionsSubscriber
|
||||
if (!$activity->isGlobal()) {
|
||||
$parameters['project'] = $activity->getProject()->getId();
|
||||
}
|
||||
$event->addAction('create-timesheet', ['title' => 'create-timesheet', 'translation_domain' => 'actions', 'icon' => 'start', 'url' => $this->path('admin_timesheet_create', $parameters), 'class' => 'modal-ajax-form']);
|
||||
$event->addAction('create-timesheet', ['title' => 'create-timesheet', 'icon' => 'start', 'url' => $this->path('admin_timesheet_create', $parameters), 'class' => 'modal-ajax-form']);
|
||||
}
|
||||
|
||||
if (($event->isIndexView() || $event->isView('project_details')) && $this->isGranted('delete', $activity)) {
|
||||
|
||||
@@ -34,7 +34,7 @@ final class CustomerSubscriber extends AbstractActionsSubscriber
|
||||
$isListingView = $event->isIndexView() || $event->isCustomView();
|
||||
|
||||
if (!$event->isView('customer_details') && $canView) {
|
||||
$event->addAction('details', ['title' => 'details', 'translation_domain' => 'actions', 'url' => $this->path('customer_details', ['id' => $customer->getId()])]);
|
||||
$event->addAction('details', ['title' => 'details', 'url' => $this->path('customer_details', ['id' => $customer->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('edit', $customer)) {
|
||||
@@ -43,7 +43,7 @@ final class CustomerSubscriber extends AbstractActionsSubscriber
|
||||
|
||||
if ($this->isGranted('permissions', $customer)) {
|
||||
$class = $event->isView('permissions') ? '' : 'modal-ajax-form';
|
||||
$event->addAction('permissions', ['title' => 'permissions', 'translation_domain' => 'actions', 'url' => $this->path('admin_customer_permissions', ['id' => $customer->getId()]), 'class' => $class]);
|
||||
$event->addAction('permissions', ['title' => 'permissions', 'url' => $this->path('admin_customer_permissions', ['id' => $customer->getId()]), 'class' => $class]);
|
||||
}
|
||||
|
||||
if ($isListingView) {
|
||||
@@ -61,15 +61,15 @@ final class CustomerSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_project') || $this->isGranted('view_teamlead_project') || $this->isGranted('view_team_project')) {
|
||||
$event->addActionToSubmenu('filter', 'project', ['title' => 'project.filter', 'translation_domain' => 'actions', 'url' => $this->path('admin_project', ['customers[]' => $customer->getId()])]);
|
||||
$event->addActionToSubmenu('filter', 'project', ['title' => 'project.filter', 'url' => $this->path('admin_project', ['customers[]' => $customer->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_activity')) {
|
||||
$event->addActionToSubmenu('filter', 'activity', ['title' => 'activity.filter', 'translation_domain' => 'actions', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId()])]);
|
||||
$event->addActionToSubmenu('filter', 'activity', ['title' => 'activity.filter', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_other_timesheet')) {
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet.filter', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet', ['customers[]' => $customer->getId()])]);
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet.filter', 'url' => $this->path('admin_timesheet', ['customers[]' => $customer->getId()])]);
|
||||
}
|
||||
|
||||
if ($event->hasSubmenu('filter')) {
|
||||
|
||||
@@ -56,7 +56,7 @@ final class InvoiceSubscriber extends AbstractActionsSubscriber
|
||||
$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', 'token' => $payload['token']]), 'title' => 'invoice.cancel', 'translation_domain' => 'actions']);
|
||||
$event->addAction($id, ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'canceled', 'token' => $payload['token']]), 'title' => 'invoice.cancel']);
|
||||
}
|
||||
|
||||
if ($this->isGranted('delete_invoice')) {
|
||||
|
||||
@@ -34,7 +34,7 @@ final class ProjectSubscriber extends AbstractActionsSubscriber
|
||||
$isListingView = $event->isIndexView() || $event->isCustomView();
|
||||
|
||||
if (!$event->isView('project_details') && $this->isGranted('view', $project)) {
|
||||
$event->addAction('details', ['title' => 'details', 'translation_domain' => 'actions', 'url' => $this->path('project_details', ['id' => $project->getId()])]);
|
||||
$event->addAction('details', ['title' => 'details', 'url' => $this->path('project_details', ['id' => $project->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('edit', $project)) {
|
||||
@@ -43,7 +43,7 @@ final class ProjectSubscriber extends AbstractActionsSubscriber
|
||||
|
||||
if ($this->isGranted('permissions', $project)) {
|
||||
$class = $event->isView('permissions') ? '' : 'modal-ajax-form';
|
||||
$event->addAction('permissions', ['title' => 'permissions', 'translation_domain' => 'actions', 'url' => $this->path('admin_project_permissions', ['id' => $project->getId()]), 'class' => $class]);
|
||||
$event->addAction('permissions', ['title' => 'permissions', 'url' => $this->path('admin_project_permissions', ['id' => $project->getId()]), 'class' => $class]);
|
||||
}
|
||||
|
||||
if ($event->countActions() > 0) {
|
||||
@@ -51,11 +51,11 @@ final class ProjectSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_activity')) {
|
||||
$event->addActionToSubmenu('filter', 'activity', ['title' => 'activity.filter', 'translation_domain' => 'actions', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId(), 'projects[]' => $project->getId()])]);
|
||||
$event->addActionToSubmenu('filter', 'activity', ['title' => 'activity.filter', 'url' => $this->path('admin_activity', ['customers[]' => $customer->getId(), 'projects[]' => $project->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_other_timesheet')) {
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet.filter', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet', ['customers[]' => $customer->getId(), 'projects[]' => $project->getId()])]);
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet.filter', 'url' => $this->path('admin_timesheet', ['customers[]' => $customer->getId(), 'projects[]' => $project->getId()])]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('create_export')) {
|
||||
@@ -79,7 +79,7 @@ final class ProjectSubscriber extends AbstractActionsSubscriber
|
||||
if (\array_key_exists('token', $payload) && $this->isGranted('edit', $project) && $this->isGranted('create_project')) {
|
||||
$event->addAction(
|
||||
'copy',
|
||||
['title' => 'copy', 'translation_domain' => 'actions', 'url' => $this->path('admin_project_duplicate', ['id' => $project->getId(), 'token' => $payload['token']])]
|
||||
['title' => 'copy', 'url' => $this->path('admin_project_duplicate', ['id' => $project->getId(), 'token' => $payload['token']])]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,14 +46,13 @@ final class TagSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_other_timesheet')) {
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet.filter', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet', ['tags' => $name])]);
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['title' => 'timesheet.filter', 'url' => $this->path('admin_timesheet', ['tags' => $name])]);
|
||||
}
|
||||
|
||||
if ($event->isIndexView() && $this->isGranted('delete_tag')) {
|
||||
$event->addAction('trash', [
|
||||
'url' => $this->path('delete_tag', ['id' => $id]),
|
||||
'class' => 'api-link text-red',
|
||||
'translation_domain' => 'actions',
|
||||
'attr' => [
|
||||
'data-event' => 'kimai.tagDelete kimai.tagUpdate',
|
||||
'data-method' => 'DELETE',
|
||||
|
||||
@@ -36,7 +36,7 @@ final class TeamSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($this->isGranted('create_team')) {
|
||||
$event->addAction('copy', ['url' => $this->path('team_duplicate', ['id' => $team->getId()]), 'title' => 'copy', 'translation_domain' => 'actions', 'class' => 'modal-ajax-form']);
|
||||
$event->addAction('copy', ['url' => $this->path('team_duplicate', ['id' => $team->getId()]), 'title' => 'copy', 'class' => 'modal-ajax-form']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ final class TeamSubscriber extends AbstractActionsSubscriber
|
||||
$event->addAction('trash', [
|
||||
'url' => $this->path('delete_team', ['id' => $team->getId()]),
|
||||
'class' => 'api-link text-red',
|
||||
'translation_domain' => 'actions',
|
||||
'attr' => [
|
||||
'data-event' => 'kimai.teamDelete kimai.teamUpdate',
|
||||
'data-method' => 'DELETE',
|
||||
|
||||
@@ -22,7 +22,7 @@ final class TimesheetsTeamSubscriber extends AbstractActionsSubscriber
|
||||
{
|
||||
if ($this->isGranted('create_other_timesheet')) {
|
||||
$event->addAction('create', ['title' => 'create', 'url' => $this->path('admin_timesheet_create'), 'class' => 'create-ts modal-ajax-form']);
|
||||
$event->addAction('multi-user', ['title' => 'create-timesheet-multiuser', 'translation_domain' => 'actions', 'url' => $this->path('admin_timesheet_create_multiuser'), 'class' => 'create-ts-mu modal-ajax-form', 'icon' => 'fas fa-user-plus']);
|
||||
$event->addAction('multi-user', ['title' => 'create-timesheet-multiuser', 'url' => $this->path('admin_timesheet_create_multiuser'), 'class' => 'create-ts-mu modal-ajax-form', 'icon' => 'fas fa-user-plus']);
|
||||
}
|
||||
|
||||
if ($this->isGranted('export_other_timesheet')) {
|
||||
|
||||
@@ -31,7 +31,7 @@ final class UserFormsSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($this->isGranted('edit', $user)) {
|
||||
$event->addAction('edit', ['url' => $this->path('user_profile_edit', ['username' => $user->getUserIdentifier()]), 'title' => 'profile-stats', 'translation_domain' => 'actions']);
|
||||
$event->addAction('edit', ['url' => $this->path('user_profile_edit', ['username' => $user->getUserIdentifier()]), 'title' => 'profile-stats']);
|
||||
}
|
||||
if ($this->isGranted('preferences', $user)) {
|
||||
$event->addConfig($this->path('user_profile_preferences', ['username' => $user->getUserIdentifier()]));
|
||||
|
||||
@@ -40,7 +40,7 @@ final class UserSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($this->isGranted('view', $user)) {
|
||||
$event->addAction('profile-stats', ['icon' => 'avatar', 'url' => $this->path('user_profile', ['username' => $user->getUserIdentifier()]), 'translation_domain' => 'actions', 'title' => 'profile-stats']);
|
||||
$event->addAction('profile-stats', ['icon' => 'avatar', 'url' => $this->path('user_profile', ['username' => $user->getUserIdentifier()]), 'title' => 'profile-stats']);
|
||||
$event->addDivider();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ final class UserSubscriber extends AbstractActionsSubscriber
|
||||
}
|
||||
|
||||
if ($user->isEnabled() && $this->isGranted('view_other_timesheet')) {
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['url' => $this->path('admin_timesheet', ['users[]' => $user->getId()]), 'title' => 'timesheet.filter', 'translation_domain' => 'actions']);
|
||||
$event->addActionToSubmenu('filter', 'timesheet', ['url' => $this->path('admin_timesheet', ['users[]' => $user->getId()]), 'title' => 'timesheet.filter']);
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_team')) {
|
||||
|
||||
@@ -28,7 +28,7 @@ final class AnnotationExtractor implements ExtractorInterface
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return ColumnDefinition[]
|
||||
* @return list<ColumnDefinition>
|
||||
* @throws ExtractorException
|
||||
*/
|
||||
public function extract($value): array
|
||||
@@ -154,11 +154,7 @@ final class AnnotationExtractor implements ExtractorInterface
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($columns as $name => $definition) {
|
||||
if (null === $definition) {
|
||||
unset($columns[$name]);
|
||||
}
|
||||
}
|
||||
$columns = array_filter($columns, function ($value) { return $value !== null; });
|
||||
|
||||
return array_values($columns);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ final class MultiUpdateTable extends AbstractType
|
||||
|
||||
$builder->add('entities', HiddenType::class, [
|
||||
'required' => false,
|
||||
'attr' => ['class' => 'multi_update_ids']
|
||||
]);
|
||||
|
||||
$builder->get('entities')->addModelTransformer(
|
||||
|
||||
@@ -38,6 +38,8 @@ final class ActivityType extends AbstractType
|
||||
public function groupBy(Activity $activity, $key, $index): string
|
||||
{
|
||||
if (null === $activity->getProject()) {
|
||||
// this creates a optgroup with an empty title. previously this was null, which resulted in options without optgroup
|
||||
// and those are ordered by Tomselect at the top - so globals always came first, see #4674
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Configuration\MailConfiguration;
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Mailer\Envelope;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
@@ -31,7 +32,11 @@ final class KimaiMailer implements MailerInterface
|
||||
}
|
||||
|
||||
if (\count($message->getFrom()) === 0) {
|
||||
$message->from($this->configuration->getFromAddress());
|
||||
$fallback = $this->configuration->getFromAddress();
|
||||
if ($fallback === null) {
|
||||
throw new \RuntimeException('Missing email "from" address');
|
||||
}
|
||||
$message->from(new Address($fallback, 'Kimai'));
|
||||
}
|
||||
|
||||
$this->mailer->send($message);
|
||||
|
||||
@@ -17,7 +17,7 @@ use DateTimeInterface;
|
||||
final class MonthlyStatistic implements DateStatisticInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string|int, array<int<1, 12>, StatisticDate>>
|
||||
* @var array<string, array<int<1, 12>, StatisticDate>>
|
||||
*/
|
||||
private array $years = [];
|
||||
private DateTimeInterface $begin;
|
||||
@@ -50,7 +50,7 @@ final class MonthlyStatistic implements DateStatisticInterface
|
||||
$day = (int) $begin->format('d');
|
||||
|
||||
while ($tmp < $this->end) {
|
||||
$curYear = $tmp->format('Y');
|
||||
$curYear = (string) $tmp->format('Y');
|
||||
if (!isset($years[$curYear])) {
|
||||
$year = [];
|
||||
for ($i = 1; $i < 13; $i++) {
|
||||
@@ -67,7 +67,7 @@ final class MonthlyStatistic implements DateStatisticInterface
|
||||
}
|
||||
$tmp->modify('+1 month');
|
||||
}
|
||||
$this->years = $years;
|
||||
$this->years = $years; // @phpstan-ignore assign.propertyType
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Entity\Project;
|
||||
use App\Repository\ActivityRateRepository;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Repository\ProjectRateRepository;
|
||||
use App\Repository\Query\ActivityQuery;
|
||||
|
||||
final class ProjectDuplicationService
|
||||
{
|
||||
@@ -53,7 +54,11 @@ final class ProjectDuplicationService
|
||||
$this->projectRateRepository->saveRate($newRate);
|
||||
}
|
||||
|
||||
$allActivities = $this->activityRepository->findByProject($project);
|
||||
$query = new ActivityQuery();
|
||||
$query->addProject($project);
|
||||
$query->setExcludeGlobals(true);
|
||||
|
||||
$allActivities = $this->activityRepository->getActivitiesForQuery($query);
|
||||
foreach ($allActivities as $activity) {
|
||||
$newActivity = clone $activity;
|
||||
$newActivity->setProject($newProject);
|
||||
|
||||
@@ -37,18 +37,6 @@ class ActivityRepository extends EntityRepository
|
||||
{
|
||||
use RepositorySearchTrait;
|
||||
|
||||
/**
|
||||
* @param Project $project
|
||||
* @return array<Activity>
|
||||
*/
|
||||
public function findByProject(Project $project): array
|
||||
{
|
||||
$query = new ActivityQuery();
|
||||
$query->addProject($project);
|
||||
|
||||
return $this->getActivitiesForQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $activityIds
|
||||
* @return array<Activity>
|
||||
|
||||
@@ -64,7 +64,7 @@ trait RepositorySearchTrait
|
||||
$c = 0;
|
||||
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
|
||||
$and = $qb->expr()->andX();
|
||||
/** @var non-falsy-string&literal-string $alias */
|
||||
/** @var non-falsy-string&lowercase-string $alias */
|
||||
$alias = 'meta' . $a++;
|
||||
$paramName = 'metaName' . $i++;
|
||||
$paramValue = 'metaValue' . $c++;
|
||||
|
||||
@@ -108,8 +108,6 @@ final class TimesheetService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $timesheet
|
||||
* @return Timesheet
|
||||
* @throws ValidationFailedException for invalid timesheets or running timesheets that should be stopped
|
||||
* @throws InvalidArgumentException for already persisted timesheets
|
||||
* @throws AccessDeniedException if user is not allowed to start timesheet
|
||||
@@ -233,11 +231,10 @@ final class TimesheetService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $timesheet
|
||||
* @param string[] $groups
|
||||
* @throws ValidationFailedException
|
||||
*/
|
||||
private function validateTimesheet(Timesheet $timesheet, array $groups = []): void
|
||||
public function validateTimesheet(Timesheet $timesheet, array $groups = []): void
|
||||
{
|
||||
$errors = $this->validator->validate($timesheet, null, $groups);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ use App\Utils\LocaleFormatter;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
use Twig\DeprecatedCallableInfo;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
@@ -41,7 +42,7 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
|
||||
new TwigFilter('date_short', [$this, 'dateShort']),
|
||||
new TwigFilter('date_time', [$this, 'dateTime']),
|
||||
// cannot be deleted right now, needs to be kept for invoice and export templates
|
||||
new TwigFilter('date_full', [$this, 'dateTime'], ['deprecated' => true, 'alternative' => 'date_time']),
|
||||
new TwigFilter('date_full', [$this, 'dateTime'], ['deprecation_info' => new DeprecatedCallableInfo('Kimai', '2.0', 'date_time')]),
|
||||
new TwigFilter('date_format', [$this, 'dateFormat']),
|
||||
new TwigFilter('date_weekday', [$this, 'dateWeekday']),
|
||||
new TwigFilter('time', [$this, 'time']),
|
||||
|
||||
@@ -31,7 +31,7 @@ final class LocaleFormatter
|
||||
private ?NumberFormatter $moneyFormatter = null;
|
||||
private ?NumberFormatter $moneyFormatterNoCurrency = null;
|
||||
|
||||
public function __construct(private LocaleService $localeService, private string $locale)
|
||||
public function __construct(private readonly LocaleService $localeService, private readonly string $locale)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace App\Validator\Constraints;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Timesheet as TimesheetEntity;
|
||||
use App\Form\Model\MultiUserTimesheet;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
@@ -35,6 +36,10 @@ final class TimesheetOverlappingValidator extends ConstraintValidator
|
||||
throw new UnexpectedTypeException($value, TimesheetEntity::class);
|
||||
}
|
||||
|
||||
if ($value instanceof MultiUserTimesheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
$begin = $value->getBegin();
|
||||
$end = $value->getEnd();
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ final class WorkingTimeCalculatorDay implements WorkingTimeCalculator
|
||||
public function getWorkHoursForDay(\DateTimeInterface $dateTime): int
|
||||
{
|
||||
return (int) match ($dateTime->format('N')) {
|
||||
'1' => $this->user->getPreferenceValue(self::WORK_HOURS_MONDAY, 0),
|
||||
'2' => $this->user->getPreferenceValue(self::WORK_HOURS_TUESDAY, 0),
|
||||
'3' => $this->user->getPreferenceValue(self::WORK_HOURS_WEDNESDAY, 0),
|
||||
'4' => $this->user->getPreferenceValue(self::WORK_HOURS_THURSDAY, 0),
|
||||
'5' => $this->user->getPreferenceValue(self::WORK_HOURS_FRIDAY, 0),
|
||||
'6' => $this->user->getPreferenceValue(self::WORK_HOURS_SATURDAY, 0),
|
||||
'7' => $this->user->getPreferenceValue(self::WORK_HOURS_SUNDAY, 0),
|
||||
'1' => $this->user->getPreferenceValue(self::WORK_HOURS_MONDAY, 0, false),
|
||||
'2' => $this->user->getPreferenceValue(self::WORK_HOURS_TUESDAY, 0, false),
|
||||
'3' => $this->user->getPreferenceValue(self::WORK_HOURS_WEDNESDAY, 0, false),
|
||||
'4' => $this->user->getPreferenceValue(self::WORK_HOURS_THURSDAY, 0, false),
|
||||
'5' => $this->user->getPreferenceValue(self::WORK_HOURS_FRIDAY, 0, false),
|
||||
'6' => $this->user->getPreferenceValue(self::WORK_HOURS_SATURDAY, 0, false),
|
||||
'7' => $this->user->getPreferenceValue(self::WORK_HOURS_SUNDAY, 0, false),
|
||||
default => throw new \Exception('Unknown day: ' . $dateTime->format('Y-m-d'))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use App\Model\Month as BaseMonth;
|
||||
*/
|
||||
final class Month extends BaseMonth
|
||||
{
|
||||
public function __construct(\DateTimeImmutable $month, private User $user)
|
||||
public function __construct(\DateTimeImmutable $month, private readonly User $user)
|
||||
{
|
||||
parent::__construct($month);
|
||||
}
|
||||
|
||||
12
symfony.lock
12
symfony.lock
@@ -503,18 +503,6 @@
|
||||
"ref": "179470cb6492db92dffee208cfdb436f175c93b4"
|
||||
}
|
||||
},
|
||||
"symfony/polyfill-intl-grapheme": {
|
||||
"version": "v1.13.1"
|
||||
},
|
||||
"symfony/polyfill-intl-icu": {
|
||||
"version": "v1.6.0"
|
||||
},
|
||||
"symfony/polyfill-intl-idn": {
|
||||
"version": "v1.11.0"
|
||||
},
|
||||
"symfony/polyfill-intl-normalizer": {
|
||||
"version": "v1.13.1"
|
||||
},
|
||||
"symfony/process": {
|
||||
"version": "v4.0.3"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block main %}
|
||||
{% set formEditTemplate = kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||
{% set formOptions = {
|
||||
'title': (activity.id is null ? 'create-activity'|trans({}, 'actions') : 'edit'|trans({}, 'actions')),
|
||||
'title': (activity.id is null ? 'create-activity'|trans : 'edit'|trans),
|
||||
'form': form,
|
||||
'back': path('admin_activity')
|
||||
} %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block main %}
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ activity.name,
|
||||
'title': ('permissions'|trans) ~ ': ' ~ activity.name,
|
||||
'form': form,
|
||||
'back': path('activity_details', {'id': activity.id})
|
||||
}) }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block main %}
|
||||
{% set formEditTemplate = kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||
{% set formOptions = {
|
||||
'title': (customer.id is null ? 'create'|trans : 'edit'|trans({}, 'actions')),
|
||||
'title': (customer.id is null ? 'create'|trans : 'edit'|trans),
|
||||
'form': form,
|
||||
'back': path('admin_customer')
|
||||
} %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block main %}
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ customer.name,
|
||||
'title': ('permissions'|trans) ~ ': ' ~ customer.name,
|
||||
'form': form,
|
||||
'back': path('customer_details', {'id': customer.id})
|
||||
}) }}
|
||||
|
||||
@@ -49,9 +49,9 @@
|
||||
<h3 class="card-title">{{ widget.title|trans({}, widget.translationDomain) }}</h3>
|
||||
<div class="card-actions">
|
||||
{% if widget.hasForm() %}
|
||||
{{ card_tool_button('configuration', {'title': 'settings', 'translation_domain': 'actions', 'class': 'modal-ajax-form', 'url': path('tasks_create')}) }}
|
||||
{{ card_tool_button('configuration', {'title': 'settings', 'class': 'modal-ajax-form', 'url': path('tasks_create')}) }}
|
||||
{% endif %}
|
||||
{{ card_tool_button('delete', {'title': 'widget_remove', 'translation_domain': 'actions', 'url': '#', 'onclick': "removeWidget(this, '" ~ widget.id ~ "'); return false;"}) }}
|
||||
{{ card_tool_button('delete', {'title': 'widget_remove', 'url': '#', 'onclick': "removeWidget(this, '" ~ widget.id ~ "'); return false;"}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<input type="submit" data-loading-text="{{ (submit_button|default('action.save'))|trans }}…" value="{{ (submit_button|default('action.save'))|trans }}" class="btn btn-primary" />
|
||||
{% endblock %}
|
||||
{% if _back is not same as (false) %}
|
||||
<a href="{{ _back }}" class="btn btn-link">{{ 'back'|trans({}, 'actions') }}</a>
|
||||
<a href="{{ _back }}" class="btn btn-link">{{ 'back'|trans }}</a>
|
||||
{% endif %}
|
||||
{% if _reset is not same as (false) %}
|
||||
<input type="reset" value="{{ 'action.reset'|trans }}" class="btn btn-link pull-right" />
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{% block box_footer %}
|
||||
<input type="submit" value="{{ 'delete'|trans }}" class="btn btn-danger" />
|
||||
{% if back %}
|
||||
<a href="{{ back }}" class="btn btn-link">{{ 'back'|trans({}, 'actions') }}</a>
|
||||
<a href="{{ back }}" class="btn btn-link">{{ 'back'|trans }}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_after %}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{% endblock %}
|
||||
{% block box_attributes %}id="team_listing_box"{% endblock %}
|
||||
{% block box_title %}
|
||||
{{ 'permissions'|trans({}, 'actions') }}
|
||||
{{ 'permissions'|trans }}
|
||||
{% if teams|length > 0 %}<small class="text-body-secondary d-none d-sm-inline ms-1">{{ 'team.visibility_restricted'|trans({}, 'teams') }}</small>{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}{% if teams|length > 0 %}p-0{% endif %}{% endblock %}
|
||||
|
||||
@@ -286,7 +286,7 @@
|
||||
<form class="w-100 d-print-none row row-cards p-3">
|
||||
<div class="card mb-1 p-0">
|
||||
<div class="card-header">
|
||||
{{ 'settings'|trans({}, 'actions') }}
|
||||
{{ 'settings'|trans }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<label for="duration-decimal">
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
{% endblock %}
|
||||
{% block box_footer %}
|
||||
<button type="submit" class="btn btn-primary">{{ 'action.save'|trans }}</button>
|
||||
<a href="{{ path('admin_invoice_template') }}" class="btn btn-link">{{ 'back'|trans({}, 'actions') }}</a>
|
||||
<a href="{{ path('admin_invoice_template') }}" class="btn btn-link">{{ 'back'|trans }}</a>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% elseif upload_error is not null %}
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
|
||||
{% else %}
|
||||
{% if headerOptions.batchUpdate is defined %}
|
||||
<input type="checkbox" id="multi_update_all" class="multiupdater form-check-input m-0 align-middle" title="{{ 'batch_table_checkbox_all'|trans }}">
|
||||
<input type="checkbox" id="multi_update_all_{{ tableName }}" class="multi_update_all multiupdater form-check-input m-0 align-middle" title="{{ 'batch_table_checkbox_all'|trans }}">
|
||||
{% endif %}
|
||||
{{ headerTitle }}
|
||||
{% if headerOptions.html_after is defined %}
|
||||
@@ -250,7 +250,7 @@
|
||||
{% if (route is not empty and entries is not null) or multi_update_form is not null %}
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
{% if multi_update_form is not null %}
|
||||
{{ form_start(multi_update_form, {'attr': {'id': 'multi_update_form', 'style': 'display:none', 'data-question': 'update_multiple'|trans}}) }}
|
||||
{{ form_start(multi_update_form, {'attr': {'class': 'multi_update_form', 'style': 'display:none', 'data-question': 'update_multiple'|trans}}) }}
|
||||
{% for formChild in multi_update_form.children %}
|
||||
{{ form_widget(formChild) }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
{% if values.children is defined and values.children|length > 0 %}
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn {{ btnClasses }} dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ icon(icon, true) }}{% if large %} {{ values.title is defined ? values.title|trans : icon|trans({}, 'actions') }}{% endif %}
|
||||
{{ icon(icon, true) }}{% if large %} {{ values.title is defined ? values.title|trans : icon|trans }}{% endif %}
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
{%- for icon, values in values.children %}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block main %}
|
||||
{% set formEditTemplate = kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||
{% set formOptions = {
|
||||
'title': (project.id is null ? 'create-project'|trans({}, 'actions') : 'edit'|trans({}, 'actions')),
|
||||
'title': (project.id is null ? 'create-project'|trans : 'edit'|trans),
|
||||
'form': form,
|
||||
'back': path('admin_project')
|
||||
} %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block main %}
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ project.name,
|
||||
'title': ('permissions'|trans) ~ ': ' ~ project.name,
|
||||
'form': form,
|
||||
'back': path('project_details', {'id': project.id})
|
||||
}) }}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% block main %}
|
||||
{% set formEditTemplate = kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||
{% set formOptions = {
|
||||
'title': (tag.id is null ? 'create'|trans : 'edit'|trans({}, 'actions')),
|
||||
'title': (tag.id is null ? 'create'|trans : 'edit'|trans),
|
||||
'form': form,
|
||||
'back': path('tags')
|
||||
} %}
|
||||
|
||||
@@ -91,9 +91,9 @@
|
||||
{% block datatable_column %}
|
||||
<td class="{{ tables.class(dataTable, column) }}{% if column == 'description' %} timesheet-description{% endif %}">
|
||||
{% if column == 'id' %}
|
||||
{% if is_granted('edit', entry) or is_granted('delete', entry) %}
|
||||
{{ tables.datatable_multiupdate_row(entry.id) }}
|
||||
{% endif %}
|
||||
{% if is_granted('edit', entry) or is_granted('delete', entry) %}
|
||||
{{ tables.datatable_multiupdate_row(entry.id) }}
|
||||
{% endif %}
|
||||
{% elseif column == 'date' %}
|
||||
{{ entry.begin|date_short }}
|
||||
{% elseif column == 'starttime' %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% block main %}
|
||||
{% set formEditTemplate = kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||
{% set formOptions = {
|
||||
'title': (access_token.id is null ? 'create'|trans : 'edit'|trans({}, 'actions')),
|
||||
'title': (access_token.id is null ? 'create'|trans : 'edit'|trans),
|
||||
'form': form,
|
||||
'back': path('user_profile_access_token', {'username': user.userIdentifier})
|
||||
} %}
|
||||
|
||||
@@ -225,7 +225,7 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
|
||||
if (\count($globalError) > 0) {
|
||||
self::assertArrayHasKey('errors', $result['errors']);
|
||||
foreach ($globalError as $err) {
|
||||
self::assertTrue(\in_array($err, $result['errors']['errors']), 'Missing global validation error: ' . $err);
|
||||
self::assertTrue(\in_array($err, $result['errors']['errors']), 'Missing global validation error: ' . $err); // @phpstan-ignore binaryOp.invalid
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,11 +60,13 @@ class ActionsControllerTest extends APIControllerBaseTest
|
||||
|
||||
$this->assertIsArray($result);
|
||||
foreach ($result as $item) {
|
||||
self::assertIsArray($item);
|
||||
self::assertApiResponseTypeStructure('PageActionItem', $item);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ($entries as $id) {
|
||||
self::assertIsArray($result[$i]);
|
||||
self::assertEquals($id, $result[$i]['id'], \sprintf('Failed action "%s" with name "%s" in view "%s"', $i, $id, $view));
|
||||
$i++;
|
||||
}
|
||||
@@ -110,11 +112,13 @@ class ActionsControllerTest extends APIControllerBaseTest
|
||||
|
||||
$this->assertIsArray($result);
|
||||
foreach ($result as $item) {
|
||||
self::assertIsArray($item);
|
||||
self::assertApiResponseTypeStructure('PageActionItem', $item);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ($entries as $id) {
|
||||
self::assertIsArray($result[$i]);
|
||||
self::assertEquals($id, $result[$i]['id'], \sprintf('Failed action "%s" with name "%s" in view "%s"', $i, $id, $view));
|
||||
$i++;
|
||||
}
|
||||
@@ -159,11 +163,13 @@ class ActionsControllerTest extends APIControllerBaseTest
|
||||
|
||||
$this->assertIsArray($result);
|
||||
foreach ($result as $item) {
|
||||
self::assertIsArray($item);
|
||||
self::assertApiResponseTypeStructure('PageActionItem', $item);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ($entries as $id) {
|
||||
self::assertIsArray($result[$i]);
|
||||
self::assertEquals($id, $result[$i]['id'], \sprintf('Failed action "%s" with name "%s" in view "%s"', $i, $id, $view));
|
||||
$i++;
|
||||
}
|
||||
@@ -204,11 +210,13 @@ class ActionsControllerTest extends APIControllerBaseTest
|
||||
|
||||
$this->assertIsArray($result);
|
||||
foreach ($result as $item) {
|
||||
self::assertIsArray($item);
|
||||
self::assertApiResponseTypeStructure('PageActionItem', $item);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ($entries as $id) {
|
||||
self::assertIsArray($result[$i]);
|
||||
self::assertEquals($id, $result[$i]['id'], \sprintf('Failed action "%s" with name "%s" in view "%s"', $i, $id, $view));
|
||||
$i++;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use App\Repository\ActivityRateRepository;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Repository\Query\VisibilityInterface;
|
||||
use App\Tests\Mocks\ActivityTestMetaFieldSubscriberMock;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
@@ -179,6 +180,7 @@ class ActivityControllerTest extends APIControllerBaseTest
|
||||
for ($i = 0; $i < \count($result); $i++) {
|
||||
$activity = $result[$i];
|
||||
$hasProject = $expected[$i][0];
|
||||
self::assertIsArray($activity);
|
||||
self::assertApiResponseTypeStructure('ActivityCollection', $activity);
|
||||
if ($hasProject && $projectId !== null) {
|
||||
$this->assertEquals($projectId, $activity['project']);
|
||||
@@ -219,6 +221,7 @@ class ActivityControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(5, \count($result));
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('ActivityCollection', $result[0]);
|
||||
$this->assertEquals($imports[0]->getId(), $result[4]['project']);
|
||||
$this->assertEquals($imports[1]->getId(), $result[3]['project']);
|
||||
@@ -429,7 +432,9 @@ class ActivityControllerTest extends APIControllerBaseTest
|
||||
public function testMetaAction(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
static::getContainer()->get('event_dispatcher')->addSubscriber(new ActivityTestMetaFieldSubscriberMock());
|
||||
/** @var EventDispatcher $dispatcher */
|
||||
$dispatcher = static::getContainer()->get('event_dispatcher');
|
||||
$dispatcher->addSubscriber(new ActivityTestMetaFieldSubscriberMock());
|
||||
|
||||
$data = [
|
||||
'name' => 'metatestmock',
|
||||
|
||||
@@ -20,7 +20,6 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
|
||||
/**
|
||||
* @covers \App\API\Authentication\TokenAuthenticator
|
||||
@@ -118,7 +117,6 @@ class TokenAuthenticatorTest extends TestCase
|
||||
|
||||
$request = new Request([], [], [], [], [], ['REQUEST_URI' => '/api/fooo', 'HTTP_X-AUTH-USER' => 'foo2', 'HTTP_X-AUTH-TOKEN' => 'bar']);
|
||||
$passport = $sut->authenticate($request);
|
||||
self::assertInstanceOf(Passport::class, $passport);
|
||||
$badge = $passport->getBadge(UserBadge::class);
|
||||
self::assertInstanceOf(UserBadge::class, $badge);
|
||||
self::assertEquals('foo2', $badge->getUserIdentifier());
|
||||
|
||||
@@ -21,6 +21,7 @@ use App\Entity\User;
|
||||
use App\Repository\CustomerRateRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Tests\Mocks\CustomerTestMetaFieldSubscriberMock;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
@@ -104,6 +105,7 @@ class CustomerControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(1, \count($result));
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('CustomerCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -120,6 +122,7 @@ class CustomerControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(1, \count($result));
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('CustomerCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -372,7 +375,9 @@ class CustomerControllerTest extends APIControllerBaseTest
|
||||
public function testMetaAction(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
self::getContainer()->get('event_dispatcher')->addSubscriber(new CustomerTestMetaFieldSubscriberMock());
|
||||
/** @var EventDispatcher $dispatcher */
|
||||
$dispatcher = static::getContainer()->get('event_dispatcher');
|
||||
$dispatcher->addSubscriber(new CustomerTestMetaFieldSubscriberMock());
|
||||
|
||||
$data = [
|
||||
'name' => 'metatestmock',
|
||||
|
||||
@@ -51,6 +51,7 @@ class InvoiceControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(10, \count($result));
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('InvoiceCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -72,6 +73,7 @@ class InvoiceControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(7, \count($result));
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('InvoiceCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -91,6 +93,7 @@ class InvoiceControllerTest extends APIControllerBaseTest
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(4, \count($result));
|
||||
$this->assertPagination($client->getResponse(), 2, 4, 5, 20);
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('InvoiceCollection', $result[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ use App\Repository\ProjectRateRepository;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\VisibilityInterface;
|
||||
use App\Tests\Mocks\ProjectTestMetaFieldSubscriberMock;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpKernel\HttpKernelBrowser;
|
||||
|
||||
/**
|
||||
@@ -105,6 +106,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(1, \count($result));
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('ProjectCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -219,6 +221,7 @@ class ProjectControllerTest extends APIControllerBaseTest
|
||||
|
||||
for ($i = 0; $i < \count($expected); $i++) {
|
||||
$project = $result[$i];
|
||||
self::assertIsArray($project);
|
||||
self::assertApiResponseTypeStructure('ProjectCollection', $project);
|
||||
if ($customerId !== null) {
|
||||
$this->assertEquals($customerId, $project['customer']);
|
||||
@@ -579,7 +582,9 @@ class ProjectControllerTest extends APIControllerBaseTest
|
||||
public function testMetaAction(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
self::getContainer()->get('event_dispatcher')->addSubscriber(new ProjectTestMetaFieldSubscriberMock());
|
||||
/** @var EventDispatcher $dispatcher */
|
||||
$dispatcher = static::getContainer()->get('event_dispatcher');
|
||||
$dispatcher->addSubscriber(new ProjectTestMetaFieldSubscriberMock());
|
||||
|
||||
$data = [
|
||||
'name' => 'metatestmock',
|
||||
|
||||
@@ -150,7 +150,13 @@ trait RateControllerTestTrait
|
||||
$this->assertEquals(\count($expectedRates), \count($result));
|
||||
|
||||
foreach ($result as $rate) {
|
||||
$this->assertRateStructure($rate, ($rate['user'] === null ? null : $rate['user']['id']));
|
||||
$this->assertIsArray($rate);
|
||||
if ($rate['user'] === null) {
|
||||
$this->assertRateStructure($rate);
|
||||
} else {
|
||||
$this->assertIsArray($rate['user']);
|
||||
$this->assertRateStructure($rate, $rate['user']['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ class TagControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(3, \count($result));
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TagEntity', $result[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result);
|
||||
$this->assertNotEmpty($result);
|
||||
self::assertEquals(2, \count($result));
|
||||
self::assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TeamCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -152,7 +153,9 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$updateId = $result['id'];
|
||||
$this->assertIsNumeric($updateId);
|
||||
|
||||
$data = [
|
||||
'name' => 'foo',
|
||||
@@ -171,14 +174,17 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertNotEmpty($result['id']);
|
||||
self::assertCount(3, $result['members']);
|
||||
$this->assertIsNumeric($updateId);
|
||||
|
||||
$this->request($client, '/api/teams/' . $updateId);
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['members']);
|
||||
self::assertCount(3, $result['members']);
|
||||
|
||||
$this->assertIsArray($result['members'][1]);
|
||||
self::assertFalse($result['members'][1]['teamlead']);
|
||||
self::assertEquals(1, $result['members'][1]['user']['id']);
|
||||
self::assertEquals('clara_customer', $result['members'][1]['user']['username']);
|
||||
@@ -187,7 +193,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
self::assertEquals(4, $result['members'][2]['user']['id']);
|
||||
self::assertEquals('tony_teamlead', $result['members'][2]['user']['username']);
|
||||
|
||||
self::assertTrue(true, $result['members'][0]['teamlead']);
|
||||
self::assertTrue($result['members'][0]['teamlead']);
|
||||
self::assertEquals(2, $result['members'][0]['user']['id']);
|
||||
self::assertEquals('john_user', $result['members'][0]['user']['username']);
|
||||
}
|
||||
@@ -204,6 +210,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
$data = [
|
||||
'name' => '1',
|
||||
@@ -230,6 +238,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertNotEmpty($result['id']);
|
||||
$id = $result['id'];
|
||||
$this->assertIsNumeric($id);
|
||||
|
||||
$this->request($client, '/api/teams/' . $id, 'DELETE');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
@@ -249,7 +258,10 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsArray($result['members']);
|
||||
self::assertCount(1, $result['members']);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/2', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
@@ -257,6 +269,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['members']);
|
||||
self::assertCount(2, $result['members']);
|
||||
}
|
||||
|
||||
@@ -273,6 +286,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
// team not found
|
||||
$this->assertEntityNotFoundForPost($client, '/api/teams/999/members/999');
|
||||
@@ -303,6 +318,9 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
$this->assertIsArray($result['members']);
|
||||
self::assertCount(4, $result['members']);
|
||||
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/members/2', 'DELETE');
|
||||
@@ -311,6 +329,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['members']);
|
||||
self::assertCount(3, $result['members']);
|
||||
}
|
||||
|
||||
@@ -329,6 +348,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
// team not found
|
||||
$this->assertNotFoundForDelete($client, '/api/teams/999/members/999');
|
||||
@@ -359,6 +380,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(0, $result['customers']);
|
||||
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'POST');
|
||||
@@ -367,7 +390,9 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['customers']);
|
||||
self::assertCount(1, $result['customers']);
|
||||
$this->assertIsArray($result['customers'][0]);
|
||||
self::assertEquals(1, $result['customers'][0]['id']);
|
||||
}
|
||||
|
||||
@@ -384,6 +409,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
// team not found
|
||||
$this->assertEntityNotFoundForPost($client, '/api/teams/999/customers/999');
|
||||
@@ -395,6 +422,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(1, $result['customers']);
|
||||
|
||||
// cannot add existing customer
|
||||
@@ -416,12 +445,16 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(0, $result['customers']);
|
||||
|
||||
// add customer
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(1, $result['customers']);
|
||||
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'DELETE');
|
||||
@@ -429,7 +462,10 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['customers']);
|
||||
self::assertCount(0, $result['customers']);
|
||||
|
||||
/** @var EntityManager $em */
|
||||
@@ -454,6 +490,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
// team not found
|
||||
$this->assertNotFoundForDelete($client, '/api/teams/999/customers/999');
|
||||
@@ -477,6 +515,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(0, $result['projects']);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
@@ -484,7 +524,9 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['projects']);
|
||||
self::assertCount(1, $result['projects']);
|
||||
$this->assertIsArray($result['projects'][0]);
|
||||
self::assertEquals(1, $result['projects'][0]['id']);
|
||||
}
|
||||
|
||||
@@ -501,6 +543,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
// team not found
|
||||
$this->assertEntityNotFoundForPost($client, '/api/teams/999/projects/999');
|
||||
@@ -514,6 +558,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(1, $result['projects']);
|
||||
|
||||
// cannot add existing project
|
||||
@@ -535,12 +581,16 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(0, $result['projects']);
|
||||
|
||||
// add project
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsArray($result['projects']);
|
||||
self::assertCount(1, $result['projects']);
|
||||
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'DELETE');
|
||||
@@ -549,6 +599,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['projects']);
|
||||
self::assertCount(0, $result['projects']);
|
||||
|
||||
/** @var EntityManager $em */
|
||||
@@ -573,6 +624,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
// team not found
|
||||
$this->assertNotFoundForDelete($client, '/api/teams/999/projects/999');
|
||||
@@ -596,6 +649,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(0, $result['activities']);
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/activities/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
@@ -603,7 +658,9 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['activities']);
|
||||
self::assertCount(1, $result['activities']);
|
||||
$this->assertIsArray($result['activities'][0]);
|
||||
self::assertEquals(1, $result['activities'][0]['id']);
|
||||
}
|
||||
|
||||
@@ -620,6 +677,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
// team not found
|
||||
$this->assertEntityNotFoundForPost($client, '/api/teams/999/activities/999');
|
||||
@@ -631,6 +690,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/activities/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(1, $result['activities']);
|
||||
|
||||
// cannot add existing activity
|
||||
@@ -652,12 +713,16 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsArray($result['activities']);
|
||||
self::assertCount(0, $result['activities']);
|
||||
|
||||
// add activity
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/activities/1', 'POST');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
self::assertCount(1, $result['activities']);
|
||||
|
||||
$this->request($client, '/api/teams/' . $result['id'] . '/activities/1', 'DELETE');
|
||||
@@ -666,6 +731,7 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TeamEntity', $result);
|
||||
$this->assertIsArray($result['activities']);
|
||||
self::assertCount(0, $result['activities']);
|
||||
}
|
||||
|
||||
@@ -684,6 +750,8 @@ class TeamControllerTest extends APIControllerBaseTest
|
||||
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$result = json_decode($client->getResponse()->getContent(), true);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertIsNumeric($result['id']);
|
||||
|
||||
// team not found
|
||||
$this->assertNotFoundForDelete($client, '/api/teams/999/activities/9999');
|
||||
|
||||
@@ -20,6 +20,7 @@ use App\Entity\User;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
use App\Tests\Mocks\TimesheetTestMetaFieldSubscriberMock;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
@@ -63,6 +64,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(10, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -79,6 +81,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(10, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $result[0]);
|
||||
}
|
||||
|
||||
@@ -103,6 +106,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(10, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
|
||||
$query = ['users' => [2]];
|
||||
@@ -115,6 +119,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(10, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -162,6 +167,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(10, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -186,6 +192,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(17, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -237,6 +244,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(4, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -289,6 +297,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(5, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -324,6 +333,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(7, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
|
||||
$query = [
|
||||
@@ -342,6 +352,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(10, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
|
||||
$query = [
|
||||
@@ -358,6 +369,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(17, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -896,6 +908,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||
self::assertNotEmpty($result['id']);
|
||||
self::assertIsNumeric($result['id']);
|
||||
$id = $result['id'];
|
||||
|
||||
$this->request($client, '/api/timesheets/' . $id, 'DELETE');
|
||||
@@ -999,6 +1012,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(1, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $result[0]);
|
||||
}
|
||||
|
||||
@@ -1024,6 +1038,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
|
||||
self::assertEquals(3, \count($result));
|
||||
foreach ($result as $timesheet) {
|
||||
$this->assertIsArray($timesheet);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $timesheet);
|
||||
}
|
||||
}
|
||||
@@ -1142,6 +1157,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(10, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
|
||||
$query = ['tags' => ['Test', 'Admin']];
|
||||
@@ -1154,6 +1170,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(10, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
|
||||
$query = ['tags' => ['Nothing-2-see', 'here']];
|
||||
@@ -1166,6 +1183,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(20, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
}
|
||||
|
||||
@@ -1190,6 +1208,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsString($content);
|
||||
$result = json_decode($content, true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||
$this->assertEmpty($result['description']);
|
||||
$this->assertEmpty($result['tags']);
|
||||
@@ -1227,6 +1246,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsString($content);
|
||||
$result = json_decode($content, true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||
$this->assertEmpty($result['description']);
|
||||
$this->assertEmpty($result['tags']);
|
||||
@@ -1272,6 +1292,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsString($content);
|
||||
$result = json_decode($content, true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||
self::assertEquals('foo', $result['description']);
|
||||
self::assertEquals([['name' => 'sdfsdf', 'value' => 'nnnnn'], ['name' => '1234567890', 'value' => '1234567890']], $result['metaFields']);
|
||||
@@ -1342,8 +1363,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertNotEmpty($result['id']);
|
||||
$this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied
|
||||
self::assertEquals(2016, $result['rate']);
|
||||
|
||||
$this->request($client, '/api/timesheets/' . $result['id'] . '/duplicate', 'PATCH');
|
||||
$id = $result['id'];
|
||||
self::assertIsNumeric($id);
|
||||
$this->request($client, '/api/timesheets/' . $id . '/duplicate', 'PATCH');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$content = $client->getResponse()->getContent();
|
||||
@@ -1380,6 +1402,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsString($content);
|
||||
$result = json_decode($content, true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||
|
||||
$em->clear();
|
||||
@@ -1456,7 +1479,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$timesheets = $this->importFixtureForUser(User::ROLE_USER);
|
||||
$id = $timesheets[0]->getId();
|
||||
static::getContainer()->get('event_dispatcher')->addSubscriber(new TimesheetTestMetaFieldSubscriberMock());
|
||||
/** @var EventDispatcher $dispatcher */
|
||||
$dispatcher = static::getContainer()->get('event_dispatcher');
|
||||
$dispatcher->addSubscriber(new TimesheetTestMetaFieldSubscriberMock());
|
||||
|
||||
$data = [
|
||||
'name' => 'metatestmock',
|
||||
@@ -1472,6 +1497,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
self::assertIsString($content);
|
||||
$result = json_decode($content, true);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
|
||||
self::assertEquals(['name' => 'metatestmock', 'value' => 'another,testing,bar'], $result['metaFields'][0]);
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ class UserControllerTest extends APIControllerBaseTest
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(7, \count($result));
|
||||
foreach ($result as $user) {
|
||||
$this->assertIsArray($user);
|
||||
self::assertApiResponseTypeStructure('UserCollection', $user);
|
||||
}
|
||||
}
|
||||
@@ -70,6 +71,7 @@ class UserControllerTest extends APIControllerBaseTest
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(7, \count($result));
|
||||
foreach ($result as $user) {
|
||||
$this->assertIsArray($user);
|
||||
self::assertApiResponseTypeStructure('UserEntity', $user);
|
||||
}
|
||||
}
|
||||
@@ -86,6 +88,7 @@ class UserControllerTest extends APIControllerBaseTest
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(1, \count($result));
|
||||
foreach ($result as $user) {
|
||||
$this->assertIsArray($user);
|
||||
self::assertApiResponseTypeStructure('UserCollection', $user);
|
||||
}
|
||||
}
|
||||
@@ -102,6 +105,7 @@ class UserControllerTest extends APIControllerBaseTest
|
||||
$this->assertNotEmpty($result);
|
||||
$this->assertEquals(8, \count($result));
|
||||
foreach ($result as $user) {
|
||||
$this->assertIsArray($user);
|
||||
self::assertApiResponseTypeStructure('UserCollection', $user);
|
||||
}
|
||||
}
|
||||
@@ -283,7 +287,9 @@ class UserControllerTest extends APIControllerBaseTest
|
||||
'ROLE_TEAMLEAD',
|
||||
],
|
||||
];
|
||||
$this->request($client, '/api/users/' . $result['id'], 'PATCH', [], json_encode($data));
|
||||
$id = $result['id'];
|
||||
self::assertIsNumeric($id);
|
||||
$this->request($client, '/api/users/' . $id, 'PATCH', [], json_encode($data));
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$content = $client->getResponse()->getContent();
|
||||
|
||||
@@ -113,11 +113,7 @@ class ActivityServiceTest extends TestCase
|
||||
{
|
||||
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$dispatcher->expects($this->exactly(2))->method('dispatch')->willReturnCallback(function ($event) {
|
||||
if ($event instanceof ActivityMetaDefinitionEvent) {
|
||||
self::assertInstanceOf(Activity::class, $event->getEntity());
|
||||
} elseif ($event instanceof ActivityCreateEvent) {
|
||||
self::assertInstanceOf(Activity::class, $event->getActivity());
|
||||
} else {
|
||||
if (!$event instanceof ActivityMetaDefinitionEvent && !$event instanceof ActivityCreateEvent) {
|
||||
$this->fail('Invalid event received');
|
||||
}
|
||||
|
||||
@@ -136,11 +132,7 @@ class ActivityServiceTest extends TestCase
|
||||
{
|
||||
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$dispatcher->expects($this->exactly(2))->method('dispatch')->willReturnCallback(function ($event) {
|
||||
if ($event instanceof ActivityCreatePreEvent) {
|
||||
self::assertInstanceOf(Activity::class, $event->getActivity());
|
||||
} elseif ($event instanceof ActivityCreatePostEvent) {
|
||||
self::assertInstanceOf(Activity::class, $event->getActivity());
|
||||
} else {
|
||||
if (!$event instanceof ActivityCreatePreEvent && !$event instanceof ActivityCreatePostEvent) {
|
||||
$this->fail('Invalid event received');
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Command\ActivateUserCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
@@ -33,6 +34,7 @@ class ActivateUserCommandTest extends KernelTestCase
|
||||
$this->application = new Application($kernel);
|
||||
$container = self::$kernel->getContainer();
|
||||
|
||||
/** @var UserService $userService */
|
||||
$userService = $container->get(UserService::class);
|
||||
|
||||
$this->application->add(new ActivateUserCommand($userService));
|
||||
@@ -71,8 +73,10 @@ class ActivateUserCommandTest extends KernelTestCase
|
||||
$this->assertStringContainsString('[OK] User "chris_user" has been activated.', $output);
|
||||
|
||||
$container = self::$kernel->getContainer();
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = $container->get('doctrine')->getRepository(User::class);
|
||||
$userRepository = $doctrine->getRepository(User::class);
|
||||
$user = $userRepository->loadUserByIdentifier('chris_user');
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertTrue($user->isEnabled());
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Command\ChangePasswordCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
@@ -35,6 +36,7 @@ class ChangePasswordCommandTest extends KernelTestCase
|
||||
$this->application = new Application($kernel);
|
||||
$container = self::$kernel->getContainer();
|
||||
|
||||
/** @var UserService $userService */
|
||||
$userService = $container->get(UserService::class);
|
||||
|
||||
$this->application->add(new ChangePasswordCommand($userService));
|
||||
@@ -86,8 +88,10 @@ class ChangePasswordCommandTest extends KernelTestCase
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('[OK] Changed password for user "john_user".', $output);
|
||||
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = self::getContainer()->get('doctrine');
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = self::getContainer()->get('doctrine')->getRepository(User::class);
|
||||
$userRepository = $doctrine->getRepository(User::class);
|
||||
$user = $userRepository->loadUserByIdentifier('john_user');
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Command\CreateUserCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
@@ -31,10 +32,9 @@ class CreateUserCommandTest extends KernelTestCase
|
||||
$kernel = self::bootKernel();
|
||||
$this->application = new Application($kernel);
|
||||
$container = self::$kernel->getContainer();
|
||||
|
||||
$this->application->add(new CreateUserCommand(
|
||||
$container->get(UserService::class),
|
||||
));
|
||||
/** @var UserService $userService */
|
||||
$userService = $container->get(UserService::class);
|
||||
$this->application->add(new CreateUserCommand($userService));
|
||||
}
|
||||
|
||||
public function testCreateUserFailsForShortPassword(): void
|
||||
@@ -53,11 +53,12 @@ class CreateUserCommandTest extends KernelTestCase
|
||||
$this->assertStringContainsString('[OK] Success! Created user: MyTestUser', $output);
|
||||
|
||||
$container = self::$kernel->getContainer();
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = $container->get('doctrine')->getRepository(User::class);
|
||||
$userRepository = $doctrine->getRepository(User::class);
|
||||
$user = $userRepository->loadUserByIdentifier('MyTestUser');
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertNotNull($user);
|
||||
}
|
||||
|
||||
protected function createUser($username, $email, $role, $password): CommandTester
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Command\DeactivateUserCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
@@ -32,7 +33,7 @@ class DeactivateUserCommandTest extends KernelTestCase
|
||||
$kernel = self::bootKernel();
|
||||
$this->application = new Application($kernel);
|
||||
$container = self::$kernel->getContainer();
|
||||
|
||||
/** @var UserService $userService */
|
||||
$userService = $container->get(UserService::class);
|
||||
|
||||
$this->application->add(new DeactivateUserCommand($userService));
|
||||
@@ -71,8 +72,10 @@ class DeactivateUserCommandTest extends KernelTestCase
|
||||
$this->assertStringContainsString('[OK] User "john_user" has been deactivated.', $output);
|
||||
|
||||
$container = self::$kernel->getContainer();
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = $container->get('doctrine')->getRepository(User::class);
|
||||
$userRepository = $doctrine->getRepository(User::class);
|
||||
$user = $userRepository->loadUserByIdentifier('john_user');
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertFalse($user->isEnabled());
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Command\DemoteUserCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
@@ -34,6 +35,7 @@ class DemoteUserCommandTest extends KernelTestCase
|
||||
$this->application = new Application($kernel);
|
||||
$container = self::$kernel->getContainer();
|
||||
|
||||
/** @var UserService $userService */
|
||||
$userService = $container->get(UserService::class);
|
||||
|
||||
$this->application->add(new DemoteUserCommand($userService));
|
||||
@@ -80,8 +82,10 @@ class DemoteUserCommandTest extends KernelTestCase
|
||||
$this->assertStringContainsString('[OK] Role "ROLE_TEAMLEAD" has been removed from user "tony_teamlead".', $output);
|
||||
|
||||
$container = self::$kernel->getContainer();
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = $container->get('doctrine')->getRepository(User::class);
|
||||
$userRepository = $doctrine->getRepository(User::class);
|
||||
$user = $userRepository->loadUserByIdentifier('tony_teamlead');
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertFalse($user->hasTeamleadRole());
|
||||
@@ -95,8 +99,10 @@ class DemoteUserCommandTest extends KernelTestCase
|
||||
$this->assertStringContainsString('[OK] Super administrator role has been removed from the user "susan_super".', $output);
|
||||
|
||||
$container = self::$kernel->getContainer();
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = $container->get('doctrine')->getRepository(User::class);
|
||||
$userRepository = $doctrine->getRepository(User::class);
|
||||
$user = $userRepository->loadUserByIdentifier('susan_super');
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertFalse($user->isSuperAdmin());
|
||||
|
||||
@@ -45,6 +45,9 @@ class ExportCreateCommandTest extends KernelTestCase
|
||||
|
||||
if (is_dir($path)) {
|
||||
$files = glob($path . '*');
|
||||
if ($files === false) {
|
||||
return;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
unlink($file);
|
||||
}
|
||||
@@ -71,12 +74,12 @@ class ExportCreateCommandTest extends KernelTestCase
|
||||
$container = self::getContainer();
|
||||
|
||||
$application->add(new ExportCreateCommand(
|
||||
$container->get(ServiceExport::class),
|
||||
$container->get(CustomerRepository::class),
|
||||
$container->get(ProjectRepository::class),
|
||||
$container->get(TeamRepository::class),
|
||||
$container->get(UserRepository::class),
|
||||
$container->get(TranslatorInterface::class),
|
||||
$container->get(ServiceExport::class), // @phpstan-ignore argument.type
|
||||
$container->get(CustomerRepository::class), // @phpstan-ignore argument.type
|
||||
$container->get(ProjectRepository::class), // @phpstan-ignore argument.type
|
||||
$container->get(TeamRepository::class), // @phpstan-ignore argument.type
|
||||
$container->get(UserRepository::class), // @phpstan-ignore argument.type
|
||||
$container->get(TranslatorInterface::class), // @phpstan-ignore argument.type
|
||||
$mailer ?? $container->get(KimaiMailer::class),
|
||||
));
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\InstallCommand;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
@@ -27,9 +28,11 @@ class InstallCommandTest extends KernelTestCase
|
||||
$kernel = self::bootKernel();
|
||||
$this->application = new Application($kernel);
|
||||
$container = self::$kernel->getContainer();
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
|
||||
$this->application->add(new InstallCommand(
|
||||
$container->get('doctrine')->getConnection()
|
||||
$doctrine->getConnection() // @phpstan-ignore argument.type
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ class InvoiceCreateCommandTest extends KernelTestCase
|
||||
|
||||
if (is_dir($path)) {
|
||||
$files = glob($path . '*');
|
||||
if ($files === false) {
|
||||
return;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
unlink($file);
|
||||
}
|
||||
@@ -64,12 +67,12 @@ class InvoiceCreateCommandTest extends KernelTestCase
|
||||
$container = self::getContainer();
|
||||
|
||||
$this->application->add(new InvoiceCreateCommand(
|
||||
$container->get(ServiceInvoice::class),
|
||||
$container->get(CustomerRepository::class),
|
||||
$container->get(ProjectRepository::class),
|
||||
$container->get(InvoiceTemplateRepository::class),
|
||||
$container->get(UserRepository::class),
|
||||
$container->get('event_dispatcher')
|
||||
$container->get(ServiceInvoice::class), // @phpstan-ignore argument.type
|
||||
$container->get(CustomerRepository::class), // @phpstan-ignore argument.type
|
||||
$container->get(ProjectRepository::class), // @phpstan-ignore argument.type
|
||||
$container->get(InvoiceTemplateRepository::class), // @phpstan-ignore argument.type
|
||||
$container->get(UserRepository::class), // @phpstan-ignore argument.type
|
||||
$container->get('event_dispatcher') // @phpstan-ignore argument.type
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Command\PromoteUserCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
@@ -35,6 +36,7 @@ class PromoteUserCommandTest extends KernelTestCase
|
||||
$container = self::$kernel->getContainer();
|
||||
|
||||
$userService = $container->get(UserService::class);
|
||||
$this->assertInstanceOf(UserService::class, $userService);
|
||||
|
||||
$this->application->add(new PromoteUserCommand($userService));
|
||||
}
|
||||
@@ -80,8 +82,10 @@ class PromoteUserCommandTest extends KernelTestCase
|
||||
$this->assertStringContainsString('[OK] Role "ROLE_TEAMLEAD" has been added to user "john_user".', $output);
|
||||
|
||||
$container = self::$kernel->getContainer();
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = $container->get('doctrine')->getRepository(User::class);
|
||||
$userRepository = $doctrine->getRepository(User::class);
|
||||
$user = $userRepository->loadUserByIdentifier('john_user');
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertTrue($user->hasTeamleadRole());
|
||||
@@ -95,8 +99,10 @@ class PromoteUserCommandTest extends KernelTestCase
|
||||
$this->assertStringContainsString('[OK] User "john_user" has been promoted as a super administrator.', $output);
|
||||
|
||||
$container = self::$kernel->getContainer();
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $container->get('doctrine');
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = $container->get('doctrine')->getRepository(User::class);
|
||||
$userRepository = $doctrine->getRepository(User::class);
|
||||
$user = $userRepository->loadUserByIdentifier('john_user');
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertTrue($user->isSuperAdmin());
|
||||
|
||||
@@ -42,7 +42,7 @@ class VersionCommandTest extends KernelTestCase
|
||||
$this->assertEquals($result . PHP_EOL, $output);
|
||||
}
|
||||
|
||||
public function getTestData(): array // @phpstan-ignore-line
|
||||
public function getTestData(): array // @phpstan-ignore missingType.iterableValue
|
||||
{
|
||||
return [
|
||||
[[], 'Kimai ' . Constants::VERSION . ' by Kevin Papst.'],
|
||||
|
||||
@@ -20,6 +20,7 @@ use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
use App\Tests\Mocks\ActivityTestMetaFieldSubscriberMock;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Symfony\Component\DomCrawler\Field\ChoiceFormField;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpKernel\HttpKernelBrowser;
|
||||
|
||||
/**
|
||||
@@ -203,7 +204,6 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
]);
|
||||
|
||||
$location = $this->assertIsModalRedirect($client, '/details');
|
||||
self::assertNotNull($location);
|
||||
$this->requestPure($client, $location);
|
||||
|
||||
$this->assertDetailsPage($client);
|
||||
@@ -221,7 +221,9 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
public function testCreateActionShowsMetaFields(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
self::getContainer()->get('event_dispatcher')->addSubscriber(new ActivityTestMetaFieldSubscriberMock());
|
||||
$eventDispatcher = self::getContainer()->get('event_dispatcher');
|
||||
static::assertInstanceOf(EventDispatcher::class, $eventDispatcher);
|
||||
$eventDispatcher->addSubscriber(new ActivityTestMetaFieldSubscriberMock());
|
||||
$this->assertAccessIsGranted($client, '/admin/activity/create');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ use App\Tests\Mocks\SystemConfigurationFactory;
|
||||
use OneLogin\Saml2\Auth;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Security\Http\SecurityRequestAttributes;
|
||||
@@ -105,7 +104,6 @@ class SamlControllerTest extends TestCase
|
||||
$sut = new SamlController($factory, $this->getSamlConfiguration());
|
||||
$result = $sut->metadataAction();
|
||||
|
||||
self::assertInstanceOf(Response::class, $result);
|
||||
self::assertEquals('xml', $result->headers->get('Content-Type'));
|
||||
|
||||
$expected = new \DOMDocument();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user