diff --git a/phpstan.neon b/phpstan.neon index 779be9d2..17926fc7 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3032,11 +3032,6 @@ parameters: count: 1 path: src/Invoice/Hydrator/InvoiceModelActivityHydrator.php - - - message: "#^Cannot call method getEnd\\(\\) on App\\\\Repository\\\\Query\\\\InvoiceQuery\\|null\\.$#" - count: 1 - path: src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php - - message: "#^Method App\\\\Invoice\\\\Hydrator\\\\InvoiceModelCustomerHydrator\\:\\:getBudgetValues\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/Configuration/MailConfiguration.php b/src/Configuration/MailConfiguration.php index 2ca93cb5..c8088e08 100644 --- a/src/Configuration/MailConfiguration.php +++ b/src/Configuration/MailConfiguration.php @@ -11,13 +11,13 @@ namespace App\Configuration; final class MailConfiguration { - public function __construct(private string $mailFrom) + public function __construct(private readonly string $mailFrom) { } public function getFromAddress(): ?string { - if (empty($this->mailFrom)) { + if (trim($this->mailFrom) === '') { return null; } diff --git a/src/Constants.php b/src/Constants.php index ffaf3ec8..5b668854 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -17,11 +17,11 @@ class Constants /** * The current release version */ - public const VERSION = '2.26.0'; + public const VERSION = '2.27.0'; /** * The current release: major * 10000 + minor * 100 + patch */ - public const VERSION_ID = 22600; + public const VERSION_ID = 22700; /** * The software name */ diff --git a/src/Customer/CustomerStatisticService.php b/src/Customer/CustomerStatisticService.php index 073e18c0..e2b889e7 100644 --- a/src/Customer/CustomerStatisticService.php +++ b/src/Customer/CustomerStatisticService.php @@ -16,7 +16,6 @@ use App\Model\CustomerBudgetStatisticModel; use App\Model\CustomerStatistic; use App\Repository\TimesheetRepository; use App\Timesheet\DateTimeFactory; -use DateTime; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Types\Types; @@ -36,7 +35,7 @@ class CustomerStatisticService /** * WARNING: this method does not respect the budget type. Your results will always be with the "full lifetime data" or the "selected date-range". */ - public function getCustomerStatistics(Customer $customer, ?DateTime $begin = null, ?DateTime $end = null): CustomerStatistic + public function getCustomerStatistics(Customer $customer, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): CustomerStatistic { $statistics = $this->getBudgetStatistic([$customer], $begin, $end); $event = new CustomerStatisticEvent($customer, array_pop($statistics), $begin, $end); @@ -51,7 +50,7 @@ class CustomerStatisticService $stats->setStatisticTotal($this->getCustomerStatistics($customer)); $begin = null; - $end = DateTime::createFromInterface($today); + $end = DateTimeImmutable::createFromInterface($today); if ($customer->isMonthlyBudget()) { $dateFactory = new DateTimeFactory($today->getTimezone()); diff --git a/src/Event/CustomerStatisticEvent.php b/src/Event/CustomerStatisticEvent.php index 9a21af2f..381a8877 100644 --- a/src/Event/CustomerStatisticEvent.php +++ b/src/Event/CustomerStatisticEvent.php @@ -14,7 +14,7 @@ use App\Model\CustomerStatistic; final class CustomerStatisticEvent extends AbstractCustomerEvent { - public function __construct(Customer $customer, private CustomerStatistic $statistic, private ?\DateTime $begin = null, private ?\DateTime $end = null) + public function __construct(Customer $customer, private readonly CustomerStatistic $statistic, private readonly ?\DateTimeInterface $begin = null, private readonly ?\DateTimeInterface $end = null) { parent::__construct($customer); } @@ -24,12 +24,12 @@ final class CustomerStatisticEvent extends AbstractCustomerEvent return $this->statistic; } - public function getBegin(): ?\DateTime + public function getBegin(): ?\DateTimeInterface { return $this->begin; } - public function getEnd(): ?\DateTime + public function getEnd(): ?\DateTimeInterface { return $this->end; } diff --git a/src/Event/WorkingTimeYearEvent.php b/src/Event/WorkingTimeYearEvent.php index 04b05959..139fc5d6 100644 --- a/src/Event/WorkingTimeYearEvent.php +++ b/src/Event/WorkingTimeYearEvent.php @@ -15,10 +15,14 @@ use Symfony\Contracts\EventDispatcher\Event; /** * Working time for every day of the given year. * Will be reflected in the working-time summary row. + * + * Only to be used with already approved entries. + * + * Can be locked before, but also can be locked by the system. */ final class WorkingTimeYearEvent extends Event { - public function __construct(private Year $year, private \DateTimeInterface $until) + public function __construct(private readonly Year $year, private readonly \DateTimeInterface $until) { } diff --git a/src/Form/Toolbar/ToolbarFormTrait.php b/src/Form/Toolbar/ToolbarFormTrait.php index 6494fcb7..75f45d88 100644 --- a/src/Form/Toolbar/ToolbarFormTrait.php +++ b/src/Form/Toolbar/ToolbarFormTrait.php @@ -266,7 +266,7 @@ trait ToolbarFormTrait $builder->addEventListener( FormEvents::PRE_SUBMIT, - function (FormEvent $event) use ($name, $multiProject, $activityOptions) { + function (FormEvent $event) use ($name, $multiProject, $activityOptions, $options) { /** @var array $data */ $data = $event->getData(); $event->getForm()->add($name, ActivityType::class, array_merge($activityOptions, [ @@ -299,7 +299,7 @@ trait ToolbarFormTrait return $repo->getQueryBuilderForFormType($query); }, - ])); + ], $options)); } ); } diff --git a/src/Invoice/Hydrator/InvoiceModelActivityHydrator.php b/src/Invoice/Hydrator/InvoiceModelActivityHydrator.php index d5fe0bf8..2a2b4a30 100644 --- a/src/Invoice/Hydrator/InvoiceModelActivityHydrator.php +++ b/src/Invoice/Hydrator/InvoiceModelActivityHydrator.php @@ -18,7 +18,7 @@ final class InvoiceModelActivityHydrator implements InvoiceModelHydrator { use BudgetHydratorTrait; - public function __construct(private ActivityStatisticService $activityStatistic) + public function __construct(private readonly ActivityStatisticService $activityStatistic) { } @@ -70,8 +70,9 @@ final class InvoiceModelActivityHydrator implements InvoiceModelHydrator $prefix . 'invoice_text' => $activity->getInvoiceText() ?? '', ]; - if ($model->getQuery()?->getEnd() !== null) { - $statistic = $this->activityStatistic->getBudgetStatisticModel($activity, $model->getQuery()->getEnd()); + $end = $model->getQuery()?->getEnd(); + if ($end !== null) { + $statistic = $this->activityStatistic->getBudgetStatisticModel($activity, $end); $values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model)); } diff --git a/src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php b/src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php index 75349e8f..8c862bfb 100644 --- a/src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php +++ b/src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php @@ -17,7 +17,7 @@ final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator { use BudgetHydratorTrait; - public function __construct(private CustomerStatisticService $customerStatisticService) + public function __construct(private readonly CustomerStatisticService $customerStatisticService) { } @@ -29,34 +29,37 @@ final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator return []; } + $prefix = 'customer.'; + $values = [ - 'customer.id' => $customer->getId(), - 'customer.address' => $customer->getAddress() ?? '', - 'customer.name' => $customer->getName() ?? '', - 'customer.contact' => $customer->getContact() ?? '', - 'customer.company' => $customer->getCompany() ?? '', - 'customer.vat' => $customer->getVatId() ?? '', // deprecated since 2.0.15 - 'customer.vat_id' => $customer->getVatId() ?? '', - 'customer.number' => $customer->getNumber() ?? '', - 'customer.country' => $customer->getCountry(), - 'customer.homepage' => $customer->getHomepage() ?? '', - 'customer.comment' => $customer->getComment() ?? '', - 'customer.email' => $customer->getEmail() ?? '', - 'customer.fax' => $customer->getFax() ?? '', - 'customer.phone' => $customer->getPhone() ?? '', - 'customer.mobile' => $customer->getMobile() ?? '', - 'customer.invoice_text' => $customer->getInvoiceText() ?? '', + $prefix . 'id' => $customer->getId(), + $prefix . 'address' => $customer->getAddress() ?? '', + $prefix . 'name' => $customer->getName() ?? '', + $prefix . 'contact' => $customer->getContact() ?? '', + $prefix . 'company' => $customer->getCompany() ?? '', + $prefix . 'vat' => $customer->getVatId() ?? '', // deprecated since 2.0.15 + $prefix . 'vat_id' => $customer->getVatId() ?? '', + $prefix . 'number' => $customer->getNumber() ?? '', + $prefix . 'country' => $customer->getCountry(), + $prefix . 'homepage' => $customer->getHomepage() ?? '', + $prefix . 'comment' => $customer->getComment() ?? '', + $prefix . 'email' => $customer->getEmail() ?? '', + $prefix . 'fax' => $customer->getFax() ?? '', + $prefix . 'phone' => $customer->getPhone() ?? '', + $prefix . 'mobile' => $customer->getMobile() ?? '', + $prefix . 'invoice_text' => $customer->getInvoiceText() ?? '', ]; - /** @var \DateTime $end */ - $end = $model->getQuery()->getEnd(); - $statistic = $this->customerStatisticService->getBudgetStatisticModel($customer, $end); + $end = $model->getQuery()?->getEnd(); + if ($end !== null) { + $statistic = $this->customerStatisticService->getBudgetStatisticModel($customer, $end); - $values = array_merge($values, $this->getBudgetValues('customer.', $statistic, $model)); + $values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model)); + } foreach ($customer->getMetaFields() as $metaField) { $values = array_merge($values, [ - 'customer.meta.' . $metaField->getName() => $metaField->getValue(), + $prefix . 'meta.' . $metaField->getName() => $metaField->getValue(), ]); } diff --git a/src/Invoice/Hydrator/InvoiceModelProjectHydrator.php b/src/Invoice/Hydrator/InvoiceModelProjectHydrator.php index c5c871e4..8253428e 100644 --- a/src/Invoice/Hydrator/InvoiceModelProjectHydrator.php +++ b/src/Invoice/Hydrator/InvoiceModelProjectHydrator.php @@ -18,7 +18,7 @@ final class InvoiceModelProjectHydrator implements InvoiceModelHydrator { use BudgetHydratorTrait; - public function __construct(private ProjectStatisticService $projectStatistic) + public function __construct(private readonly ProjectStatisticService $projectStatistic) { } @@ -83,8 +83,9 @@ final class InvoiceModelProjectHydrator implements InvoiceModelHydrator $prefix . 'invoice_text' => $project->getInvoiceText() ?? '', ]; - if ($model->getQuery()?->getEnd() !== null) { - $statistic = $this->projectStatistic->getBudgetStatisticModel($project, $model->getQuery()->getEnd()); + $end = $model->getQuery()?->getEnd(); + if ($end !== null) { + $statistic = $this->projectStatistic->getBudgetStatisticModel($project, $end); $values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model)); } diff --git a/src/Invoice/Renderer/AbstractTwigRenderer.php b/src/Invoice/Renderer/AbstractTwigRenderer.php index 2509f1d0..3ba38148 100644 --- a/src/Invoice/Renderer/AbstractTwigRenderer.php +++ b/src/Invoice/Renderer/AbstractTwigRenderer.php @@ -12,17 +12,19 @@ namespace App\Invoice\Renderer; use App\Invoice\InvoiceModel; use App\Invoice\RendererInterface; use App\Model\InvoiceDocument; -use App\Twig\TwigRendererTrait; +use App\Twig\LocaleFormatExtensions; +use App\Twig\SecurityPolicy\InvoicePolicy; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Twig\Environment; +use Twig\Extension\SandboxExtension; /** * @internal */ abstract class AbstractTwigRenderer implements RendererInterface { - use TwigRendererTrait; - - public function __construct(private Environment $twig) + public function __construct(private readonly Environment $twig) { } @@ -45,6 +47,65 @@ abstract class AbstractTwigRenderer implements RendererInterface 'entries' => $entries ], $options); + // cloning twig, because we don't want to change the return $this->renderTwigTemplateWithLanguage($this->twig, $template, $options, $language, $formatLocale); } + + private function renderTwigTemplateWithLanguage(Environment $twig, string $template, array $options = [], ?string $language = null, ?string $formatLocale = null): string + { + $previousTranslation = null; + $previousFormatLocale = null; + + if ($language !== null) { + $previousTranslation = $this->switchTranslationLocale($twig, $language); + } + if ($formatLocale !== null) { + $previousFormatLocale = $this->switchFormatLocale($twig, $formatLocale); + } + + if (!$twig->hasExtension(SandboxExtension::class)) { + $twig->addExtension(new SandboxExtension(new InvoicePolicy())); + } + + $sandbox = $twig->getExtension(SandboxExtension::class); + $sandbox->enableSandbox(); + + $content = $twig->render($template, $options); + + $sandbox->disableSandbox(); + + if ($previousTranslation !== null) { + $this->switchTranslationLocale($twig, $previousTranslation); + } + if ($previousFormatLocale !== null) { + $this->switchFormatLocale($twig, $previousFormatLocale); + } + + return $content; + } + + private function switchTranslationLocale(Environment $twig, string $language): string + { + /** @var TranslationExtension $extension */ + $extension = $twig->getExtension(TranslationExtension::class); + + $translator = $extension->getTranslator(); + if (!$translator instanceof LocaleAwareInterface) { + throw new \Exception('Translator is expected to be of type LocaleAwareInterface'); + } + $previous = $translator->getLocale(); + $translator->setLocale($language); + + return $previous; + } + + private function switchFormatLocale(Environment $twig, string $language): string + { + /** @var LocaleFormatExtensions $extension */ + $extension = $twig->getExtension(LocaleFormatExtensions::class); + $previous = $extension->getLocale(); + $extension->setLocale($language); + + return $previous; + } } diff --git a/src/Mail/KimaiMailer.php b/src/Mail/KimaiMailer.php index 9c04675c..f15049ba 100644 --- a/src/Mail/KimaiMailer.php +++ b/src/Mail/KimaiMailer.php @@ -10,6 +10,7 @@ namespace App\Mail; use App\Configuration\MailConfiguration; +use App\Constants; use App\Entity\User; use Symfony\Component\Mailer\Envelope; use Symfony\Component\Mailer\MailerInterface; @@ -36,7 +37,7 @@ final class KimaiMailer implements MailerInterface if ($fallback === null) { throw new \RuntimeException('Missing email "from" address'); } - $message->from(new Address($fallback, 'Kimai')); + $message->from(new Address($fallback, Constants::SOFTWARE)); } $this->mailer->send($message); diff --git a/src/Project/ProjectStatisticService.php b/src/Project/ProjectStatisticService.php index a382407f..bc88546d 100644 --- a/src/Project/ProjectStatisticService.php +++ b/src/Project/ProjectStatisticService.php @@ -199,7 +199,7 @@ class ProjectStatisticService $stats->setStatisticTotal($this->getProjectStatistics($project)); $begin = null; - $end = $today; + $end = DateTimeImmutable::createFromInterface($today); if ($project->isMonthlyBudget()) { $dateFactory = new DateTimeFactory($today->getTimezone()); diff --git a/src/Repository/TagRepository.php b/src/Repository/TagRepository.php index d70998bd..44d03f5b 100644 --- a/src/Repository/TagRepository.php +++ b/src/Repository/TagRepository.php @@ -114,7 +114,7 @@ class TagRepository extends EntityRepository $qb ->resetDQLPart('select') ->resetDQLPart('orderBy') - ->select($qb->expr()->count('tag.id')) + ->select($qb->expr()->count('tag')) ; /** @var int<0, max> $counter */ $counter = (int) $qb->getQuery()->getSingleScalarResult(); diff --git a/src/Repository/TimesheetRepository.php b/src/Repository/TimesheetRepository.php index 77138136..43254c67 100644 --- a/src/Repository/TimesheetRepository.php +++ b/src/Repository/TimesheetRepository.php @@ -459,7 +459,7 @@ class TimesheetRepository extends EntityRepository $qb ->resetDQLPart('select') ->resetDQLPart('orderBy') - ->select($qb->expr()->count('t.id')) + ->select($qb->expr()->count('t')) ; return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line @@ -868,7 +868,7 @@ class TimesheetRepository extends EntityRepository $qb = $this->getEntityManager()->createQueryBuilder(); $qb - ->select($qb->expr()->count('t.id')) + ->select($qb->expr()->count('t')) ->from(Timesheet::class, 't') ; diff --git a/src/Twig/TwigRendererTrait.php b/src/Twig/TwigRendererTrait.php deleted file mode 100644 index a5ea5bd4..00000000 --- a/src/Twig/TwigRendererTrait.php +++ /dev/null @@ -1,78 +0,0 @@ -switchTranslationLocale($twig, $language); - } - if ($formatLocale !== null) { - $previousFormatLocale = $this->switchFormatLocale($twig, $formatLocale); - } - - // enable basic security measures - if (!$twig->hasExtension(SandboxExtension::class)) { - $sandbox = new SandboxExtension(new InvoicePolicy()); - $sandbox->enableSandbox(); - $twig->addExtension($sandbox); - } - - $content = $twig->render($template, $options); - - if ($previousTranslation !== null) { - $this->switchTranslationLocale($twig, $previousTranslation); - } - if ($previousFormatLocale !== null) { - $this->switchFormatLocale($twig, $previousFormatLocale); - } - - return $content; - } - - protected function switchTranslationLocale(Environment $twig, string $language): string - { - /** @var TranslationExtension $extension */ - $extension = $twig->getExtension(TranslationExtension::class); - - $translator = $extension->getTranslator(); - if (!$translator instanceof LocaleAwareInterface) { - throw new \Exception('Translator is expected to be of type LocaleAwareInterface'); - } - $previous = $translator->getLocale(); - $translator->setLocale($language); - - return $previous; - } - - protected function switchFormatLocale(Environment $twig, string $language): string - { - /** @var LocaleFormatExtensions $extension */ - $extension = $twig->getExtension(LocaleFormatExtensions::class); - $previous = $extension->getLocale(); - $extension->setLocale($language); - - return $previous; - } -} diff --git a/src/Utils/Pagination.php b/src/Utils/Pagination.php index 7d16e13a..9746d152 100644 --- a/src/Utils/Pagination.php +++ b/src/Utils/Pagination.php @@ -11,6 +11,7 @@ namespace App\Utils; use App\Repository\Query\BaseQuery; use Pagerfanta\Adapter\AdapterInterface; +use Pagerfanta\Adapter\ArrayAdapter; use Pagerfanta\Pagerfanta; final class Pagination extends Pagerfanta @@ -19,6 +20,10 @@ final class Pagination extends Pagerfanta { parent::__construct($adapter); + if ($adapter instanceof ArrayAdapter && ($size = $adapter->getNbResults()) > 0) { + $this->setMaxPerPage($size); + } + if ($query === null || !$query->isApiCall()) { $this->setNormalizeOutOfRangePages(true); } diff --git a/templates/default/_form.html.twig b/templates/default/_form.html.twig index 4ee5a512..2a23ec1f 100644 --- a/templates/default/_form.html.twig +++ b/templates/default/_form.html.twig @@ -15,9 +15,15 @@
- {{ form_errors(form) }} - {% block form_body %} - {{ form_rest(form) }} + {% block form_body_outer %} + {% block form_body_pre %} + {{ form_errors(form) }} + {% endblock %} + {% block form_body %} + {{ form_rest(form) }} + {% endblock %} + {% block form_body_post %} + {% endblock %} {% endblock %}
diff --git a/tests/Ldap/LdapBadgeTest.php b/tests/Ldap/LdapBadgeTest.php new file mode 100644 index 00000000..8d497f3f --- /dev/null +++ b/tests/Ldap/LdapBadgeTest.php @@ -0,0 +1,32 @@ +markResolved(); + self::assertTrue($badge->isResolved()); + } + + public function testIsResolvedReturnsFalseInitially(): void + { + $badge = new LdapBadge(); + self::assertFalse($badge->isResolved()); + } +} diff --git a/tests/Mail/KimaiMailerTest.php b/tests/Mail/KimaiMailerTest.php index 7020e5ae..05548464 100644 --- a/tests/Mail/KimaiMailerTest.php +++ b/tests/Mail/KimaiMailerTest.php @@ -22,16 +22,19 @@ use Symfony\Component\Mime\Email; */ class KimaiMailerTest extends TestCase { - public function getSut(): KimaiMailer + public function getSut(?MailerInterface $mailer = null): KimaiMailer { $config = new MailConfiguration('zippel@example.com'); - $mailer = $this->createMock(MailerInterface::class); + if ($mailer === null) { + $mailer = $this->createMock(MailerInterface::class); + $mailer->expects(self::once())->method('send'); + } return new KimaiMailer($config, $mailer); } - public function testSendSetsFrom(): void + public function testSendSetsFromHeaderFromFallback(): void { $user = new User(); $user->setUserIdentifier('Testing'); @@ -47,4 +50,83 @@ class KimaiMailerTest extends TestCase self::assertEquals([new Address('zippel@example.com', 'Kimai')], $message->getFrom()); } + + public function testSendToUserSetsFromHeaderFromFallback(): void + { + $user = new User(); + $user->setUserIdentifier('Testing'); + $user->setEmail('foo@example.com'); + $user->setAlias('Super User'); + $user->setEnabled(true); + + $mailer = $this->getSut(); + $message = new Email(); + + $mailer->sendToUser($user, $message); + + self::assertEquals([new Address('zippel@example.com', 'Kimai')], $message->getFrom()); + } + + public function testSendToUserSendsEmailWhenUserIsEnabledAndHasEmail(): void + { + $user = $this->createMock(User::class); + $user->method('isEnabled')->willReturn(true); + $user->method('getEmail')->willReturn('foo-bar@example.com'); + + $email = new Email(); + self::assertEquals([], $email->getTo()); + $mailer = $this->createMock(MailerInterface::class); + $mailer->expects(self::once())->method('send')->with($email); + + $sut = $this->getSut($mailer); + + $sut->sendToUser($user, $email); + self::assertEquals([new Address('zippel@example.com', 'Kimai')], $email->getFrom()); + self::assertEquals([new Address('foo-bar@example.com')], $email->getTo()); + } + + public function testSendToUserDoesNotSendEmailWhenUserIsDisabled(): void + { + $user = $this->createMock(User::class); + $user->method('isEnabled')->willReturn(false); + $user->method('getEmail')->willReturn('user@example.com'); + + $email = new Email(); + $mailer = $this->createMock(MailerInterface::class); + $mailer->expects(self::never())->method('send'); + $sut = $this->getSut($mailer); + + $sut->sendToUser($user, $email); + } + + public function testSendToUserDoesNotSendEmailWhenUserHasNoEmail(): void + { + $user = $this->createMock(User::class); + $user->method('isEnabled')->willReturn(true); + $user->method('getEmail')->willReturn(null); + + $email = new Email(); + $mailer = $this->createMock(MailerInterface::class); + $mailer->expects(self::never())->method('send'); + $sut = $this->getSut($mailer); + + $sut->sendToUser($user, $email); + } + + public function testSThrowsOnEmptyFromAddress(): void + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Missing email "from" address'); + + $user = $this->createMock(User::class); + $user->method('isEnabled')->willReturn(true); + $user->method('getEmail')->willReturn('test@example.com'); + + $email = new Email(); + $config = new MailConfiguration(''); + $mailer = $this->createMock(MailerInterface::class); + $sut = new KimaiMailer($config, $mailer); + + $sut->sendToUser($user, $email); + } } diff --git a/tests/Plugin/PackageTest.php b/tests/Plugin/PackageTest.php new file mode 100644 index 00000000..98bf671b --- /dev/null +++ b/tests/Plugin/PackageTest.php @@ -0,0 +1,38 @@ +createMock(PluginMetadata::class); + $package = new Package($fileInfo, $metadata); + + self::assertSame($fileInfo, $package->getPackageFile()); + } + + public function testGetMetadataReturnsCorrectMetadata(): void + { + $fileInfo = new \SplFileInfo('path/to/package.zip'); + $metadata = $this->createMock(PluginMetadata::class); + $package = new Package($fileInfo, $metadata); + + self::assertSame($metadata, $package->getMetadata()); + } +} diff --git a/tests/Twig/PaginationExtensionTest.php b/tests/Twig/PaginationExtensionTest.php index 15f0637e..bf8476f9 100644 --- a/tests/Twig/PaginationExtensionTest.php +++ b/tests/Twig/PaginationExtensionTest.php @@ -65,6 +65,7 @@ class PaginationExtensionTest extends TestCase $values = array_fill(0, 151, 'blub'); $pagerfanta = new Pagination(new ArrayAdapter($values)); + $pagerfanta->setMaxPerPage(10); $result = $sut->renderPagination($pagerfanta, [ 'css_container_class' => 'pagination pagination-sm inline', 'routeName' => 'project_activities', @@ -98,6 +99,7 @@ class PaginationExtensionTest extends TestCase $values = array_fill(0, 151, 'blub'); $pagerfanta = new Pagination(new ArrayAdapter($values)); + $pagerfanta->setMaxPerPage(10); $result = $sut->renderPagination($pagerfanta, [ 'css_container_class' => 'pagination pagination-sm inline', 'routeName' => 'project_activities', @@ -106,6 +108,26 @@ class PaginationExtensionTest extends TestCase $this->assertPaginationHtml($result); } + public function testRenderPaginationWithoutPageSize(): void + { + $sut = $this->getSut(); + + $values = array_fill(0, 151, 'blub'); + $pagerfanta = new Pagination(new ArrayAdapter($values)); + $result = $sut->renderPagination($pagerfanta, [ + 'css_container_class' => 'pagination pagination-sm inline', + 'routeName' => 'project_activities', + 'routeParams' => ['id' => 137] + ]); + + $expected = + ''; + + self::assertEquals($expected, $result); + } + public function testRenderPaginationWithoutRouteName(): void { $this->expectException(\Exception::class); diff --git a/translations/email.de.xlf b/translations/email.de.xlf index 95dfaa56..8539b17c 100644 --- a/translations/email.de.xlf +++ b/translations/email.de.xlf @@ -72,7 +72,7 @@ Bitte überprüfen Sie diese unter: %url% approval_rejected_message - Ihre Genehmigungsanfrage wurde von %user% abgelehnt. + Ihre Genehmigungsanfrage wurde von %created_by% abgelehnt. %list% @@ -84,7 +84,7 @@ Bitte überprüfen Sie ihre Anfragen unter: %url% approval_approved_message - Ihre Anfrage wurde von %user% genehmigt. + Ihre Anfrage wurde von %created_by% genehmigt. %list% diff --git a/translations/email.en.xlf b/translations/email.en.xlf index 7f608737..92de18a4 100644 --- a/translations/email.en.xlf +++ b/translations/email.en.xlf @@ -72,7 +72,7 @@ Please review them at: %url% approval_rejected_message - Your authorisation request has been rejected by %user%. + Your authorisation request has been rejected by %created_by%. %list% @@ -84,7 +84,7 @@ Please review your requests at: %url% approval_approved_message - Your request has been approved by %user%. + Your request has been approved by %created_by%. %list% diff --git a/translations/email.es.xlf b/translations/email.es.xlf index 9f4f5d0c..6dd5b4ea 100644 --- a/translations/email.es.xlf +++ b/translations/email.es.xlf @@ -64,7 +64,7 @@ Por favor revísalos en: %url% approval_rejected_message - Su solicitud de autorización ha sido rechazada por %user%. + Su solicitud de autorización ha sido rechazada por %created_by%. %list% @@ -80,7 +80,7 @@ Por favor revise sus solicitudes en: %url% approval_approved_message - Su solicitud ha sido aprobada por %user%. + Su solicitud ha sido aprobada por %created_by%. %list% diff --git a/translations/email.he.xlf b/translations/email.he.xlf index 2eda63d3..3d262846 100644 --- a/translations/email.he.xlf +++ b/translations/email.he.xlf @@ -68,7 +68,7 @@ Please review them at: %url% approval_rejected_message - בקשת האימות נדחתה על ידי המשתמש %user%. + בקשת האימות נדחתה על ידי המשתמש %created_by%. %list% @@ -84,7 +84,7 @@ Please review them at: %url% approval_approved_message - הבקשה שלך אושרה על ידי %user%. + הבקשה שלך אושרה על ידי %created_by%. %list% diff --git a/translations/email.hr.xlf b/translations/email.hr.xlf index c511e5a0..0f3c8b3b 100644 --- a/translations/email.hr.xlf +++ b/translations/email.hr.xlf @@ -72,7 +72,7 @@ Pregledaj ih na: %url% approval_rejected_message - %user% je odbio/la tvoj zahtjev za autorizaciju. + %created_by% je odbio/la tvoj zahtjev za autorizaciju. %list% @@ -84,7 +84,7 @@ Pregledaj tvoje zahtjeve na: %url% approval_approved_message - %user% je odobrio/la tvoj zahtjev. + %created_by% je odobrio/la tvoj zahtjev. %list% diff --git a/translations/email.id.xlf b/translations/email.id.xlf index a8ec7758..f059e6a9 100644 --- a/translations/email.id.xlf +++ b/translations/email.id.xlf @@ -72,7 +72,7 @@ Silakan tinjau di: %url% approval_rejected_message - Permintaan otorisasi Anda telah ditolak oleh %user%. + Permintaan otorisasi Anda telah ditolak oleh %created_by%. %list% @@ -84,7 +84,7 @@ Silakan tinjau permintaan Anda di: %url% approval_approved_message - Permintaan Anda telah disetujui oleh %user%. + Permintaan Anda telah disetujui oleh %created_by%. %list% diff --git a/translations/email.it.xlf b/translations/email.it.xlf index 3c88b3d6..f891173c 100644 --- a/translations/email.it.xlf +++ b/translations/email.it.xlf @@ -64,7 +64,7 @@ Controllali qui: %url% approval_approved_message - La tua richiesta è stata approvata da %user%. + La tua richiesta è stata approvata da %created_by%. %list% @@ -72,7 +72,7 @@ Puoi trovare tutte le tue richieste qui: %url% approval_rejected_message - La tua richiesta di autorizzazione è stata rifiutata da %user%. + La tua richiesta di autorizzazione è stata rifiutata da %created_by%. %list% diff --git a/translations/email.pt_BR.xlf b/translations/email.pt_BR.xlf index df3af119..77f96daa 100644 --- a/translations/email.pt_BR.xlf +++ b/translations/email.pt_BR.xlf @@ -72,7 +72,7 @@ Favor revisá-los em: %url% approval_approved_message - Sua solicitação foi aprovada pelo %user%. + Sua solicitação foi aprovada pelo %created_by%. %list% @@ -84,7 +84,7 @@ Você pode encontrar todas as suas solicitações em: %url% approval_rejected_message - Sua solicitação de autorização foi recusada pelo %user%. + Sua solicitação de autorização foi recusada pelo %created_by%. %list% diff --git a/translations/email.uk.xlf b/translations/email.uk.xlf index da59e59a..8951ec99 100644 --- a/translations/email.uk.xlf +++ b/translations/email.uk.xlf @@ -72,7 +72,7 @@ Please review them at: %url% approval_rejected_message - Ваш запит на авторизацію %user% скасовує. + Ваш запит на авторизацію %created_by% скасовує. %list% @@ -84,7 +84,7 @@ Please review them at: %url% approval_approved_message - %user% схвалює Ваш запит. + %created_by% схвалює Ваш запит. %list% diff --git a/translations/messages.cs.xlf b/translations/messages.cs.xlf index 4beccdf2..13d32efd 100644 --- a/translations/messages.cs.xlf +++ b/translations/messages.cs.xlf @@ -1462,10 +1462,6 @@ complete_month.help Toto uzamkne všechny dny roku předcházející vybrané datum. Uživatel dál nebude moci vytvářet ani upravovat časy pro uzamčené období. - - work_times_result - Odpracované hodiny - work_times_should.none_configured Pro tohoto uživatele nebyly v nastavení pracovní smlouvy nakonfigurovány žádné cílové hodiny. diff --git a/translations/messages.da.xlf b/translations/messages.da.xlf index 37b6a2ff..710d49f8 100644 --- a/translations/messages.da.xlf +++ b/translations/messages.da.xlf @@ -1026,10 +1026,6 @@ Review Gennemse - - work_times_result - Timer arbejdet - notifications.welcome Tak for at du har tilmedt dig notifikationer! diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index f1ceb019..31a9387f 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -650,10 +650,6 @@ work_times_is Geleistete Stunden - - work_times_result - Geleistete Stunden - confirmed_by_at Bestätigt von %user% am %date% @@ -1938,6 +1934,14 @@ Log out of all devices Von allen Geräten abmelden + + result + Ergebnis + + + training + Fortbildung + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 82796c01..46d36832 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -650,10 +650,6 @@ work_times_is Hours worked - - work_times_result - Hours worked - confirmed_by_at Confirmed by %user% at %date% @@ -1938,6 +1934,14 @@ Log out of all devices Log out of all devices + + result + Result + + + training + Training + diff --git a/translations/messages.es.xlf b/translations/messages.es.xlf index 63d2c290..af0a2c06 100644 --- a/translations/messages.es.xlf +++ b/translations/messages.es.xlf @@ -1534,10 +1534,6 @@ receive_from Recibir de %name% - - work_times_result - Horas trabajadas - You have submitted %used% of your available %days% vacation days so far. Has utilizado %used% de tus %days% días disponibles de vacaciones. diff --git a/translations/messages.fi.xlf b/translations/messages.fi.xlf index 10171e2e..982c75d3 100644 --- a/translations/messages.fi.xlf +++ b/translations/messages.fi.xlf @@ -1406,10 +1406,6 @@ favorite_routes Suosikit - - work_times_result - Työskennellyt tunnit - You have submitted %used% of your available %days% vacation days so far. Olet käyttänyt %used% vapaata saatavilla olevista %days% päivästä. diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index 9f9ab28d..e2859d84 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -1498,10 +1498,6 @@ manual_bookings.work_contract_intro Les réservations manuelles ne peuvent être ni modifiées ni supprimées ! - - work_times_result - Heures travaillées - invisible Invisible diff --git a/translations/messages.he.xlf b/translations/messages.he.xlf index 482a9466..0c5e0a87 100644 --- a/translations/messages.he.xlf +++ b/translations/messages.he.xlf @@ -1446,10 +1446,6 @@ work_times_is שעות עבודה בפועל - - work_times_result - שעות עבודה - confirmed_by_at אושר על ידי %user% ב־%date% diff --git a/translations/messages.hr.xlf b/translations/messages.hr.xlf index ca88758a..a99ae411 100644 --- a/translations/messages.hr.xlf +++ b/translations/messages.hr.xlf @@ -1442,10 +1442,6 @@ manual_bookings Ručne rezervacije - - work_times_result - Odrađeni sati - work_times_is Odrađeni sati diff --git a/translations/messages.id.xlf b/translations/messages.id.xlf index 1d033265..7af1a8b8 100644 --- a/translations/messages.id.xlf +++ b/translations/messages.id.xlf @@ -650,10 +650,6 @@ work_times_should.none_configured Tidak ada target jam yang ditetapkan pada pengguna ini dalam setelan kontrak pekerjaan. - - work_times_result - Jam bekerja - profile.registration_date Teregistrasi di diff --git a/translations/messages.it.xlf b/translations/messages.it.xlf index 26beafa6..424646df 100644 --- a/translations/messages.it.xlf +++ b/translations/messages.it.xlf @@ -1482,10 +1482,6 @@ confirmed_by_at Confermato da %user% il %date% - - work_times_result - Ore lavorate - manual_bookings Prenotazioni manuali diff --git a/translations/messages.nl.xlf b/translations/messages.nl.xlf index 1f9cd195..34052c49 100644 --- a/translations/messages.nl.xlf +++ b/translations/messages.nl.xlf @@ -1430,10 +1430,6 @@ work_times_is Gewerkte uren - - work_times_result - Gewerkte uren - manual_bookings Handmatige boekingen diff --git a/translations/messages.pl.xlf b/translations/messages.pl.xlf index 3c70f566..23471100 100644 --- a/translations/messages.pl.xlf +++ b/translations/messages.pl.xlf @@ -1574,10 +1574,6 @@ work_times_is Przepracowane godziny - - work_times_result - Przepracowane godziny - confirmed_by_at Potwierdzone przez %user% dnia %date% diff --git a/translations/messages.pt.xlf b/translations/messages.pt.xlf index e69bb152..ca58078c 100644 --- a/translations/messages.pt.xlf +++ b/translations/messages.pt.xlf @@ -1630,10 +1630,6 @@ work_times_is Horas realizadas - - work_times_result - Horas trabalhadas - confirmed_by_at Confirmado por %user% em %date% diff --git a/translations/messages.pt_BR.xlf b/translations/messages.pt_BR.xlf index b867a95f..ba6e31ca 100644 --- a/translations/messages.pt_BR.xlf +++ b/translations/messages.pt_BR.xlf @@ -1474,10 +1474,6 @@ completed_month_pdf PDF do mês concluído - - work_times_result - Horas trabalhadas - work_times_should.none_configured Nenhuma hora-alvo foi configurada para esse usuário nas configurações do contrato de trabalho. diff --git a/translations/messages.ro.xlf b/translations/messages.ro.xlf index 2b63c36e..3c2e3bf2 100644 --- a/translations/messages.ro.xlf +++ b/translations/messages.ro.xlf @@ -1406,10 +1406,6 @@ work_times_is Timpul de lucru este - - work_times_result - Ore lucrate - confirmed_by_at Confirmat de %user% la %date% diff --git a/translations/messages.ru.xlf b/translations/messages.ru.xlf index dc0282a5..16496c90 100644 --- a/translations/messages.ru.xlf +++ b/translations/messages.ru.xlf @@ -1354,10 +1354,6 @@ Expected number of hours Предполагаемые часы - - work_times_result - Отработанные часы - confirmed_by_at Подтверждено %user% в %date% diff --git a/translations/messages.sk.xlf b/translations/messages.sk.xlf index f5c5bec1..21306379 100644 --- a/translations/messages.sk.xlf +++ b/translations/messages.sk.xlf @@ -1170,10 +1170,6 @@ export.warn_result_amount Vaše vyhľadávanie vedie k %count% výsledkom. Pokiaľ sa export nepodarí, musíte zúžiť vaše vyhľadávanie. - - work_times_result - Odpracované hodiny - work_times_should.none_configured Pro tohoto uživateľa neboli v nastaveniach pracovnej zmluvy nakonfigurované žiadne cielové hodiny. diff --git a/translations/messages.sv.xlf b/translations/messages.sv.xlf index b3f76903..c8b96fb3 100644 --- a/translations/messages.sv.xlf +++ b/translations/messages.sv.xlf @@ -1434,10 +1434,6 @@ confirmed_by_at Bekräftad %date% av %user% - - work_times_result - Timmar arbetade - status.approved Godkänd diff --git a/translations/messages.tr.xlf b/translations/messages.tr.xlf index d0c18281..a0634fff 100644 --- a/translations/messages.tr.xlf +++ b/translations/messages.tr.xlf @@ -1470,10 +1470,6 @@ work_times_should.none_configured İş sözleşmesi ayarlarında bu kullanıcı için herhangi bir hedef saat yapılandırılmamış. - - work_times_result - Çalışılan saatler - manual_bookings.duration_help Süre hem pozitif (is-time artması) hem de negatif (is-time azalması) olabilir. diff --git a/translations/messages.uk.xlf b/translations/messages.uk.xlf index e18eff15..1c1f1b49 100644 --- a/translations/messages.uk.xlf +++ b/translations/messages.uk.xlf @@ -1438,10 +1438,6 @@ Expected number of hours Очікувані години - - work_times_result - Відпрацьовані години - manual_bookings Ручне бронювання diff --git a/translations/messages.vi.xlf b/translations/messages.vi.xlf index 0edb1a79..032e7769 100644 --- a/translations/messages.vi.xlf +++ b/translations/messages.vi.xlf @@ -1310,10 +1310,6 @@ evaluation Đánh giá - - work_times_result - Số giờ làm việc - work_times_is Giờ làm việc thực tế diff --git a/translations/messages.zh_CN.xlf b/translations/messages.zh_CN.xlf index 51731c3e..692fbabe 100644 --- a/translations/messages.zh_CN.xlf +++ b/translations/messages.zh_CN.xlf @@ -1478,10 +1478,6 @@ manual_bookings.duration_help 持续时间既可以是正的(增加的是时间)也可以是负的(减少的是时间)。 - - work_times_result - 工作时间 - confirmed_by_at 由 %user% 在 %date% 确认 diff --git a/translations/messages.zh_Hant.xlf b/translations/messages.zh_Hant.xlf index 37e59328..8fb573a3 100644 --- a/translations/messages.zh_Hant.xlf +++ b/translations/messages.zh_Hant.xlf @@ -1410,10 +1410,6 @@ unit_price 單價 - - work_times_result - 工作時數 - parental 育嬰假