Release 2.0.34 (#4281)

This commit is contained in:
Kevin Papst
2023-09-17 22:32:01 +02:00
committed by GitHub
parent a6b237936e
commit de419350b2
29 changed files with 383 additions and 482 deletions

View File

@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '2.0.33';
public const VERSION = '2.0.34';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 20033;
public const VERSION_ID = 20034;
/**
* The software name
*/

View File

@@ -68,6 +68,7 @@ final class ContractController extends AbstractController
$page = new PageSetup('work_times');
$page->setHelp('contract.html');
$page->setActionName('contract');
$page->setActionPayload(['profile' => $profile, 'year' => $yearDate]);
$page->setPaginationForm($form);
// additional boxes by plugins

View File

@@ -16,6 +16,7 @@ use App\Reporting\ProjectDateRange\ProjectDateRangeForm;
use App\Reporting\ProjectDateRange\ProjectDateRangeQuery;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -24,7 +25,7 @@ final class ProjectDateRangeController extends AbstractController
#[Route(path: '/reporting/project_daterange', name: 'report_project_daterange', methods: ['GET', 'POST'])]
#[IsGranted('report:project')]
#[IsGranted(new Expression("is_granted('budget_any', 'project')"))]
public function __invoke(Request $request, ProjectStatisticService $service)
public function __invoke(Request $request, ProjectStatisticService $service): Response
{
$dateFactory = $this->getDateTimeFactory();
$user = $this->getUser();

View File

@@ -16,6 +16,7 @@ use App\Reporting\ProjectDetails\ProjectDetailsQuery;
use App\Utils\PageSetup;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -24,7 +25,7 @@ final class ProjectDetailsController extends AbstractController
#[Route(path: '/reporting/project_details', name: 'report_project_details', methods: ['GET'])]
#[IsGranted('report:project')]
#[IsGranted(new Expression("is_granted('details', 'project')"))]
public function __invoke(Request $request, ProjectStatisticService $service)
public function __invoke(Request $request, ProjectStatisticService $service): Response
{
$dateFactory = $this->getDateTimeFactory();
$user = $this->getUser();

View File

@@ -15,6 +15,7 @@ use App\Reporting\ProjectInactive\ProjectInactiveForm;
use App\Reporting\ProjectInactive\ProjectInactiveQuery;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -23,7 +24,7 @@ final class ProjectInactiveController extends AbstractController
#[Route(path: '/reporting/project_inactive', name: 'report_project_inactive', methods: ['GET', 'POST'])]
#[IsGranted('report:project')]
#[IsGranted(new Expression("is_granted('budget_any', 'project')"))]
public function __invoke(Request $request, ProjectStatisticService $service)
public function __invoke(Request $request, ProjectStatisticService $service): Response
{
$dateFactory = $this->getDateTimeFactory();
$user = $this->getUser();

View File

@@ -15,6 +15,7 @@ use App\Reporting\ProjectView\ProjectViewForm;
use App\Reporting\ProjectView\ProjectViewQuery;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -23,7 +24,7 @@ final class ProjectViewController extends AbstractController
#[Route(path: '/reporting/project_view', name: 'report_project_view', methods: ['GET', 'POST'])]
#[IsGranted('report:project')]
#[IsGranted(new Expression("is_granted('budget_any', 'project')"))]
public function __invoke(Request $request, ProjectStatisticService $service)
public function __invoke(Request $request, ProjectStatisticService $service): Response
{
$dateFactory = $this->getDateTimeFactory();
$user = $this->getUser();

View File

@@ -232,7 +232,7 @@ class Activity implements EntityWithMetaFields, EntityWithBudget
return $this;
}
public function addTeam(Team $team)
public function addTeam(Team $team): void
{
if ($this->teams->contains($team)) {
return;
@@ -242,7 +242,7 @@ class Activity implements EntityWithMetaFields, EntityWithBudget
$team->addActivity($this);
}
public function removeTeam(Team $team)
public function removeTeam(Team $team): void
{
if (!$this->teams->contains($team)) {
return;

View File

@@ -15,17 +15,17 @@ interface CommentInterface
public function getMessage(): ?string;
public function setMessage(string $message);
public function setMessage(string $message): void;
public function getCreatedBy(): ?User;
public function setCreatedBy(User $createdBy);
public function setCreatedBy(User $createdBy): void;
public function getCreatedAt(): ?\DateTime;
public function setCreatedAt(\DateTime $createdAt);
public function setCreatedAt(\DateTime $createdAt): void;
public function isPinned(): bool;
public function setPinned(bool $pinned);
public function setPinned(bool $pinned): void;
}

View File

@@ -440,7 +440,7 @@ class Customer implements EntityWithMetaFields, EntityWithBudget
return $this;
}
public function addTeam(Team $team)
public function addTeam(Team $team): void
{
if ($this->teams->contains($team)) {
return;
@@ -450,7 +450,7 @@ class Customer implements EntityWithMetaFields, EntityWithBudget
$team->addCustomer($this);
}
public function removeTeam(Team $team)
public function removeTeam(Team $team): void
{
if (!$this->teams->contains($team)) {
return;

View File

@@ -253,7 +253,7 @@ class Project implements EntityWithMetaFields, EntityWithBudget
* Make sure begin and end date have the correct timezone.
* This will be called once for each item after being loaded from the database.
*/
protected function localizeDates()
protected function localizeDates(): void
{
if ($this->localized) {
return;
@@ -392,7 +392,7 @@ class Project implements EntityWithMetaFields, EntityWithBudget
return $this;
}
public function addTeam(Team $team)
public function addTeam(Team $team): void
{
if ($this->teams->contains($team)) {
return;
@@ -402,7 +402,7 @@ class Project implements EntityWithMetaFields, EntityWithBudget
$team->addProject($this);
}
public function removeTeam(Team $team)
public function removeTeam(Team $team): void
{
if (!$this->teams->contains($team)) {
return;

View File

@@ -371,7 +371,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
* @param string $name
* @param bool|int|string|float|null $value
*/
public function setPreferenceValue(string $name, $value = null)
public function setPreferenceValue(string $name, $value = null): void
{
$pref = $this->getPreference($name);
@@ -421,7 +421,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->getLocale();
}
public function setLanguage(?string $language)
public function setLanguage(?string $language): void
{
if ($language === null) {
$language = User::DEFAULT_LANGUAGE;
@@ -449,7 +449,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return (string) $this->getPreferenceValue(UserPreference::SKIN, 'default', false);
}
public function setTimezone(?string $timezone)
public function setTimezone(?string $timezone): void
{
if ($timezone === null) {
$timezone = date_default_timezone_get();
@@ -536,7 +536,9 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
}
$this->memberships->removeElement($member);
$member->getTeam()->removeMember($member);
if ($member->getTeam() !== null) {
$member->getTeam()->removeMember($member);
}
$member->setUser(null);
$member->setTeam(null);
}
@@ -590,7 +592,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
public function hasTeamMember(User $user): bool
{
foreach ($this->memberships as $membership) {
if ($membership->getTeam()->hasUser($user)) {
if ($membership->getTeam() !== null && $membership->getTeam()->hasUser($user)) {
return true;
}
}
@@ -598,6 +600,34 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return false;
}
/**
* Use this function to check if the current user can read data from the given user.
*/
public function canSeeUser(User $user): bool
{
if ($user->getId() === $this->getId()) {
return true;
}
if ($this->canSeeAllData()) {
return true;
}
if (!$user->isEnabled()) {
return false;
}
if (!$this->isSystemAccount() && $user->isSystemAccount()) {
return false;
}
if ($this->isTeamleadOfUser($user)) {
return true;
}
return false;
}
/**
* List of all teams, this user is part of
*
@@ -673,6 +703,17 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return false;
}
public function isTeamleadOfUser(User $user): bool
{
foreach ($this->memberships as $membership) {
if ($membership->isTeamlead() && $membership->getTeam() !== null && $membership->getTeam()->hasUser($user)) {
return true;
}
}
return false;
}
public function canSeeAllData(): bool
{
return $this->isSuperAdmin() || true === $this->isAllowedToSeeAllData;
@@ -708,7 +749,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->hasRole(static::ROLE_ADMIN);
}
public function getDisplayName(): ?string
public function getDisplayName(): string
{
if (!empty($this->getAlias())) {
return $this->getAlias();
@@ -744,18 +785,16 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return $this->auth === null || $this->auth === self::AUTH_INTERNAL;
}
public function addRole(string $role)
public function addRole(string $role): void
{
$role = strtoupper($role);
if ($role === static::DEFAULT_ROLE) {
return $this;
return;
}
if (!\in_array($role, $this->roles, true)) {
$this->roles[] = $role;
}
return $this;
}
public function eraseCredentials(): void

View File

@@ -16,6 +16,8 @@ use App\Repository\UserRepository;
use App\Utils\Color;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -65,10 +67,6 @@ final class UserType extends AbstractType
'type' => 'integer',
'description' => 'User ID',
],
'attr' => [
'data-select-attributes' => 'color,title,username,initials,accountNumber,alias',
'data-renderer' => 'color',
],
]);
$resolver->setDefault('query_builder', function (Options $options) {
@@ -93,6 +91,14 @@ final class UserType extends AbstractType
});
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['attr'] = array_merge($view->vars['attr'], [
'data-select-attributes' => 'color,title,username,initials,accountNumber,alias',
'data-renderer' => 'color',
]);
}
public function getParent(): string
{
return EntityType::class;

View File

@@ -92,7 +92,7 @@ final class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
'entry.user_name' => $user->getUserIdentifier(),
'entry.user_title' => $user->getTitle() ?? '',
'entry.user_alias' => $user->getAlias() ?? '',
'entry.user_display' => $user->getDisplayName() ?? '',
'entry.user_display' => $user->getDisplayName(),
]);
foreach ($user->getVisiblePreferences() as $pref) {

View File

@@ -27,7 +27,7 @@ final class InvoiceModelUserHydrator implements InvoiceModelHydrator
'user.email' => $user->getEmail(),
'user.title' => $user->getTitle() ?? '',
'user.alias' => $user->getAlias() ?? '',
'user.display' => $user->getDisplayName() ?? '',
'user.display' => $user->getDisplayName(),
];
foreach ($user->getPreferences() as $metaField) {

View File

@@ -19,13 +19,13 @@ final class Pagination extends Pagerfanta
{
parent::__construct($adapter);
if ($query === null || !$query->isApiCall()) {
$this->setNormalizeOutOfRangePages(true);
}
if ($query !== null) {
$this->setMaxPerPage($query->getPageSize());
$this->setCurrentPage($query->getPage());
}
if ($query === null || !$query->isApiCall()) {
$this->setNormalizeOutOfRangePages(true);
}
}
}

View File

@@ -22,6 +22,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
final class UserVoter extends Voter
{
private const ALLOWED_ATTRIBUTES = [
'access_user',
'view',
'edit',
'roles',
@@ -62,10 +63,18 @@ final class UserVoter extends Voter
return false;
}
if (!($subject instanceof User)) {
return false;
}
if ($attribute === 'contract') {
return $this->permissionManager->hasRolePermission($user, 'contract_other_profile');
}
if ($attribute === 'access_user') {
return $user->canSeeUser($subject);
}
if ($attribute === 'view_team_member') {
if ($subject->getId() !== $user->getId()) {
return false;