diff --git a/README.md b/README.md index fbd11500..bdff6aea 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,10 @@ and so much more. There are two versions of Kimai existing: -- [Version 1](https://github.com/kimai/kimai/tree/1.x) — compatible with PHP 7.4, which is in maintenance mode since 2023 -- [Version 2](https://github.com/kimai/kimai) — stable and "almost released" (waiting for some major plugins, which are not yet migrated) +- [Version 2](https://github.com/kimai/kimai) — the current stable release (PHP 8.1 only) +- [Version 1](https://github.com/kimai/kimai/tree/1.x) — the "old" version, in maintenance mode since Jan. 2023 (PHP 7.4, PHP 8.1) + +If you start fresh, do **not** use Version 1, it won't receive any more updates. ### Links @@ -72,7 +74,7 @@ It is open for changes and input from the community, your [ideas and questions]( Release versions will be created on a regular basis, every couple of weeks latest. Every code change, whether it's a new feature or a bugfix, will be done on the `main` branch. -For the time being and until 2.0 is widely adopted, the [1.x branch](https://github.com/kimai/kimai/tree/1.x) will receive bug fixes. +Until 2.0 is widely adopted, the [1.x branch](https://github.com/kimai/kimai/tree/1.x) will receive (critical) bug and security fixes. ## Contributing diff --git a/phpstan.neon b/phpstan.neon index 18e7617c..9b85fd4f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2266,11 +2266,6 @@ parameters: count: 9 path: src/Event/PageActionsEvent.php - - - message: "#^Property App\\\\Event\\\\PageActionsEvent\\:\\:\\$divider has no type specified\\.$#" - count: 1 - path: src/Event/PageActionsEvent.php - - message: "#^Property App\\\\Event\\\\PermissionSectionsEvent\\:\\:\\$sections type has no value type specified in iterable type array\\.$#" count: 1 @@ -4531,11 +4526,6 @@ parameters: count: 1 path: src/Invoice/Renderer/PdfRenderer.php - - - message: "#^Binary operation \"\\.\" between string and non\\-empty\\-list\\\\|string results in an error\\.$#" - count: 2 - path: src/Invoice/ServiceInvoice.php - - message: "#^Cannot call method getCustomer\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#" count: 1 @@ -4581,11 +4571,6 @@ parameters: count: 1 path: src/Invoice/ServiceInvoice.php - - - message: "#^Method App\\\\Invoice\\\\ServiceInvoice\\:\\:saveGeneratedInvoice\\(\\) should return string but returns array\\\\|string\\.$#" - count: 1 - path: src/Invoice/ServiceInvoice.php - - message: "#^Parameter \\#1 \\$key of function array_key_exists expects int\\|string, int\\|null given\\.$#" count: 1 @@ -4601,11 +4586,6 @@ parameters: count: 1 path: src/Invoice/ServiceInvoice.php - - - message: "#^Parameter \\#2 \\$name of method Symfony\\\\Component\\\\HttpFoundation\\\\File\\\\File\\:\\:move\\(\\) expects string\\|null, array\\\\|string given\\.$#" - count: 1 - path: src/Invoice/ServiceInvoice.php - - message: "#^Parameter \\#2 \\$string of function explode expects string, string\\|null given\\.$#" count: 2 @@ -4621,11 +4601,6 @@ parameters: count: 1 path: src/Invoice/ServiceInvoice.php - - - message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, array\\\\|string given\\.$#" - count: 1 - path: src/Invoice/ServiceInvoice.php - - message: "#^Property App\\\\Invoice\\\\ServiceInvoice\\:\\:\\$invoiceItemRepositories type has no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/API/ActionsController.php b/src/API/ActionsController.php index 1466ce49..e7f6a4dc 100644 --- a/src/API/ActionsController.php +++ b/src/API/ActionsController.php @@ -46,6 +46,7 @@ final class ActionsController extends BaseApiController */ private function convertEvent(PageActionsEvent $event, string $locale): array { + $event->setLocale($locale); $this->dispatcher->dispatch($event, $event->getEventName()); $translator = $this->translator; diff --git a/src/Constants.php b/src/Constants.php index ea739ecd..9e2a8085 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -17,11 +17,11 @@ class Constants /** * The current release version */ - public const VERSION = '2.0.18'; + public const VERSION = '2.0.19'; /** * The current release: major * 10000 + minor * 100 + patch */ - public const VERSION_ID = 20018; + public const VERSION_ID = 20019; /** * The software name */ diff --git a/src/Event/PageActionsEvent.php b/src/Event/PageActionsEvent.php index e0da9d4c..efbcefe9 100644 --- a/src/Event/PageActionsEvent.php +++ b/src/Event/PageActionsEvent.php @@ -18,9 +18,10 @@ use App\Entity\User; */ class PageActionsEvent extends ThemeEvent { - private $action; - private $view; - private $divider = 0; + private string $action; + private string $view; + private int $divider = 0; + private ?string $locale = null; public function __construct(User $user, array $payload, string $action, string $view) { @@ -195,4 +196,14 @@ class PageActionsEvent extends ThemeEvent return \count($this->payload['actions']); } + + public function getLocale(): ?string + { + return $this->locale; + } + + public function setLocale(?string $locale): void + { + $this->locale = $locale; + } } diff --git a/src/Event/ThemeEvent.php b/src/Event/ThemeEvent.php index 82f40687..96a8f5c9 100644 --- a/src/Event/ThemeEvent.php +++ b/src/Event/ThemeEvent.php @@ -52,8 +52,13 @@ class ThemeEvent extends Event return $this->payload; } + /** + * @deprecated since 2.0.19, will be removed with 2.1 + */ public function setPayload(mixed $payload): void { + @trigger_error('ThemeEvent::setPayload() is deprecated, use AbstractActionsSubscriber instead.', E_USER_DEPRECATED); + $this->payload = $payload; } } diff --git a/src/EventSubscriber/Actions/AbstractActionsSubscriber.php b/src/EventSubscriber/Actions/AbstractActionsSubscriber.php index 08634ab2..8a4aded7 100644 --- a/src/EventSubscriber/Actions/AbstractActionsSubscriber.php +++ b/src/EventSubscriber/Actions/AbstractActionsSubscriber.php @@ -19,6 +19,8 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; */ abstract class AbstractActionsSubscriber implements EventSubscriberInterface { + private ?string $locale = null; + public function __construct(private AuthorizationCheckerInterface $auth, private UrlGeneratorInterface $urlGenerator) { } @@ -30,16 +32,27 @@ abstract class AbstractActionsSubscriber implements EventSubscriberInterface protected function path(string $route, array $parameters = []): string { + if ($this->locale !== null) { + $parameters['_locale'] = $this->locale; + } + return $this->urlGenerator->generate($route, $parameters); } public static function getSubscribedEvents(): array { return [ - 'actions.' . static::getActionName() => ['onActions', 1000], + 'actions.' . static::getActionName() => ['handleEvent', 1000], ]; } + final public function handleEvent(PageActionsEvent $event): void + { + $this->locale = $event->getLocale(); + + $this->onActions($event); + } + public static function getActionName(): string { throw new \Exception('You need to overwrite getActionName() or getSubscribedEvents() in ' . static::class); diff --git a/src/Invoice/ServiceInvoice.php b/src/Invoice/ServiceInvoice.php index b0f017a6..bef25577 100644 --- a/src/Invoice/ServiceInvoice.php +++ b/src/Invoice/ServiceInvoice.php @@ -197,16 +197,22 @@ final class ServiceInvoice if (stripos($part, 'filename=') === false) { continue; } - $filename = explode('filename=', $part); - if (\count($filename) > 1) { - $filename = $filename[1]; + $tmp = explode('filename=', $part); + if (\count($tmp) > 1) { + $filename = $tmp[1]; } } } else { $disposition = $event->getResponse()->headers->get('Content-Type'); $parts = explode(';', $disposition); $parts = explode('/', $parts[0]); - $filename .= '.' . $parts[1]; + if (\count($parts) > 1) { + $filename .= '.' . $parts[1]; + } + } + + if (mb_strlen($filename) >= 150) { + throw new \Exception(sprintf('Invoice filename "%s" is too long, max. 150 characters allowed', $filename)); } if (is_file($invoiceDirectory . $filename)) { diff --git a/src/Ldap/LdapDriver.php b/src/Ldap/LdapDriver.php index 0c325605..b04eab81 100644 --- a/src/Ldap/LdapDriver.php +++ b/src/Ldap/LdapDriver.php @@ -118,6 +118,6 @@ class LdapDriver if (null === $this->logger) { return; } - $this->logger->error($message, $context); + $this->logger->debug($message, $context); } } diff --git a/tests/Event/PageActionsEventTest.php b/tests/Event/PageActionsEventTest.php index efdfb7fe..94eed7d2 100644 --- a/tests/Event/PageActionsEventTest.php +++ b/tests/Event/PageActionsEventTest.php @@ -33,6 +33,7 @@ class PageActionsEventTest extends TestCase $this->assertSame($user, $sut->getUser()); $this->assertEquals([], $sut->getActions()); $this->assertEquals(['actions' => [], 'view' => 'bar'], $sut->getPayload()); + $this->assertNull($sut->getLocale()); $sut = new PageActionsEvent($user, ['hello' => 'world'], 'foo', 'bar'); $this->assertSame($user, $sut->getUser()); @@ -66,6 +67,12 @@ class PageActionsEventTest extends TestCase $this->assertEquals(['foo' => ['url' => 'bar']], $sut->getActions()); $sut->replaceAction('foo', ['url' => 'xyz']); $this->assertEquals(['foo' => ['url' => 'xyz']], $sut->getActions()); + + $sut->setLocale('de'); + $this->assertEquals('de', $sut->getLocale()); + + $sut->setLocale(null); + $this->assertNull($sut->getLocale()); } public function testSubmenu() diff --git a/tests/Event/ThemeEventTest.php b/tests/Event/ThemeEventTest.php index de0269c8..383f5e8b 100644 --- a/tests/Event/ThemeEventTest.php +++ b/tests/Event/ThemeEventTest.php @@ -18,7 +18,7 @@ use PHPUnit\Framework\TestCase; */ class ThemeEventTest extends TestCase { - public function testEmpty() + public function testEmpty(): void { $sut = new ThemeEvent(); $this->assertNull($sut->getUser()); @@ -26,7 +26,7 @@ class ThemeEventTest extends TestCase $this->assertEquals('', $sut->getContent()); } - public function testDefaultValues() + public function testDefaultValues(): void { $user = new User(); $user->setAlias('foo'); @@ -36,17 +36,13 @@ class ThemeEventTest extends TestCase $this->assertSame($user, $sut->getUser()); } - public function testGetterAndSetter() + public function testGetterAndSetter(): void { $user = new User(); $user->setAlias('foo'); $payload = [null, '', 'test', new \stdClass()]; - $sut = new ThemeEvent($user); - $sut->setPayload($payload); - $this->assertEquals($payload, $sut->getPayload()); - $sut = new ThemeEvent($user, $payload); $this->assertEquals($payload, $sut->getPayload()); @@ -57,4 +53,19 @@ class ThemeEventTest extends TestCase $sut->addContent('