added team permissions (#996)
This commit is contained in:
@@ -11,6 +11,7 @@ namespace App\Repository;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Model\ActivityStatistic;
|
||||
use App\Repository\Loader\ActivityLoader;
|
||||
use App\Repository\Paginator\LoaderPaginator;
|
||||
@@ -81,6 +82,47 @@ class ActivityRepository extends EntityRepository
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
|
||||
{
|
||||
// make sure that all queries without a user see all projects
|
||||
if (null === $user && empty($teams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that admins see all activities
|
||||
if (null !== $user && ($user->isSuperAdmin() || $user->isAdmin())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $user) {
|
||||
$teams = array_merge($teams, $user->getTeams()->toArray());
|
||||
}
|
||||
|
||||
$qb->leftJoin('p.teams', 'teams')
|
||||
->leftJoin('c.teams', 'c_teams');
|
||||
|
||||
if (empty($teams)) {
|
||||
$qb->andWhere($qb->expr()->isNull('c_teams'));
|
||||
$qb->andWhere($qb->expr()->isNull('teams'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$orProject = $qb->expr()->orX(
|
||||
$qb->expr()->isNull('teams'),
|
||||
$qb->expr()->isMemberOf(':teams', 'p.teams')
|
||||
);
|
||||
$qb->andWhere($orProject);
|
||||
|
||||
$orCustomer = $qb->expr()->orX(
|
||||
$qb->expr()->isNull('c_teams'),
|
||||
$qb->expr()->isMemberOf(':teams', 'c.teams')
|
||||
);
|
||||
$qb->andWhere($orCustomer);
|
||||
|
||||
$qb->setParameter('teams', $teams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.1
|
||||
*/
|
||||
@@ -181,16 +223,11 @@ class ActivityRepository extends EntityRepository
|
||||
$qb
|
||||
->select('a')
|
||||
->from(Activity::class, 'a')
|
||||
->leftJoin('a.project', 'p')
|
||||
->leftJoin('p.customer', 'c')
|
||||
->addOrderBy('a.' . $query->getOrderBy(), $query->getOrder())
|
||||
;
|
||||
|
||||
if (!$query->isGlobalsOnly()) {
|
||||
$qb
|
||||
->leftJoin('a.project', 'p')
|
||||
->leftJoin('p.customer', 'c')
|
||||
;
|
||||
}
|
||||
|
||||
$where = $qb->expr()->andX();
|
||||
|
||||
if (in_array($query->getVisibility(), [ActivityQuery::SHOW_VISIBLE, ActivityQuery::SHOW_HIDDEN])) {
|
||||
@@ -239,6 +276,8 @@ class ActivityRepository extends EntityRepository
|
||||
$qb->andWhere($where);
|
||||
}
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getCurrentUser());
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Model\CustomerStatistic;
|
||||
use App\Repository\Loader\CustomerLoader;
|
||||
use App\Repository\Paginator\LoaderPaginator;
|
||||
@@ -107,8 +108,41 @@ class CustomerRepository extends EntityRepository
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
|
||||
{
|
||||
// make sure that all queries without a user see all customers
|
||||
if (null === $user && empty($teams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that admins see all customers
|
||||
if (null !== $user && ($user->isSuperAdmin() || $user->isAdmin())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $user) {
|
||||
$teams = array_merge($teams, $user->getTeams()->toArray());
|
||||
}
|
||||
|
||||
$qb->leftJoin('c.teams', 'teams');
|
||||
|
||||
if (empty($teams)) {
|
||||
$qb->andWhere($qb->expr()->isNull('teams'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$or = $qb->expr()->orX(
|
||||
$qb->expr()->isNull('teams'),
|
||||
$qb->expr()->isMemberOf(':teams', 'c.teams')
|
||||
);
|
||||
$qb->andWhere($or);
|
||||
|
||||
$qb->setParameter('teams', $teams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.1
|
||||
* @deprecated since 1.1 - don't use this method, it ignores team permission checks
|
||||
*/
|
||||
public function builderForEntityType($customer)
|
||||
{
|
||||
@@ -145,6 +179,8 @@ class CustomerRepository extends EntityRepository
|
||||
$qb->setParameter('ignored', $query->getCustomerToIgnore());
|
||||
}
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getUser(), $query->getTeams());
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
@@ -152,7 +188,7 @@ class CustomerRepository extends EntityRepository
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->select('c', 'meta')
|
||||
$qb->select('c')
|
||||
->from(Customer::class, 'c')
|
||||
->leftJoin('c.meta', 'meta')
|
||||
->orderBy('c.' . $query->getOrderBy(), $query->getOrder());
|
||||
@@ -165,6 +201,8 @@ class CustomerRepository extends EntityRepository
|
||||
$qb->setParameter('visible', false, \PDO::PARAM_BOOL);
|
||||
}
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,27 @@ final class ActivityIdLoader implements LoaderInterface
|
||||
->andWhere($qb->expr()->in('p.id', $projectIds))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL a.{id}', 'PARTIAL project.{id}', 'teams', 'teamlead')
|
||||
->from(Activity::class, 'a')
|
||||
->leftJoin('a.project', 'project')
|
||||
->leftJoin('project.teams', 'teams')
|
||||
->leftJoin('teams.teamlead', 'teamlead')
|
||||
->andWhere($qb->expr()->in('a.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL a.{id}', 'PARTIAL project.{id}', 'PARTIAL customer.{id}', 'teams', 'teamlead')
|
||||
->from(Activity::class, 'a')
|
||||
->leftJoin('a.project', 'project')
|
||||
->leftJoin('project.customer', 'customer')
|
||||
->leftJoin('customer.teams', 'teams')
|
||||
->leftJoin('teams.teamlead', 'teamlead')
|
||||
->andWhere($qb->expr()->in('a.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,5 +42,14 @@ final class CustomerIdLoader implements LoaderInterface
|
||||
->andWhere($qb->expr()->in('c.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL c.{id}', 'teams', 'teamlead')
|
||||
->from(Customer::class, 'c')
|
||||
->leftJoin('c.teams', 'teams')
|
||||
->leftJoin('teams.teamlead', 'teamlead')
|
||||
->andWhere($qb->expr()->in('c.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,24 @@ final class ProjectIdLoader implements LoaderInterface
|
||||
->andWhere($qb->expr()->in('p.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL p.{id}', 'teams', 'teamlead')
|
||||
->from(Project::class, 'p')
|
||||
->leftJoin('p.teams', 'teams')
|
||||
->leftJoin('teams.teamlead', 'teamlead')
|
||||
->andWhere($qb->expr()->in('p.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL p.{id}', 'PARTIAL customer.{id}', 'teams', 'teamlead')
|
||||
->from(Project::class, 'p')
|
||||
->leftJoin('p.customer', 'customer')
|
||||
->leftJoin('customer.teams', 'teams')
|
||||
->leftJoin('teams.teamlead', 'teamlead')
|
||||
->andWhere($qb->expr()->in('p.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
46
src/Repository/Loader/TeamIdLoader.php
Normal file
46
src/Repository/Loader/TeamIdLoader.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Repository\Loader;
|
||||
|
||||
use App\Entity\Team;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class TeamIdLoader implements LoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $ids
|
||||
*/
|
||||
public function loadResults(array $ids): void
|
||||
{
|
||||
if (empty($ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$em = $this->entityManager;
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL t.{id}', 'users')
|
||||
->from(Team::class, 't')
|
||||
->leftJoin('t.users', 'users')
|
||||
->andWhere($qb->expr()->in('t.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
38
src/Repository/Loader/TeamLoader.php
Normal file
38
src/Repository/Loader/TeamLoader.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Repository\Loader;
|
||||
|
||||
use App\Entity\Team;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class TeamLoader implements LoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var TeamIdLoader
|
||||
*/
|
||||
private $loader;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->loader = new TeamIdLoader($entityManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Team[] $teams
|
||||
*/
|
||||
public function loadResults(array $teams): void
|
||||
{
|
||||
$ids = array_map(function (Team $team) {
|
||||
return $team->getId();
|
||||
}, $teams);
|
||||
|
||||
$this->loader->loadResults($ids);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace App\Repository;
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Model\ProjectStatistic;
|
||||
use App\Repository\Loader\ProjectLoader;
|
||||
use App\Repository\Paginator\LoaderPaginator;
|
||||
@@ -91,8 +92,49 @@ class ProjectRepository extends EntityRepository
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
|
||||
{
|
||||
// make sure that all queries without a user see all projects
|
||||
if (null === $user && empty($teams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that admins see all projects
|
||||
if (null !== $user && ($user->isSuperAdmin() || $user->isAdmin())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $user) {
|
||||
$teams = array_merge($teams, $user->getTeams()->toArray());
|
||||
}
|
||||
|
||||
$qb->leftJoin('p.teams', 'teams')
|
||||
->leftJoin('c.teams', 'c_teams');
|
||||
|
||||
if (empty($teams)) {
|
||||
$qb->andWhere($qb->expr()->isNull('c_teams'));
|
||||
$qb->andWhere($qb->expr()->isNull('teams'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$orProject = $qb->expr()->orX(
|
||||
$qb->expr()->isNull('teams'),
|
||||
$qb->expr()->isMemberOf(':teams', 'p.teams')
|
||||
);
|
||||
$qb->andWhere($orProject);
|
||||
|
||||
$orCustomer = $qb->expr()->orX(
|
||||
$qb->expr()->isNull('c_teams'),
|
||||
$qb->expr()->isMemberOf(':teams', 'c.teams')
|
||||
);
|
||||
$qb->andWhere($orCustomer);
|
||||
|
||||
$qb->setParameter('teams', $teams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.1
|
||||
* @deprecated since 1.1 - don't use this method, it ignores team permission checks
|
||||
*/
|
||||
public function builderForEntityType($project, $customer)
|
||||
{
|
||||
@@ -114,7 +156,7 @@ class ProjectRepository extends EntityRepository
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb
|
||||
->select('p', 'c')
|
||||
->select('p')
|
||||
->from(Project::class, 'p')
|
||||
->leftJoin('p.customer', 'c')
|
||||
->addOrderBy('c.name', 'ASC')
|
||||
@@ -140,6 +182,8 @@ class ProjectRepository extends EntityRepository
|
||||
$qb->setParameter('ignored', $query->getProjectToIgnore());
|
||||
}
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getUser(), $query->getTeams());
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
@@ -150,11 +194,11 @@ class ProjectRepository extends EntityRepository
|
||||
$qb
|
||||
->select('p')
|
||||
->from(Project::class, 'p')
|
||||
->leftJoin('p.customer', 'c')
|
||||
;
|
||||
|
||||
if (in_array($query->getVisibility(), [ProjectQuery::SHOW_VISIBLE, ProjectQuery::SHOW_HIDDEN])) {
|
||||
$qb
|
||||
->leftJoin('p.customer', 'c')
|
||||
->andWhere($qb->expr()->eq('p.visible', ':visible'))
|
||||
->andWhere($qb->expr()->eq('c.visible', ':customer_visible'))
|
||||
;
|
||||
@@ -173,6 +217,8 @@ class ProjectRepository extends EntityRepository
|
||||
->setParameter('customer', $query->getCustomer());
|
||||
}
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getCurrentUser());
|
||||
|
||||
$qb->orderBy('p.' . $query->getOrderBy(), $query->getOrder());
|
||||
|
||||
return $qb;
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
namespace App\Repository\Query;
|
||||
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
|
||||
/**
|
||||
* Base class for advanced Repository queries.
|
||||
*/
|
||||
@@ -44,6 +47,45 @@ class BaseQuery
|
||||
* @var string
|
||||
*/
|
||||
private $resultType = self::RESULT_TYPE_PAGER;
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var Team[]
|
||||
*/
|
||||
private $teams = [];
|
||||
|
||||
public function addTeam(Team $team): self
|
||||
{
|
||||
$this->teams[$team->getId()] = $team;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Team[]
|
||||
*/
|
||||
public function getTeams(): array
|
||||
{
|
||||
return array_values($this->teams);
|
||||
}
|
||||
|
||||
public function getCurrentUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return $this
|
||||
*/
|
||||
public function setCurrentUser(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
namespace App\Repository\Query;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
|
||||
/**
|
||||
* Can be used for advanced queries with the: CustomerRepository
|
||||
@@ -24,6 +26,14 @@ final class CustomerFormTypeQuery
|
||||
* @var Customer|null
|
||||
*/
|
||||
private $customerToIgnore;
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var array<Team>
|
||||
*/
|
||||
private $teams = [];
|
||||
|
||||
/**
|
||||
* @param Customer|int|null $customer
|
||||
@@ -33,6 +43,33 @@ final class CustomerFormTypeQuery
|
||||
$this->customer = $customer;
|
||||
}
|
||||
|
||||
public function addTeam(Team $team): CustomerFormTypeQuery
|
||||
{
|
||||
$this->teams[$team->getId()] = $team;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Team[]
|
||||
*/
|
||||
public function getTeams(): array
|
||||
{
|
||||
return array_values($this->teams);
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): CustomerFormTypeQuery
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer|int|null
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace App\Repository\Query;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
|
||||
final class ProjectFormTypeQuery
|
||||
{
|
||||
@@ -26,6 +28,14 @@ final class ProjectFormTypeQuery
|
||||
* @var Project|null
|
||||
*/
|
||||
private $projectToIgnore;
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var array<Team>
|
||||
*/
|
||||
private $teams = [];
|
||||
|
||||
/**
|
||||
* @param Project|int|null $project
|
||||
@@ -37,6 +47,33 @@ final class ProjectFormTypeQuery
|
||||
$this->customer = $customer;
|
||||
}
|
||||
|
||||
public function addTeam(Team $team): ProjectFormTypeQuery
|
||||
{
|
||||
$this->teams[$team->getId()] = $team;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Team[]
|
||||
*/
|
||||
public function getTeams(): array
|
||||
{
|
||||
return array_values($this->teams);
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): ProjectFormTypeQuery
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer|int|null
|
||||
*/
|
||||
|
||||
@@ -11,4 +11,8 @@ namespace App\Repository\Query;
|
||||
|
||||
class TagQuery extends BaseQuery
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->setOrderBy('name');
|
||||
}
|
||||
}
|
||||
|
||||
18
src/Repository/Query/TeamQuery.php
Normal file
18
src/Repository/Query/TeamQuery.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Repository\Query;
|
||||
|
||||
class TeamQuery extends BaseQuery
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->setOrderBy('name');
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class TimesheetQuery extends ActivityQuery
|
||||
/**
|
||||
* @var User|null
|
||||
*/
|
||||
protected $user;
|
||||
protected $timesheetUser;
|
||||
/**
|
||||
* @var Activity|null
|
||||
*/
|
||||
@@ -63,7 +63,7 @@ class TimesheetQuery extends ActivityQuery
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
return $this->timesheetUser;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +72,7 @@ class TimesheetQuery extends ActivityQuery
|
||||
*/
|
||||
public function setUser($user = null)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->timesheetUser = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -256,7 +256,7 @@ class TimesheetQuery extends ActivityQuery
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->user !== null) {
|
||||
if ($this->timesheetUser !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
55
src/Repository/Query/UserFormTypeQuery.php
Normal file
55
src/Repository/Query/UserFormTypeQuery.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Repository\Query;
|
||||
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
|
||||
/**
|
||||
* Can be used for pre-filling form types with the: UserRepository
|
||||
*/
|
||||
final class UserFormTypeQuery
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var array<Team>
|
||||
*/
|
||||
private $teams = [];
|
||||
|
||||
public function addTeam(Team $team): UserFormTypeQuery
|
||||
{
|
||||
$this->teams[$team->getId()] = $team;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Team[]
|
||||
*/
|
||||
public function getTeams(): array
|
||||
{
|
||||
return array_values($this->teams);
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): UserFormTypeQuery
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,26 @@
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Tag;
|
||||
use App\Repository\Query\TagQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Pagerfanta\Adapter\DoctrineORMAdapter;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
|
||||
class TagRepository extends EntityRepository
|
||||
{
|
||||
use RepositoryTrait;
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @throws ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function deleteTag(Tag $tag)
|
||||
{
|
||||
$entityManager = $this->getEntityManager();
|
||||
$entityManager->remove($tag);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find ids of the given tagNames separated by comma
|
||||
@@ -68,7 +82,7 @@ class TagRepository extends EntityRepository
|
||||
* - amount
|
||||
*
|
||||
* @param TagQuery $query
|
||||
* @return array|\Doctrine\ORM\QueryBuilder|\Pagerfanta\Pagerfanta
|
||||
* @return Pagerfanta
|
||||
*/
|
||||
public function getTagCount(TagQuery $query)
|
||||
{
|
||||
@@ -79,9 +93,12 @@ class TagRepository extends EntityRepository
|
||||
->leftJoin('tag.timesheets', 'timesheets')
|
||||
->addGroupBy('tag.id')
|
||||
->addGroupBy('tag.name')
|
||||
->orderBy('tag.name')
|
||||
;
|
||||
->orderBy('tag.' . $query->getOrderBy(), $query->getOrder());
|
||||
|
||||
return $this->getBaseQueryResult($qb, $query);
|
||||
$paginator = new Pagerfanta(new DoctrineORMAdapter($qb->getQuery(), false));
|
||||
$paginator->setMaxPerPage($query->getPageSize());
|
||||
$paginator->setCurrentPage($query->getPage());
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
}
|
||||
|
||||
99
src/Repository/TeamRepository.php
Normal file
99
src/Repository/TeamRepository.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Team;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Repository\Loader\TeamLoader;
|
||||
use App\Repository\Paginator\LoaderPaginator;
|
||||
use App\Repository\Paginator\PaginatorInterface;
|
||||
use App\Repository\Query\TeamQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
|
||||
class TeamRepository extends EntityRepository
|
||||
{
|
||||
/**
|
||||
* @param Team $team
|
||||
* @throws ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function saveTeam(Team $team)
|
||||
{
|
||||
$entityManager = $this->getEntityManager();
|
||||
$entityManager->persist($team);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Team $team
|
||||
* @throws ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function deleteTeam(Team $team)
|
||||
{
|
||||
$entityManager = $this->getEntityManager();
|
||||
$entityManager->remove($team);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
public function getPagerfantaForQuery(TeamQuery $query): Pagerfanta
|
||||
{
|
||||
$paginator = new Pagerfanta($this->getPaginatorForQuery($query));
|
||||
$paginator->setMaxPerPage($query->getPageSize());
|
||||
$paginator->setCurrentPage($query->getPage());
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
protected function getPaginatorForQuery(TeamQuery $query): PaginatorInterface
|
||||
{
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
$qb
|
||||
->resetDQLPart('select')
|
||||
->resetDQLPart('orderBy')
|
||||
->select($qb->expr()->countDistinct('t.id'))
|
||||
;
|
||||
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
||||
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
|
||||
return new LoaderPaginator(new TeamLoader($qb->getEntityManager()), $qb, $counter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TeamQuery $query
|
||||
* @return Timesheet[]
|
||||
*/
|
||||
public function getTeamsForQuery(TeamQuery $query): iterable
|
||||
{
|
||||
// this is using the paginator internally, as it will load all joined entities into the working unit
|
||||
// do not "optimize" to use the query directly, as it would results in hundreds of additional lazy queries
|
||||
$paginator = $this->getPaginatorForQuery($query);
|
||||
|
||||
return $paginator->getAll();
|
||||
}
|
||||
|
||||
private function getQueryBuilderForQuery(TeamQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb
|
||||
->select('t')
|
||||
->from(Team::class, 't')
|
||||
;
|
||||
|
||||
$qb->orderBy('t.' . $query->getOrderBy(), $query->getOrder());
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
||||
@@ -456,6 +456,36 @@ class TimesheetRepository extends EntityRepository
|
||||
return $counter;
|
||||
}
|
||||
|
||||
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
|
||||
{
|
||||
// make sure that all queries without a user see all projects
|
||||
if (null === $user && empty($teams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($teams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that admins see all timesheet records
|
||||
if (null !== $user && ($user->isSuperAdmin() || $user->isAdmin())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qb
|
||||
->leftJoin('p.customer', 'c')
|
||||
->leftJoin('p.teams', 'teams')
|
||||
->leftJoin('c.teams', 'c_teams');
|
||||
|
||||
$orTeam = $qb->expr()->orX(
|
||||
$qb->expr()->isMemberOf(':teams', 'p.teams'),
|
||||
$qb->expr()->isMemberOf(':teams', 'c.teams')
|
||||
);
|
||||
$qb->andWhere($orTeam);
|
||||
|
||||
$qb->setParameter('teams', $teams);
|
||||
}
|
||||
|
||||
public function getPagerfantaForQuery(TimesheetQuery $query): Pagerfanta
|
||||
{
|
||||
$paginator = new Pagerfanta($this->getPaginatorForQuery($query));
|
||||
@@ -500,11 +530,43 @@ class TimesheetRepository extends EntityRepository
|
||||
$qb
|
||||
->select('t')
|
||||
->from(Timesheet::class, 't')
|
||||
->leftJoin('t.project', 'p')
|
||||
;
|
||||
|
||||
$user = [];
|
||||
if (null !== $query->getUser()) {
|
||||
$qb->andWhere('t.user = :user')
|
||||
->setParameter('user', $query->getUser());
|
||||
$user[] = $query->getUser();
|
||||
}
|
||||
|
||||
if (null === $query->getUser() && null !== $query->getCurrentUser()) {
|
||||
$currentUser = $query->getCurrentUser();
|
||||
|
||||
if (!$currentUser->isSuperAdmin() && !$currentUser->isAdmin()) {
|
||||
foreach ($currentUser->getTeams() as $team) {
|
||||
if ($currentUser->isTeamleadOf($team)) {
|
||||
$query->addTeam($team);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($query->getTeams())) {
|
||||
foreach ($query->getTeams() as $team) {
|
||||
$user = array_merge($user, $team->getUsers()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
$user = array_map(function ($user) {
|
||||
if ($user instanceof User) {
|
||||
return $user->getId();
|
||||
}
|
||||
|
||||
return $user;
|
||||
}, $user);
|
||||
$user = array_unique($user);
|
||||
|
||||
if (!empty($user)) {
|
||||
$qb->andWhere($qb->expr()->in('t.user', $user));
|
||||
}
|
||||
|
||||
if (null !== $query->getBegin()) {
|
||||
@@ -539,7 +601,6 @@ class TimesheetRepository extends EntityRepository
|
||||
$qb->andWhere('t.project = :project')
|
||||
->setParameter('project', $query->getProject());
|
||||
} elseif (null !== $query->getCustomer()) {
|
||||
$qb->join('t.project', 'p');
|
||||
$qb->andWhere('p.customer = :customer')
|
||||
->setParameter('customer', $query->getCustomer());
|
||||
}
|
||||
@@ -551,6 +612,8 @@ class TimesheetRepository extends EntityRepository
|
||||
->setParameter('tags', $query->getTags());
|
||||
}
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
|
||||
|
||||
$qb->orderBy('t.' . $query->getOrderBy(), $query->getOrder());
|
||||
|
||||
return $qb;
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\Query\UserFormTypeQuery;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
|
||||
|
||||
class UserRepository extends EntityRepository implements UserLoaderInterface
|
||||
@@ -26,12 +28,28 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to fetch the currently logged-in user.
|
||||
*
|
||||
* @param int $id
|
||||
* @return null|User
|
||||
*/
|
||||
public function getUserById($id): ?User
|
||||
{
|
||||
return $this->find($id);
|
||||
try {
|
||||
return $this->createQueryBuilder('u')
|
||||
->select('u', 'p', 't', 'tu', 'tl')
|
||||
->leftJoin('u.preferences', 'p')
|
||||
->leftJoin('u.teams', 't')
|
||||
->leftJoin('t.users', 'tu')
|
||||
->leftJoin('t.teamlead', 'tl')
|
||||
->where('u.id = :id')
|
||||
->setParameter('id', $id)
|
||||
->getQuery()
|
||||
->getSingleResult();
|
||||
} catch (\Exception $ex) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,12 +121,49 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
|
||||
public function loadUserByUsername($username)
|
||||
{
|
||||
return $this->createQueryBuilder('u')
|
||||
->select('u', 'p')
|
||||
->select('u', 'p', 't', 'tu', 'tl')
|
||||
->leftJoin('u.preferences', 'p')
|
||||
->leftJoin('u.teams', 't')
|
||||
->leftJoin('t.users', 'tu')
|
||||
->leftJoin('t.teamlead', 'tl')
|
||||
->where('u.username = :username')
|
||||
->orWhere('u.email = :username')
|
||||
->setParameter('username', $username)
|
||||
->getQuery()
|
||||
->getSingleResult();
|
||||
}
|
||||
|
||||
public function getQueryBuilderForFormType(UserFormTypeQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->createQueryBuilder('u');
|
||||
|
||||
$qb->andWhere($qb->expr()->eq('u.enabled', ':enabled'));
|
||||
$qb->setParameter('enabled', true, \PDO::PARAM_BOOL);
|
||||
|
||||
$qb->orderBy('u.username', 'ASC');
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getUser(), $query->getTeams());
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
|
||||
{
|
||||
// make sure that all queries without a user see all user
|
||||
if (null === $user && empty($teams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that admins see all user
|
||||
if (null !== $user && ($user->isSuperAdmin() || $user->isAdmin())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $user) {
|
||||
$qb->leftJoin('u.teams', 'teams')
|
||||
->leftJoin('teams.users', 'users')
|
||||
->andWhere('teams.teamlead = :id')
|
||||
->setParameter('id', $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user