Release 2.0.34 (#4281)

This commit is contained in:
Kevin Papst
2023-09-17 22:32:01 +02:00
committed by GitHub
parent a6b237936e
commit de419350b2
29 changed files with 383 additions and 482 deletions

View File

@@ -21,9 +21,9 @@ now requires the `charset` and `serverVersion` params, e.g.: `DATABASE_URL=mysql
- All plugins need to be updated: delete all previous version from your installation (`rm -r var/plugins/*`) before updating!
- The `local.yaml` is not compatible with old version, remove it before the update and then re-create it after everything works
- dashboard default config
- removed: theme.branding.translation
- removed: kimai.plugin_dir
- removed: configuring the `dashboard` is not supported any longer
- removed: custom translation files via `theme.branding.translation`
- removed: changing the plugin directory via `kimai.plugin_dir`
### Developer

View File

@@ -440,8 +440,14 @@ export default class KimaiCalendar {
calendarSource = {...calendarSource, ...{
id: 'kimai-' + source.id,
events: (fetchInfo, successCallback, failureCallback) => {
let url = source.url.replace('{from}', DATES.formatForAPI(fetchInfo.start));
url = url.replace('{to}', DATES.formatForAPI(fetchInfo.end));
const targetFrom = DATES.formatForAPI(fetchInfo.start);
const targetTo = DATES.formatForAPI(fetchInfo.end);
let url = source.url;
url = url.replace('{from}', targetFrom);
url = url.replace('__FROM__', targetFrom);
url = url.replace('{to}', targetTo);
url = url.replace('__TO__', targetTo);
API.get(url, {}, result => {
let apiEvents = [];
@@ -463,8 +469,14 @@ export default class KimaiCalendar {
id: 'json-' + source.id,
editable: false,
events: (fetchInfo, successCallback, failureCallback) => {
let url = source.url.replace('{from}', DATES.formatForAPI(fetchInfo.start));
url = url.replace('{to}', DATES.formatForAPI(fetchInfo.end));
const targetFrom = DATES.formatForAPI(fetchInfo.start);
const targetTo = DATES.formatForAPI(fetchInfo.end);
let url = source.url;
url = url.replace('{from}', targetFrom);
url = url.replace('__FROM__', targetFrom);
url = url.replace('{to}', targetTo);
url = url.replace('__TO__', targetTo);
API.get(url, {}, result => {
let apiEvents = [];

View File

@@ -179,7 +179,13 @@
"tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
"tests-integration": "vendor/bin/phpunit --group integration tests/",
"phpstan": [
"vendor/bin/phpstan analyse -c phpstan.neon",
"@phpstan-src",
"@phpstan-tests"
],
"phpstan-src": [
"vendor/bin/phpstan analyse -c phpstan.neon"
],
"phpstan-tests": [
"vendor/bin/phpstan analyse -c tests/phpstan.neon"
],
"codestyle": "vendor/bin/php-cs-fixer fix --dry-run --verbose --show-progress=none",

441
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1221,11 +1221,6 @@ parameters:
count: 3
path: src/Controller/Reporting/ProjectDateRangeController.php
-
message: "#^Method App\\\\Controller\\\\Reporting\\\\ProjectDateRangeController\\:\\:__invoke\\(\\) has no return type specified\\.$#"
count: 1
path: src/Controller/Reporting/ProjectDateRangeController.php
-
message: "#^Parameter \\#1 \\$begin of method App\\\\Form\\\\Model\\\\DateRange\\:\\:setBegin\\(\\) expects DateTime, DateTime\\|null given\\.$#"
count: 1
@@ -1241,31 +1236,16 @@ parameters:
count: 1
path: src/Controller/Reporting/ProjectDateRangeController.php
-
message: "#^Method App\\\\Controller\\\\Reporting\\\\ProjectDetailsController\\:\\:__invoke\\(\\) has no return type specified\\.$#"
count: 1
path: src/Controller/Reporting/ProjectDetailsController.php
-
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
count: 3
path: src/Controller/Reporting/ProjectInactiveController.php
-
message: "#^Method App\\\\Controller\\\\Reporting\\\\ProjectInactiveController\\:\\:__invoke\\(\\) has no return type specified\\.$#"
count: 1
path: src/Controller/Reporting/ProjectInactiveController.php
-
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
count: 3
path: src/Controller/Reporting/ProjectViewController.php
-
message: "#^Method App\\\\Controller\\\\Reporting\\\\ProjectViewController\\:\\:__invoke\\(\\) has no return type specified\\.$#"
count: 1
path: src/Controller/Reporting/ProjectViewController.php
-
message: "#^Method App\\\\Controller\\\\Reporting\\\\ReportUsersMonthController\\:\\:getData\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
@@ -1591,11 +1571,6 @@ parameters:
count: 1
path: src/Entity/Activity.php
-
message: "#^Method App\\\\Entity\\\\Activity\\:\\:addTeam\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Activity.php
-
message: "#^Method App\\\\Entity\\\\Activity\\:\\:getMetaFields\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
count: 1
@@ -1606,11 +1581,6 @@ parameters:
count: 1
path: src/Entity/Activity.php
-
message: "#^Method App\\\\Entity\\\\Activity\\:\\:removeTeam\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Activity.php
-
message: "#^Parameter \\#1 \\$name of method App\\\\Entity\\\\Activity\\:\\:getMetaField\\(\\) expects string, string\\|null given\\.$#"
count: 1
@@ -1651,36 +1621,11 @@ parameters:
count: 1
path: src/Entity/Bookmark.php
-
message: "#^Method App\\\\Entity\\\\CommentInterface\\:\\:setCreatedAt\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/CommentInterface.php
-
message: "#^Method App\\\\Entity\\\\CommentInterface\\:\\:setCreatedBy\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/CommentInterface.php
-
message: "#^Method App\\\\Entity\\\\CommentInterface\\:\\:setMessage\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/CommentInterface.php
-
message: "#^Method App\\\\Entity\\\\CommentInterface\\:\\:setPinned\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/CommentInterface.php
-
message: "#^Method App\\\\Entity\\\\Customer\\:\\:__toString\\(\\) should return string but returns string\\|null\\.$#"
count: 1
path: src/Entity/Customer.php
-
message: "#^Method App\\\\Entity\\\\Customer\\:\\:addTeam\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Customer.php
-
message: "#^Method App\\\\Entity\\\\Customer\\:\\:getMetaFields\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
count: 1
@@ -1691,11 +1636,6 @@ parameters:
count: 1
path: src/Entity/Customer.php
-
message: "#^Method App\\\\Entity\\\\Customer\\:\\:removeTeam\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Customer.php
-
message: "#^Parameter \\#1 \\$name of method App\\\\Entity\\\\Customer\\:\\:getMetaField\\(\\) expects string, string\\|null given\\.$#"
count: 1
@@ -1766,11 +1706,6 @@ parameters:
count: 1
path: src/Entity/Project.php
-
message: "#^Method App\\\\Entity\\\\Project\\:\\:addTeam\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Project.php
-
message: "#^Method App\\\\Entity\\\\Project\\:\\:getMetaFields\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
count: 1
@@ -1781,16 +1716,6 @@ parameters:
count: 1
path: src/Entity/Project.php
-
message: "#^Method App\\\\Entity\\\\Project\\:\\:localizeDates\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Project.php
-
message: "#^Method App\\\\Entity\\\\Project\\:\\:removeTeam\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Project.php
-
message: "#^Parameter \\#1 \\$name of method App\\\\Entity\\\\Project\\:\\:getMetaField\\(\\) expects string, string\\|null given\\.$#"
count: 1
@@ -1926,31 +1851,11 @@ parameters:
count: 1
path: src/Entity/User.php
-
message: "#^Cannot call method hasUser\\(\\) on App\\\\Entity\\\\Team\\|null\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Cannot call method removeMember\\(\\) on App\\\\Entity\\\\Team\\|null\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:__toString\\(\\) should return string but returns string\\|null\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:__unserialize\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:addRole\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:getFirstDayOfWeek\\(\\) should return string but returns bool\\|float\\|int\\|string\\|null\\.$#"
count: 1
@@ -1966,11 +1871,6 @@ parameters:
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:getName\\(\\) should return string but returns string\\|null\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:getPreferences\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
count: 1
@@ -2011,11 +1911,6 @@ parameters:
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:setLanguage\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:setPassword\\(\\) has parameter \\$password with no type specified\\.$#"
count: 1
@@ -2026,51 +1921,16 @@ parameters:
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:setPreferenceValue\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:setRoles\\(\\) has parameter \\$roles with no value type specified in iterable type array\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Method App\\\\Entity\\\\User\\:\\:setTimezone\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Parameter \\#1 \\$haystack of function mb_strstr expects string, string\\|null given\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Parameter \\#1 \\$secret of class Scheb\\\\TwoFactorBundle\\\\Model\\\\Totp\\\\TotpConfiguration constructor expects string, string\\|null given\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Parameter \\#1 \\$string of function mb_strlen expects string, string\\|null given\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Parameter \\#1 \\$string of function mb_substr expects string, string\\|null given\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Parameter \\#2 \\$string of function explode expects string, string\\|null given\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#"
count: 1
path: src/Entity/User.php
-
message: "#^Property App\\\\Entity\\\\User\\:\\:\\$memberships with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#"
count: 1
@@ -2463,7 +2323,7 @@ parameters:
-
message: "#^Parameter \\#1 \\$filename of method App\\\\Export\\\\ExportFilename\\:\\:convert\\(\\) expects string, string\\|null given\\.$#"
count: 2
count: 1
path: src/Export/ExportFilename.php
-
@@ -6720,93 +6580,3 @@ parameters:
message: "#^Method App\\\\Form\\\\MultiUpdate\\\\MultiUpdateTableDTO\\:\\:setEntities\\(\\) has parameter \\$entities with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection but does not specify its types\\: TKey, T$#"
count: 1
path: src/Form/MultiUpdate/MultiUpdateTableDTO.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFilter constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\MarkdownExtension', 'commentContent'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFilter constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\MarkdownExtension', 'commentOneLiner'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFilter constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\MarkdownExtension', 'markdownToHtml'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFilter constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\MarkdownExtension', 'timesheetContent'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFilter constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\ThemeExtension', 'colorize'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFilter constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'KevinPapst\\\\\\\\TablerBundle\\\\\\\\Twig\\\\\\\\RuntimeExtension', 'icon'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\EncoreExtension', 'getEncoreEntryCssSo…'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\QrCodeExtension', 'qrCodeDataUriFuncti…'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\ThemeExtension', 'actions'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\ThemeExtension', 'generateTitle'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\ThemeExtension', 'getJavascriptTransl…'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\ThemeExtension', 'getProgressbarClass'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\ThemeExtension', 'getTimePresets'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\ThemeExtension', 'trigger'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\TimesheetExtension', 'activeEntries'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\TimesheetExtension', 'favoriteEntries'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'App\\\\\\\\Twig\\\\\\\\Runtime\\\\\\\\WidgetExtension', 'renderWidget'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php
-
message: "#^Parameter \\#2 \\$callable of class Twig\\\\TwigFunction constructor expects array\\<class\\-string, string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|null, array\\{'KevinPapst\\\\\\\\TablerBundle\\\\\\\\Twig\\\\\\\\RuntimeExtension', 'createIcon'\\} given\\.$#"
count: 1
path: src/Twig/RuntimeExtensions.php

File diff suppressed because one or more lines are too long

View File

@@ -45,7 +45,7 @@
"calendar": {
"js": [
"/build/runtime.f0079159.js",
"/build/calendar.ea514f59.js"
"/build/calendar.3f4f78fe.js"
],
"css": [
"/build/calendar.bb473428.css"
@@ -72,7 +72,7 @@
"/build/invoice-pdf.ce9aace0.js": "sha384-mtZkXrpsXFMX2YvmmTlCm91QgsTlZLzLJnDAFcTuBDtDZ7TNkqLYkfCeLzFLVfwb",
"/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7",
"/build/chart.f5becfac.js": "sha384-GSqETm8wULiVXyizvwRompfwu63r/C0Qd/AvrHDE4cqAKiIGCssb3QyBtGu1WN+W",
"/build/calendar.ea514f59.js": "sha384-s162N0JRCI0twVtDROON8kABO+riWgiXM7O6TA175IOkAh94twcLqKoAfycd2BrA",
"/build/calendar.3f4f78fe.js": "sha384-RD8enOf0Foe7IhSOGnv2DCes4QUCvLjlONH0HFq3RK9l/ozrFZakTX6SWwKOoEda",
"/build/calendar.bb473428.css": "sha384-900W9o8666Qpw21rLP7hn5Ql8tWd40k0IcE3QpFm7E7V6bKva7xQgGlyzVuuh4GK",
"/build/dashboard.6774a712.js": "sha384-lBwkNqUPv+IBbznagiFakY2BOIueq/Bg89wHO2XeM9YAZ51rPkrvdMd+XmFupHsM",
"/build/dashboard.18f5a8b7.css": "sha384-PBD8ftb2yBoSjRe2sN5Xb4dEz045kOEUfcIufdSis+tWoWdAx5j4Yic+F/6CYbrP"

View File

@@ -9,7 +9,7 @@
"build/invoice-pdf.js": "/build/invoice-pdf.ce9aace0.js",
"build/chart.js": "/build/chart.f5becfac.js",
"build/calendar.css": "/build/calendar.bb473428.css",
"build/calendar.js": "/build/calendar.ea514f59.js",
"build/calendar.js": "/build/calendar.3f4f78fe.js",
"build/dashboard.css": "/build/dashboard.18f5a8b7.css",
"build/dashboard.js": "/build/dashboard.6774a712.js",
"build/runtime.js": "/build/runtime.f0079159.js"

View File

@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '2.0.33';
public const VERSION = '2.0.34';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 20033;
public const VERSION_ID = 20034;
/**
* The software name
*/

View File

@@ -68,6 +68,7 @@ final class ContractController extends AbstractController
$page = new PageSetup('work_times');
$page->setHelp('contract.html');
$page->setActionName('contract');
$page->setActionPayload(['profile' => $profile, 'year' => $yearDate]);
$page->setPaginationForm($form);
// additional boxes by plugins

View File

@@ -16,6 +16,7 @@ use App\Reporting\ProjectDateRange\ProjectDateRangeForm;
use App\Reporting\ProjectDateRange\ProjectDateRangeQuery;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -24,7 +25,7 @@ final class ProjectDateRangeController extends AbstractController
#[Route(path: '/reporting/project_daterange', name: 'report_project_daterange', methods: ['GET', 'POST'])]
#[IsGranted('report:project')]
#[IsGranted(new Expression("is_granted('budget_any', 'project')"))]
public function __invoke(Request $request, ProjectStatisticService $service)
public function __invoke(Request $request, ProjectStatisticService $service): Response
{
$dateFactory = $this->getDateTimeFactory();
$user = $this->getUser();

View File

@@ -16,6 +16,7 @@ use App\Reporting\ProjectDetails\ProjectDetailsQuery;
use App\Utils\PageSetup;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -24,7 +25,7 @@ final class ProjectDetailsController extends AbstractController
#[Route(path: '/reporting/project_details', name: 'report_project_details', methods: ['GET'])]
#[IsGranted('report:project')]
#[IsGranted(new Expression("is_granted('details', 'project')"))]
public function __invoke(Request $request, ProjectStatisticService $service)
public function __invoke(Request $request, ProjectStatisticService $service): Response
{
$dateFactory = $this->getDateTimeFactory();
$user = $this->getUser();

View File

@@ -15,6 +15,7 @@ use App\Reporting\ProjectInactive\ProjectInactiveForm;
use App\Reporting\ProjectInactive\ProjectInactiveQuery;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -23,7 +24,7 @@ final class ProjectInactiveController extends AbstractController
#[Route(path: '/reporting/project_inactive', name: 'report_project_inactive', methods: ['GET', 'POST'])]
#[IsGranted('report:project')]
#[IsGranted(new Expression("is_granted('budget_any', 'project')"))]
public function __invoke(Request $request, ProjectStatisticService $service)
public function __invoke(Request $request, ProjectStatisticService $service): Response
{
$dateFactory = $this->getDateTimeFactory();
$user = $this->getUser();

View File

@@ -15,6 +15,7 @@ use App\Reporting\ProjectView\ProjectViewForm;
use App\Reporting\ProjectView\ProjectViewQuery;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -23,7 +24,7 @@ final class ProjectViewController extends AbstractController
#[Route(path: '/reporting/project_view', name: 'report_project_view', methods: ['GET', 'POST'])]
#[IsGranted('report:project')]
#[IsGranted(new Expression("is_granted('budget_any', 'project')"))]
public function __invoke(Request $request, ProjectStatisticService $service)
public function __invoke(Request $request, ProjectStatisticService $service): Response
{
$dateFactory = $this->getDateTimeFactory();
$user = $this->getUser();

View File

@@ -232,7 +232,7 @@ class Activity implements EntityWithMetaFields, EntityWithBudget
return $this;
}
public function addTeam(Team $team)
public function addTeam(Team $team): void
{
if ($this->teams->contains($team)) {
return;
@@ -242,7 +242,7 @@ class Activity implements EntityWithMetaFields, EntityWithBudget
$team->addActivity($this);
}
public function removeTeam(Team $team)
public function removeTeam(Team $team): void
{
if (!$this->teams->contains($team)) {
return;

View File

@@ -15,17 +15,17 @@ interface CommentInterface
public function getMessage(): ?string;
public function setMessage(string $message);
public function setMessage(string $message): void;
public function getCreatedBy(): ?User;
public function setCreatedBy(User $createdBy);
public function setCreatedBy(User $createdBy): void;
public function getCreatedAt(): ?\DateTime;
public function setCreatedAt(\DateTime $createdAt);
public function setCreatedAt(\DateTime $createdAt): void;
public function isPinned(): bool;
public function setPinned(bool $pinned);
public function setPinned(bool $pinned): void;
}

View File

@@ -440,7 +440,7 @@ class Customer implements EntityWithMetaFields, EntityWithBudget
return $this;
}
public function addTeam(Team $team)
public function addTeam(Team $team): void
{
if ($this->teams->contains($team)) {
return;
@@ -450,7 +450,7 @@ class Customer implements EntityWithMetaFields, EntityWithBudget
$team->addCustomer($this);
}
public function removeTeam(Team $team)
public function removeTeam(Team $team): void
{
if (!$this->teams->contains($team)) {
return;

View File

@@ -253,7 +253,7 @@ class Project implements EntityWithMetaFields, EntityWithBudget
* Make sure begin and end date have the correct timezone.
* This will be called once for each item after being loaded from the database.
*/
protected function localizeDates()
protected function localizeDates(): void
{
if ($this->localized) {
return;
@@ -392,7 +392,7 @@ class Project implements EntityWithMetaFields, EntityWithBudget
return $this;
}
public function addTeam(Team $team)
public function addTeam(Team $team): void
{
if ($this->teams->contains($team)) {
return;
@@ -402,7 +402,7 @@ class Project implements EntityWithMetaFields, EntityWithBudget
$team->addProject($this);
}
public function removeTeam(Team $team)
public function removeTeam(Team $team): void
{
if (!$this->teams->contains($team)) {
return;

View File

@@ -371,7 +371,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
* @param string $name
* @param bool|int|string|float|null $value
*/
public function setPreferenceValue(string $name, $value = null)
public function setPreferenceValue(string $name, $value = null): void
{
$pref = $this->getPreference($name);
@@ -421,7 +421,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->getLocale();
}
public function setLanguage(?string $language)
public function setLanguage(?string $language): void
{
if ($language === null) {
$language = User::DEFAULT_LANGUAGE;
@@ -449,7 +449,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return (string) $this->getPreferenceValue(UserPreference::SKIN, 'default', false);
}
public function setTimezone(?string $timezone)
public function setTimezone(?string $timezone): void
{
if ($timezone === null) {
$timezone = date_default_timezone_get();
@@ -536,7 +536,9 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
}
$this->memberships->removeElement($member);
$member->getTeam()->removeMember($member);
if ($member->getTeam() !== null) {
$member->getTeam()->removeMember($member);
}
$member->setUser(null);
$member->setTeam(null);
}
@@ -590,7 +592,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
public function hasTeamMember(User $user): bool
{
foreach ($this->memberships as $membership) {
if ($membership->getTeam()->hasUser($user)) {
if ($membership->getTeam() !== null && $membership->getTeam()->hasUser($user)) {
return true;
}
}
@@ -598,6 +600,34 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return false;
}
/**
* Use this function to check if the current user can read data from the given user.
*/
public function canSeeUser(User $user): bool
{
if ($user->getId() === $this->getId()) {
return true;
}
if ($this->canSeeAllData()) {
return true;
}
if (!$user->isEnabled()) {
return false;
}
if (!$this->isSystemAccount() && $user->isSystemAccount()) {
return false;
}
if ($this->isTeamleadOfUser($user)) {
return true;
}
return false;
}
/**
* List of all teams, this user is part of
*
@@ -673,6 +703,17 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return false;
}
public function isTeamleadOfUser(User $user): bool
{
foreach ($this->memberships as $membership) {
if ($membership->isTeamlead() && $membership->getTeam() !== null && $membership->getTeam()->hasUser($user)) {
return true;
}
}
return false;
}
public function canSeeAllData(): bool
{
return $this->isSuperAdmin() || true === $this->isAllowedToSeeAllData;
@@ -708,7 +749,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->hasRole(static::ROLE_ADMIN);
}
public function getDisplayName(): ?string
public function getDisplayName(): string
{
if (!empty($this->getAlias())) {
return $this->getAlias();
@@ -744,18 +785,16 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->auth === null || $this->auth === self::AUTH_INTERNAL;
}
public function addRole(string $role)
public function addRole(string $role): void
{
$role = strtoupper($role);
if ($role === static::DEFAULT_ROLE) {
return $this;
return;
}
if (!\in_array($role, $this->roles, true)) {
$this->roles[] = $role;
}
return $this;
}
public function eraseCredentials(): void

View File

@@ -16,6 +16,8 @@ use App\Repository\UserRepository;
use App\Utils\Color;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -65,10 +67,6 @@ final class UserType extends AbstractType
'type' => 'integer',
'description' => 'User ID',
],
'attr' => [
'data-select-attributes' => 'color,title,username,initials,accountNumber,alias',
'data-renderer' => 'color',
],
]);
$resolver->setDefault('query_builder', function (Options $options) {
@@ -93,6 +91,14 @@ final class UserType extends AbstractType
});
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['attr'] = array_merge($view->vars['attr'], [
'data-select-attributes' => 'color,title,username,initials,accountNumber,alias',
'data-renderer' => 'color',
]);
}
public function getParent(): string
{
return EntityType::class;

View File

@@ -92,7 +92,7 @@ final class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
'entry.user_name' => $user->getUserIdentifier(),
'entry.user_title' => $user->getTitle() ?? '',
'entry.user_alias' => $user->getAlias() ?? '',
'entry.user_display' => $user->getDisplayName() ?? '',
'entry.user_display' => $user->getDisplayName(),
]);
foreach ($user->getVisiblePreferences() as $pref) {

View File

@@ -27,7 +27,7 @@ final class InvoiceModelUserHydrator implements InvoiceModelHydrator
'user.email' => $user->getEmail(),
'user.title' => $user->getTitle() ?? '',
'user.alias' => $user->getAlias() ?? '',
'user.display' => $user->getDisplayName() ?? '',
'user.display' => $user->getDisplayName(),
];
foreach ($user->getPreferences() as $metaField) {

View File

@@ -19,13 +19,13 @@ final class Pagination extends Pagerfanta
{
parent::__construct($adapter);
if ($query === null || !$query->isApiCall()) {
$this->setNormalizeOutOfRangePages(true);
}
if ($query !== null) {
$this->setMaxPerPage($query->getPageSize());
$this->setCurrentPage($query->getPage());
}
if ($query === null || !$query->isApiCall()) {
$this->setNormalizeOutOfRangePages(true);
}
}
}

View File

@@ -22,6 +22,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
final class UserVoter extends Voter
{
private const ALLOWED_ATTRIBUTES = [
'access_user',
'view',
'edit',
'roles',
@@ -62,10 +63,18 @@ final class UserVoter extends Voter
return false;
}
if (!($subject instanceof User)) {
return false;
}
if ($attribute === 'contract') {
return $this->permissionManager->hasRolePermission($user, 'contract_other_profile');
}
if ($attribute === 'access_user') {
return $user->canSeeUser($subject);
}
if ($attribute === 'view_team_member') {
if ($subject->getId() !== $user->getId()) {
return false;

View File

@@ -10,7 +10,10 @@
{% embed '@theme/embeds/card.html.twig' %}
{% block box_body %}
{{ form_start(form) }}
{{ form_widget(form) }}
{% if form.user is defined %}
{{ form_row(form.user) }}
{% endif %}
{{ form_rest(form) }}
{{ form_end(form) }}
{% endblock %}
{% endembed %}
@@ -132,7 +135,7 @@
{
id: 'kimaiUserTimeSource',
type: 'timesheet',
url: '{{ path('get_timesheets') }}?user={{ user.id }}&size=1000&full=true&begin={from}&end={to}',
url: '{{ path('get_timesheets', {'user': user.id, 'size': 1000, 'full': 'true', 'begin': '__FROM__', 'end': '__TO__'})|raw }}',
options: {
color: '{{ config('theme.calendar.background_color') }}',
},
@@ -157,7 +160,7 @@
{
id: '{{ source.id }}{{ loop.index }}',
type: '{{ source.typeName }}',
url: '{{ source.uri }}',
url: '{{ source.uri|raw }}',
options: {...{{ source.options|json_encode|raw }}, ...{
color: '{{ source.color }}',
textColor: '{{ source.color|font_contrast }}',

View File

@@ -253,7 +253,7 @@
</table>
</div>
</div>
{% if (route is not empty and entries is not null) or multi_update_form is not null%}
{% 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}}) }}

View File

@@ -246,7 +246,7 @@
{% macro alert(type, description, title, icon, dismissible) %}
{% from '@theme/components/alert.html.twig' import alert %}
{{ alert({type: type, description: description|trans, title: title|trans, icon: icon, dismissible: dismissible}) }}
{{ alert({type: type, description: description|trans, title: title|trans, icon: icon, dismissible: dismissible, important: true}) }}
{% endmacro %}
{% macro callout(type, description, title, icon) %}

View File

@@ -287,7 +287,7 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
foreach ($messages as $i => $message) {
self::assertEquals($message, $data[$fieldName]['errors'][$i]);
}
if (\array_key_exists('errors', $data[$fieldName]) && \count($data[$fieldName]['errors']) > 0) {
if (\array_key_exists('errors', (array) $data[$fieldName]) && \count($data[$fieldName]['errors']) > 0) {
$foundErrors[$fieldName] = \count($data[$fieldName]['errors']);
}
}