Release 2.0.34 (#4281)
This commit is contained in:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user