Release 2.1.0 (#4321)

* fix deprecations
* remove unused config
* replace invalid annotation type with attribute
* use AsDoctrineListener to fix deprecation
* new ModifiedSubscriber to support custom logic and fix deprecation
* removed inheritdoc comment
* new ModifiedSubscriber to support custom logic and fix deprecation
* cleanup event dispatcher interface
* re-order annotation params
* one more doctrine based deprecation
* fix query to count active timesheets
* link to "all times" to identify active timesheets
* link icon instead of text
* fix "skin" translation in wizard
* use duration filter to show duration
* added login link command and controller
* bump tabler theme to 1.0
* added wizard to force password reset by user
* allow to configure that new accounts need to reset their password
* prevent uploading twig templates by default
* bump composer packages
* enable sandbox and basic security measures for custom twig templates for invoice and export
* bump to symfony 6.3.5
* allow to export single user reports to excel
* removed broken method to reload twig cache
* added api parameter to fetch user collection fully serialized
* allow to replace or append description via timesheet batch update
* show api username above form
This commit is contained in:
Kevin Papst
2023-10-19 11:21:50 +02:00
committed by GitHub
parent 7a5b12762a
commit 38e37f1c2e
210 changed files with 1784 additions and 1147 deletions

View File

@@ -42,9 +42,9 @@ use Symfony\Component\Validator\Constraints as Assert;
#[Exporter\Expose(name: 'username', label: 'username', exp: 'object.getUserIdentifier()')]
#[Exporter\Expose(name: 'timezone', label: 'timezone', exp: 'object.getTimezone()')]
#[Exporter\Expose(name: 'language', label: 'language', exp: 'object.getLanguage()')]
#[Exporter\Expose(name: 'last_login', label: 'lastLogin', exp: 'object.getLastLogin()', type: 'datetime')]
#[Exporter\Expose(name: 'roles', label: 'roles', exp: 'object.getRoles()', type: 'array')]
#[Exporter\Expose(name: 'active', label: 'active', exp: 'object.isEnabled()', type: 'boolean')]
#[Exporter\Expose(name: 'last_login', label: 'lastLogin', type: 'datetime', exp: 'object.getLastLogin()')]
#[Exporter\Expose(name: 'roles', label: 'roles', type: 'array', exp: 'object.getRoles()')]
#[Exporter\Expose(name: 'active', label: 'active', type: 'boolean', exp: 'object.isEnabled()')]
#[Constraints\User(groups: ['UserCreate', 'Registration', 'Default', 'Profile'])]
class User implements UserInterface, EquatableInterface, ThemeUserInterface, PasswordAuthenticatedUserInterface, TwoFactorInterface
{
@@ -127,15 +127,15 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
*
* @var Collection<UserPreference>|null
*/
#[ORM\OneToMany(targetEntity: UserPreference::class, mappedBy: 'user', cascade: ['persist'])]
#[ORM\OneToMany(mappedBy: 'user', targetEntity: UserPreference::class, cascade: ['persist'])]
private ?Collection $preferences = null;
/**
* List of all team memberships.
*
* @var Collection<TeamMember>
*/
#[ORM\OneToMany(targetEntity: TeamMember::class, mappedBy: 'user', fetch: 'LAZY', cascade: ['persist'], orphanRemoval: true)]
#[ORM\JoinColumn(onDelete: 'CASCADE', nullable: false)]
#[ORM\OneToMany(mappedBy: 'user', targetEntity: TeamMember::class, cascade: ['persist'], fetch: 'LAZY', orphanRemoval: true)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
#[Assert\NotNull]
#[Serializer\Expose]
#[Serializer\Groups(['User_Entity'])]
@@ -217,7 +217,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
#[ORM\Column(name: 'system_account', type: 'boolean', nullable: false, options: ['default' => false])]
private bool $systemAccount = false;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(onDelete: 'SET NULL', nullable: true)]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
#[Serializer\Expose]
#[Serializer\Groups(['User_Entity'])]
#[OA\Property(ref: '#/components/schemas/User')]
@@ -297,6 +297,14 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this;
}
#[Serializer\VirtualProperty]
#[Serializer\SerializedName('apiToken')]
#[Serializer\Groups(['Default'])]
public function hasApiToken(): bool
{
return $this->apiToken !== null;
}
public function getPlainApiToken(): ?string
{
return $this->plainApiToken;
@@ -836,9 +844,6 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->email !== null;
}
/**
* {@inheritdoc}
*/
public function getPassword(): ?string
{
return $this->password;
@@ -864,9 +869,6 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->confirmationToken;
}
/**
* {@inheritdoc}
*/
public function getRoles(): array
{
$roles = $this->roles;
@@ -963,7 +965,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this;
}
public function setPasswordRequestedAt(\DateTime $date = null): User
public function setPasswordRequestedAt(?\DateTime $date = null): User
{
$this->passwordRequestedAt = $date;
@@ -1113,6 +1115,20 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->getDisplayName();
}
public function requiresPasswordReset(): bool
{
if (!$this->isInternalUser() || !$this->isEnabled()) {
return false;
}
return $this->getPreferenceValue('__pw_reset__') === '1';
}
public function setRequiresPasswordReset(bool $require = true): void
{
$this->setPreferenceValue('__pw_reset__', ($require ? '1' : '0'));
}
public function hasSeenWizard(string $wizard): bool
{
$wizards = $this->getPreferenceValue('__wizards__');