Skip to content

Commit

Permalink
Release 2.27 (#5212)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinpapst authored Dec 22, 2024
1 parent 4fdfb6f commit 4332ef9
Show file tree
Hide file tree
Showing 56 changed files with 354 additions and 256 deletions.
5 changes: 0 additions & 5 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/Configuration/MailConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@

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;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
5 changes: 2 additions & 3 deletions src/Customer/CustomerStatisticService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use App\Model\CustomerStatistic;
use App\Repository\TimesheetRepository;
use App\Timesheet\DateTimeFactory;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\DBAL\Types\Types;
Expand All @@ -36,7 +35,7 @@ public function __construct(private readonly TimesheetRepository $timesheetRepos
/**
* 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);
Expand All @@ -51,7 +50,7 @@ public function getBudgetStatisticModel(Customer $customer, DateTimeInterface $t
$stats->setStatisticTotal($this->getCustomerStatistics($customer));

$begin = null;
$end = DateTime::createFromInterface($today);
$end = DateTimeImmutable::createFromInterface($today);

if ($customer->isMonthlyBudget()) {
$dateFactory = new DateTimeFactory($today->getTimezone());
Expand Down
6 changes: 3 additions & 3 deletions src/Event/CustomerStatisticEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

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);
}
Expand All @@ -24,12 +24,12 @@ public function getStatistic(): CustomerStatistic
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;
}
Expand Down
6 changes: 5 additions & 1 deletion src/Event/WorkingTimeYearEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
/**
* 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)
{
}

Expand Down
4 changes: 2 additions & 2 deletions src/Form/Toolbar/ToolbarFormTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ private function addActivitySelect(FormBuilderInterface $builder, array $options

$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($name, $multiProject, $activityOptions) {
function (FormEvent $event) use ($name, $multiProject, $activityOptions, $options) {
/** @var array<string, mixed> $data */
$data = $event->getData();
$event->getForm()->add($name, ActivityType::class, array_merge($activityOptions, [
Expand Down Expand Up @@ -299,7 +299,7 @@ function (FormEvent $event) use ($name, $multiProject, $activityOptions) {

return $repo->getQueryBuilderForFormType($query);
},
]));
], $options));
}
);
}
Expand Down
7 changes: 4 additions & 3 deletions src/Invoice/Hydrator/InvoiceModelActivityHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class InvoiceModelActivityHydrator implements InvoiceModelHydrator
{
use BudgetHydratorTrait;

public function __construct(private ActivityStatisticService $activityStatistic)
public function __construct(private readonly ActivityStatisticService $activityStatistic)
{
}

Expand Down Expand Up @@ -70,8 +70,9 @@ private function getValuesFromActivity(InvoiceModel $model, Activity $activity,
$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));
}
Expand Down
47 changes: 25 additions & 22 deletions src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
{
use BudgetHydratorTrait;

public function __construct(private CustomerStatisticService $customerStatisticService)
public function __construct(private readonly CustomerStatisticService $customerStatisticService)
{
}

Expand All @@ -29,34 +29,37 @@ public function hydrate(InvoiceModel $model): array
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(),
]);
}

Expand Down
7 changes: 4 additions & 3 deletions src/Invoice/Hydrator/InvoiceModelProjectHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class InvoiceModelProjectHydrator implements InvoiceModelHydrator
{
use BudgetHydratorTrait;

public function __construct(private ProjectStatisticService $projectStatistic)
public function __construct(private readonly ProjectStatisticService $projectStatistic)
{
}

Expand Down Expand Up @@ -83,8 +83,9 @@ private function getValuesFromProject(InvoiceModel $model, Project $project, str
$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));
}
Expand Down
69 changes: 65 additions & 4 deletions src/Invoice/Renderer/AbstractTwigRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@
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)
{
}

Expand All @@ -45,6 +47,65 @@ protected function renderTwigTemplate(InvoiceDocument $document, InvoiceModel $m
'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;
}
}
3 changes: 2 additions & 1 deletion src/Mail/KimaiMailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,7 +37,7 @@ public function send(RawMessage $message, Envelope $envelope = null): void
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);
Expand Down
2 changes: 1 addition & 1 deletion src/Project/ProjectStatisticService.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public function getBudgetStatisticModel(Project $project, DateTimeInterface $tod
$stats->setStatisticTotal($this->getProjectStatistics($project));

$begin = null;
$end = $today;
$end = DateTimeImmutable::createFromInterface($today);

if ($project->isMonthlyBudget()) {
$dateFactory = new DateTimeFactory($today->getTimezone());
Expand Down
2 changes: 1 addition & 1 deletion src/Repository/TagRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public function getTagCount(TagQuery $query): Pagination
$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();
Expand Down
4 changes: 2 additions & 2 deletions src/Repository/TimesheetRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ private function countTimesheetsForQuery(TimesheetQuery $query): int
$qb
->resetDQLPart('select')
->resetDQLPart('orderBy')
->select($qb->expr()->count('t.id'))
->select($qb->expr()->count('t'))
;

return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
Expand Down Expand Up @@ -868,7 +868,7 @@ public function hasRecordForTime(Timesheet $timesheet): bool
$qb = $this->getEntityManager()->createQueryBuilder();

$qb
->select($qb->expr()->count('t.id'))
->select($qb->expr()->count('t'))
->from(Timesheet::class, 't')
;

Expand Down
Loading

0 comments on commit 4332ef9

Please sign in to comment.