Release 2.0.19 (#4022)

* prevent invoices with exceeding filename
* fixed invalid LDAP log level
* support locale switching in action events
This commit is contained in:
Kevin Papst
2023-05-12 14:45:51 +02:00
committed by GitHub
parent 1099e76244
commit 4b2a3669e6
12 changed files with 77 additions and 61 deletions

View File

@@ -25,8 +25,10 @@ and so much more.
There are two versions of Kimai existing: 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) — the current stable release (PHP 8.1 only)
- [Version 2](https://github.com/kimai/kimai) — stable and "almost released" (waiting for some major plugins, which are not yet migrated) - [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 ### 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. 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. 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 ## Contributing

View File

@@ -2266,11 +2266,6 @@ parameters:
count: 9 count: 9
path: src/Event/PageActionsEvent.php 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\\.$#" message: "#^Property App\\\\Event\\\\PermissionSectionsEvent\\:\\:\\$sections type has no value type specified in iterable type array\\.$#"
count: 1 count: 1
@@ -4531,11 +4526,6 @@ parameters:
count: 1 count: 1
path: src/Invoice/Renderer/PdfRenderer.php path: src/Invoice/Renderer/PdfRenderer.php
-
message: "#^Binary operation \"\\.\" between string and non\\-empty\\-list\\<string\\>\\|string results in an error\\.$#"
count: 2
path: src/Invoice/ServiceInvoice.php
- -
message: "#^Cannot call method getCustomer\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#" message: "#^Cannot call method getCustomer\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
count: 1 count: 1
@@ -4581,11 +4571,6 @@ parameters:
count: 1 count: 1
path: src/Invoice/ServiceInvoice.php path: src/Invoice/ServiceInvoice.php
-
message: "#^Method App\\\\Invoice\\\\ServiceInvoice\\:\\:saveGeneratedInvoice\\(\\) should return string but returns array\\<int, string\\>\\|string\\.$#"
count: 1
path: src/Invoice/ServiceInvoice.php
- -
message: "#^Parameter \\#1 \\$key of function array_key_exists expects int\\|string, int\\|null given\\.$#" message: "#^Parameter \\#1 \\$key of function array_key_exists expects int\\|string, int\\|null given\\.$#"
count: 1 count: 1
@@ -4601,11 +4586,6 @@ parameters:
count: 1 count: 1
path: src/Invoice/ServiceInvoice.php path: src/Invoice/ServiceInvoice.php
-
message: "#^Parameter \\#2 \\$name of method Symfony\\\\Component\\\\HttpFoundation\\\\File\\\\File\\:\\:move\\(\\) expects string\\|null, array\\<int, string\\>\\|string given\\.$#"
count: 1
path: src/Invoice/ServiceInvoice.php
- -
message: "#^Parameter \\#2 \\$string of function explode expects string, string\\|null given\\.$#" message: "#^Parameter \\#2 \\$string of function explode expects string, string\\|null given\\.$#"
count: 2 count: 2
@@ -4621,11 +4601,6 @@ parameters:
count: 1 count: 1
path: src/Invoice/ServiceInvoice.php path: src/Invoice/ServiceInvoice.php
-
message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, array\\<int, string\\>\\|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\\.$#" message: "#^Property App\\\\Invoice\\\\ServiceInvoice\\:\\:\\$invoiceItemRepositories type has no value type specified in iterable type array\\.$#"
count: 1 count: 1

View File

@@ -46,6 +46,7 @@ final class ActionsController extends BaseApiController
*/ */
private function convertEvent(PageActionsEvent $event, string $locale): array private function convertEvent(PageActionsEvent $event, string $locale): array
{ {
$event->setLocale($locale);
$this->dispatcher->dispatch($event, $event->getEventName()); $this->dispatcher->dispatch($event, $event->getEventName());
$translator = $this->translator; $translator = $this->translator;

View File

@@ -17,11 +17,11 @@ class Constants
/** /**
* The current release version * The current release version
*/ */
public const VERSION = '2.0.18'; public const VERSION = '2.0.19';
/** /**
* The current release: major * 10000 + minor * 100 + patch * The current release: major * 10000 + minor * 100 + patch
*/ */
public const VERSION_ID = 20018; public const VERSION_ID = 20019;
/** /**
* The software name * The software name
*/ */

View File

@@ -18,9 +18,10 @@ use App\Entity\User;
*/ */
class PageActionsEvent extends ThemeEvent class PageActionsEvent extends ThemeEvent
{ {
private $action; private string $action;
private $view; private string $view;
private $divider = 0; private int $divider = 0;
private ?string $locale = null;
public function __construct(User $user, array $payload, string $action, string $view) public function __construct(User $user, array $payload, string $action, string $view)
{ {
@@ -195,4 +196,14 @@ class PageActionsEvent extends ThemeEvent
return \count($this->payload['actions']); return \count($this->payload['actions']);
} }
public function getLocale(): ?string
{
return $this->locale;
}
public function setLocale(?string $locale): void
{
$this->locale = $locale;
}
} }

View File

@@ -52,8 +52,13 @@ class ThemeEvent extends Event
return $this->payload; return $this->payload;
} }
/**
* @deprecated since 2.0.19, will be removed with 2.1
*/
public function setPayload(mixed $payload): void public function setPayload(mixed $payload): void
{ {
@trigger_error('ThemeEvent::setPayload() is deprecated, use AbstractActionsSubscriber instead.', E_USER_DEPRECATED);
$this->payload = $payload; $this->payload = $payload;
} }
} }

View File

@@ -19,6 +19,8 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
*/ */
abstract class AbstractActionsSubscriber implements EventSubscriberInterface abstract class AbstractActionsSubscriber implements EventSubscriberInterface
{ {
private ?string $locale = null;
public function __construct(private AuthorizationCheckerInterface $auth, private UrlGeneratorInterface $urlGenerator) 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 protected function path(string $route, array $parameters = []): string
{ {
if ($this->locale !== null) {
$parameters['_locale'] = $this->locale;
}
return $this->urlGenerator->generate($route, $parameters); return $this->urlGenerator->generate($route, $parameters);
} }
public static function getSubscribedEvents(): array public static function getSubscribedEvents(): array
{ {
return [ 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 public static function getActionName(): string
{ {
throw new \Exception('You need to overwrite getActionName() or getSubscribedEvents() in ' . static::class); throw new \Exception('You need to overwrite getActionName() or getSubscribedEvents() in ' . static::class);

View File

@@ -197,17 +197,23 @@ final class ServiceInvoice
if (stripos($part, 'filename=') === false) { if (stripos($part, 'filename=') === false) {
continue; continue;
} }
$filename = explode('filename=', $part); $tmp = explode('filename=', $part);
if (\count($filename) > 1) { if (\count($tmp) > 1) {
$filename = $filename[1]; $filename = $tmp[1];
} }
} }
} else { } else {
$disposition = $event->getResponse()->headers->get('Content-Type'); $disposition = $event->getResponse()->headers->get('Content-Type');
$parts = explode(';', $disposition); $parts = explode(';', $disposition);
$parts = explode('/', $parts[0]); $parts = explode('/', $parts[0]);
if (\count($parts) > 1) {
$filename .= '.' . $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)) { if (is_file($invoiceDirectory . $filename)) {
throw new \Exception(sprintf('Invoice "%s" already exists', $filename)); throw new \Exception(sprintf('Invoice "%s" already exists', $filename));

View File

@@ -118,6 +118,6 @@ class LdapDriver
if (null === $this->logger) { if (null === $this->logger) {
return; return;
} }
$this->logger->error($message, $context); $this->logger->debug($message, $context);
} }
} }

View File

@@ -33,6 +33,7 @@ class PageActionsEventTest extends TestCase
$this->assertSame($user, $sut->getUser()); $this->assertSame($user, $sut->getUser());
$this->assertEquals([], $sut->getActions()); $this->assertEquals([], $sut->getActions());
$this->assertEquals(['actions' => [], 'view' => 'bar'], $sut->getPayload()); $this->assertEquals(['actions' => [], 'view' => 'bar'], $sut->getPayload());
$this->assertNull($sut->getLocale());
$sut = new PageActionsEvent($user, ['hello' => 'world'], 'foo', 'bar'); $sut = new PageActionsEvent($user, ['hello' => 'world'], 'foo', 'bar');
$this->assertSame($user, $sut->getUser()); $this->assertSame($user, $sut->getUser());
@@ -66,6 +67,12 @@ class PageActionsEventTest extends TestCase
$this->assertEquals(['foo' => ['url' => 'bar']], $sut->getActions()); $this->assertEquals(['foo' => ['url' => 'bar']], $sut->getActions());
$sut->replaceAction('foo', ['url' => 'xyz']); $sut->replaceAction('foo', ['url' => 'xyz']);
$this->assertEquals(['foo' => ['url' => 'xyz']], $sut->getActions()); $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() public function testSubmenu()

View File

@@ -18,7 +18,7 @@ use PHPUnit\Framework\TestCase;
*/ */
class ThemeEventTest extends TestCase class ThemeEventTest extends TestCase
{ {
public function testEmpty() public function testEmpty(): void
{ {
$sut = new ThemeEvent(); $sut = new ThemeEvent();
$this->assertNull($sut->getUser()); $this->assertNull($sut->getUser());
@@ -26,7 +26,7 @@ class ThemeEventTest extends TestCase
$this->assertEquals('', $sut->getContent()); $this->assertEquals('', $sut->getContent());
} }
public function testDefaultValues() public function testDefaultValues(): void
{ {
$user = new User(); $user = new User();
$user->setAlias('foo'); $user->setAlias('foo');
@@ -36,17 +36,13 @@ class ThemeEventTest extends TestCase
$this->assertSame($user, $sut->getUser()); $this->assertSame($user, $sut->getUser());
} }
public function testGetterAndSetter() public function testGetterAndSetter(): void
{ {
$user = new User(); $user = new User();
$user->setAlias('foo'); $user->setAlias('foo');
$payload = [null, '', 'test', new \stdClass()]; $payload = [null, '', 'test', new \stdClass()];
$sut = new ThemeEvent($user);
$sut->setPayload($payload);
$this->assertEquals($payload, $sut->getPayload());
$sut = new ThemeEvent($user, $payload); $sut = new ThemeEvent($user, $payload);
$this->assertEquals($payload, $sut->getPayload()); $this->assertEquals($payload, $sut->getPayload());
@@ -57,4 +53,19 @@ class ThemeEventTest extends TestCase
$sut->addContent('<script>'); $sut->addContent('<script>');
$this->assertEquals('foo<script>', $sut->getContent()); $this->assertEquals('foo<script>', $sut->getContent());
} }
/**
* @group legacy
*/
public function testDeprecatedStuff(): void
{
$user = new User();
$user->setAlias('foo');
$payload = [null, '', 'test', new \stdClass()];
$sut = new ThemeEvent($user);
$sut->setPayload($payload);
$this->assertEquals($payload, $sut->getPayload());
}
} }

View File

@@ -5187,21 +5187,6 @@ parameters:
count: 1 count: 1
path: Event/SystemConfigurationEventTest.php path: Event/SystemConfigurationEventTest.php
-
message: "#^Method App\\\\Tests\\\\Event\\\\ThemeEventTest\\:\\:testDefaultValues\\(\\) has no return type specified\\.$#"
count: 1
path: Event/ThemeEventTest.php
-
message: "#^Method App\\\\Tests\\\\Event\\\\ThemeEventTest\\:\\:testEmpty\\(\\) has no return type specified\\.$#"
count: 1
path: Event/ThemeEventTest.php
-
message: "#^Method App\\\\Tests\\\\Event\\\\ThemeEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#"
count: 1
path: Event/ThemeEventTest.php
- -
message: "#^Method App\\\\Tests\\\\Event\\\\ThemeJavascriptTranslationsEventTest\\:\\:testDefaultValues\\(\\) has no return type specified\\.$#" message: "#^Method App\\\\Tests\\\\Event\\\\ThemeJavascriptTranslationsEventTest\\:\\:testDefaultValues\\(\\) has no return type specified\\.$#"
count: 1 count: 1