Release 2.2.0 (#4359)

* deactivate deprecation logging in prod for now
* fix several deprecations
* enable CSRF for logout
* allow more twig methods and functions in InvoiceSecurity policy
This commit is contained in:
Kevin Papst
2023-10-31 16:41:09 +01:00
committed by GitHub
parent 114617a052
commit 95f15e6c88
22 changed files with 396 additions and 324 deletions

View File

@@ -77,7 +77,7 @@ final class UserLoginLinkCommand extends Command
$loginLink = $loginLinkDetails->getUrl();
if ($input->getOption('password-reset') === true) {
$user->setPasswordRequestedAt(new \DateTime());
$user->markPasswordRequested();
$user->setRequiresPasswordReset(true);
$this->userRepository->saveUser($user);
}

View File

@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '2.1.0';
public const VERSION = '2.2.0';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 20100;
public const VERSION_ID = 20200;
/**
* The software name
*/

View File

@@ -17,7 +17,6 @@ use App\Event\EmailPasswordResetEvent;
use App\Form\PasswordResetForm;
use App\User\LoginManager;
use App\User\UserService;
use DateTime;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Form\FormInterface;
@@ -83,7 +82,7 @@ final class PasswordResetController extends AbstractController
// this will finally send the email
$this->eventDispatcher->dispatch(new EmailEvent($event->getEmail()));
$user->setPasswordRequestedAt(new DateTime());
$user->markPasswordRequested();
$this->userService->updateUser($user);
}
@@ -138,8 +137,7 @@ final class PasswordResetController extends AbstractController
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->setConfirmationToken(null);
$user->setPasswordRequestedAt(null);
$user->markPasswordResetted();
$user->setEnabled(true);
$this->userService->updateUser($user);

View File

@@ -188,8 +188,8 @@ class Timesheet implements EntityWithMetaFields, ExportableItem
#[ORM\Column(name: 'category', type: 'string', length: 10, nullable: false, options: ['default' => 'work'])]
#[Assert\NotNull]
private ?string $category = self::WORK;
#[ORM\Column(name: 'modified_at', type: 'datetime', nullable: true)]
private \DateTimeInterface $modifiedAt;
#[ORM\Column(name: 'modified_at', type: 'datetime_immutable', nullable: true)]
private \DateTimeImmutable $modifiedAt; // @phpstan-ignore-line - create migration and update all null values and then make it not null
/**
* Tags
*
@@ -586,12 +586,12 @@ class Timesheet implements EntityWithMetaFields, ExportableItem
return $this;
}
public function getModifiedAt(): \DateTimeInterface
public function getModifiedAt(): \DateTimeImmutable
{
return $this->modifiedAt;
}
public function setModifiedAt(\DateTimeInterface $dateTime): void
public function setModifiedAt(\DateTimeImmutable $dateTime): void
{
$this->modifiedAt = $dateTime;
}

View File

@@ -196,8 +196,8 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
#[ORM\Column(name: 'confirmation_token', type: 'string', length: 180, unique: true, nullable: true)]
#[Assert\Length(max: 180)]
private ?string $confirmationToken = null;
#[ORM\Column(name: 'password_requested_at', type: 'datetime', nullable: true)]
private ?\DateTime $passwordRequestedAt = null;
#[ORM\Column(name: 'password_requested_at', type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $passwordRequestedAt = null;
/**
* List of all role names
*/
@@ -958,26 +958,31 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this;
}
public function setConfirmationToken($confirmationToken): User
public function setConfirmationToken($confirmationToken): void
{
$this->confirmationToken = $confirmationToken;
return $this;
}
public function setPasswordRequestedAt(?\DateTime $date = null): User
public function markPasswordRequested(): void
{
$this->setPasswordRequestedAt(new \DateTimeImmutable('now', new \DateTimeZone($this->getTimezone())));
}
public function markPasswordResetted(): void
{
$this->setConfirmationToken(null);
$this->setPasswordRequestedAt(null);
}
public function setPasswordRequestedAt(?\DateTimeImmutable $date): void
{
$this->passwordRequestedAt = $date;
return $this;
}
/**
* Gets the timestamp that the user requested a password reset.
*
* @return DateTime|null
*/
public function getPasswordRequestedAt(): ?DateTime
public function getPasswordRequestedAt(): ?\DateTimeImmutable
{
return $this->passwordRequestedAt;
}

View File

@@ -41,9 +41,9 @@ class WorkingTime
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: 'approved_by', nullable: true, onDelete: 'SET NULL')]
private ?User $approvedBy = null;
#[ORM\Column(name: 'approved_at', type: 'datetime', nullable: true)]
#[ORM\Column(name: 'approved_at', type: 'datetime_immutable', nullable: true)]
#[Assert\NotNull]
private ?\DateTimeInterface $approvedAt = null;
private ?\DateTimeImmutable $approvedAt = null;
public function __construct(User $user, \DateTimeInterface $date)
{
@@ -96,12 +96,12 @@ class WorkingTime
$this->approvedBy = $approvedBy;
}
public function getApprovedAt(): ?\DateTimeInterface
public function getApprovedAt(): ?\DateTimeImmutable
{
return $this->approvedAt;
}
public function setApprovedAt(?\DateTimeInterface $approvedAt): void
public function setApprovedAt(?\DateTimeImmutable $approvedAt): void
{
$this->approvedAt = $approvedAt;
}

View File

@@ -9,15 +9,15 @@
namespace App\Model;
use DateTimeInterface;
use DateTimeImmutable;
class Day
{
public function __construct(private DateTimeInterface $day)
public function __construct(private DateTimeImmutable $day)
{
}
public function getDay(): DateTimeInterface
public function getDay(): DateTimeImmutable
{
return $this->day;
}

View File

@@ -20,7 +20,7 @@ class Month
public function __construct(private \DateTimeInterface $month)
{
$date = new \DateTimeImmutable($this->month->format('Y-m-01 00:00:00'));
$date = new \DateTimeImmutable($this->month->format('Y-m-01 00:00:00'), $month->getTimezone());
$start = $date->format('m');
while ($start === $date->format('m')) {
$day = $this->createDay($date);
@@ -29,7 +29,7 @@ class Month
}
}
protected function createDay(\DateTimeInterface $day): Day
protected function createDay(\DateTimeImmutable $day): Day
{
return new Day($day);
}

View File

@@ -20,17 +20,15 @@ class Year
public function __construct(private DateTimeInterface $month)
{
$monthDate = new \DateTimeImmutable();
$monthDate = $monthDate->setDate((int) $this->month->format('Y'), 1, 1);
$monthDate = $monthDate->setTime(1, 0);
$monthDate = new \DateTimeImmutable($this->month->format('Y-01-01 01:00:00'), $this->month->getTimezone());
for ($i = 1; $i < 13; $i++) {
$month = $this->createMonth($monthDate);
$this->setMonth($month);
$tmp = $this->createMonth($monthDate);
$this->setMonth($tmp);
$monthDate = $monthDate->add(new \DateInterval('P1M'));
}
}
protected function createMonth(\DateTimeInterface $month): Month
protected function createMonth(\DateTimeImmutable $month): Month
{
return new Month($month);
}

View File

@@ -31,14 +31,113 @@ final class InvoicePolicy implements SecurityPolicyInterface
$this->policy->addPolicy(new SecurityPolicy(
['block', 'if', 'for', 'set', 'extends'],
[
// Twig core filters
'map', 'escape', 'trans', 'default', 'nl2br', 'trim', 'raw',
'join', 'u', 'slice', 'date', 'month_name', 'first', 'country_name',
'replace', 'length', 'number_format', 'split',
// =================================================================
// vendor/twig/twig/src/Extension/CoreExtension.php
// Kimai filters
'md2html', 'desc2html', 'comment2html', 'comment1line', 'multiline_indent', 'nl2str',
'date_short', 'duration', 'amount', 'money', 'duration_decimal',
// formatting filters
'date',
'date_modify',
'format',
'replace',
'number_format',
'abs',
'round',
// encoding
'url_encode',
'json_encode',
'convert_encoding',
// string filters
'title',
'capitalize',
'upper',
'lower',
'striptags',
'trim',
'nl2br',
'spaceless',
// array helpers
'join',
'split',
'sort',
'merge',
'batch',
'column',
'filter',
'map',
'reduce',
// string/array filters
'reverse',
'length',
'slice',
'first',
'last',
// iteration and runtime
'default',
'keys',
// =================================================================
// vendor/twig/twig/src/Extension/EscaperExtension.php
'escape',
'e',
'raw',
// =================================================================
// vendor/symfony/twig-bridge/Extension/TranslationExtension.php
'trans',
// =================================================================
// vendor/twig/string-extra/StringExtension.php
'u',
'slug',
// =================================================================
// vendor/twig/intl-extra/IntlExtension.php
'country_name',
'currency_name',
'currency_symbol',
'language_name',
'locale_name',
'format_currency',
'format_number',
'format_*_number',
'format_datetime',
'format_date',
'format_time',
// =================================================================
// src/Twig/LocaleFormatExtensions.php
'month_name',
'day_name',
'date_short',
'date_time',
'date_full',
'date_format',
'date_weekday',
'time',
'duration',
'duration_decimal',
'money',
'amount',
// =================================================================
// src/Twig/RuntimeExtensions.php
'md2html',
'desc2html',
'comment2html',
'comment1line',
// =================================================================
// src/Twig/Extensions.php
'multiline_indent',
'color',
'font_contrast',
'default_color',
'nl2str',
],
[
PdfContext::class => ['setoption'],
@@ -46,11 +145,37 @@ final class InvoicePolicy implements SecurityPolicyInterface
],
[], // properties
[
// Twig core functions
'cycle', 'asset', 'range',
// =================================================================
// vendor/twig/twig/src/Extension/CoreExtension.php
'max',
'min',
'range',
'constant',
'cycle',
'random',
'date',
'asset',
'range',
// Kimai functions
'encore_entry_css_source', 'qr_code_data_uri', 'config',
// =================================================================
// vendor/symfony/twig-bridge/Extension/TranslationExtension.php
't',
// =================================================================
// vendor/symfony/webpack-encore-bundle/src/Twig/EntryFilesTwigExtension.php
'encore_entry_css_source',
// =================================================================
// vendor/symfony/twig-bridge/Extension/AssetExtension.php
'asset',
// =================================================================
// Twig/RuntimeExtensions.php
'qr_code_data_uri',
// =================================================================
// Twig/Configuration.php
'config',
]
));
}

View File

@@ -18,7 +18,7 @@ use App\Model\Month as BaseMonth;
*/
final class Month extends BaseMonth
{
public function __construct(\DateTimeInterface $month, private User $user)
public function __construct(\DateTimeImmutable $month, private User $user)
{
parent::__construct($month);
}
@@ -67,7 +67,7 @@ final class Month extends BaseMonth
return null;
}
protected function createDay(\DateTimeInterface $day): Day
protected function createDay(\DateTimeImmutable $day): Day
{
return new Day($day);
}

View File

@@ -28,7 +28,7 @@ final class Year extends BaseYear
return $this->user;
}
protected function createMonth(\DateTimeInterface $month): Month
protected function createMonth(\DateTimeImmutable $month): Month
{
return new Month($month, $this->user);
}

View File

@@ -117,7 +117,8 @@ final class WorkingTimeService
}
$workingTime->setApprovedBy($approver);
$workingTime->setApprovedAt($approvalDate);
// FIXME see calling method
$workingTime->setApprovedAt(\DateTimeImmutable::createFromInterface($approvalDate));
$this->workingTimeRepository->scheduleWorkingTimeUpdate($workingTime);
}