toolbar dropdown and visibility improvements (#933)
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
<?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 Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Class AbstractRepository
|
||||
*/
|
||||
abstract class AbstractRepository extends EntityRepository
|
||||
{
|
||||
use RepositoryTrait;
|
||||
}
|
||||
@@ -10,16 +10,20 @@
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Model\ActivityStatistic;
|
||||
use App\Repository\Loader\ActivityLoader;
|
||||
use App\Repository\Paginator\LoaderPaginator;
|
||||
use App\Repository\Paginator\PaginatorInterface;
|
||||
use App\Repository\Query\ActivityFormTypeQuery;
|
||||
use App\Repository\Query\ActivityQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
|
||||
class ActivityRepository extends AbstractRepository
|
||||
class ActivityRepository extends EntityRepository
|
||||
{
|
||||
/**
|
||||
* @param Activity $activity
|
||||
@@ -33,15 +37,6 @@ class ActivityRepository extends AbstractRepository
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return null|Activity
|
||||
*/
|
||||
public function getById($id)
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|bool $visible
|
||||
* @return int
|
||||
@@ -89,67 +84,128 @@ class ActivityRepository extends AbstractRepository
|
||||
/**
|
||||
* Returns a query builder that is used for ActivityType and your own 'query_builder' option.
|
||||
*
|
||||
* @param Activity|string|null $activity
|
||||
* @param Project|string|null $project
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
* @param ActivityFormTypeQuery $query
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function builderForEntityType($activity = null, $project = null)
|
||||
{
|
||||
$query = new ActivityQuery();
|
||||
$query->setHiddenEntity($activity);
|
||||
$query->setResultType(ActivityQuery::RESULT_TYPE_QUERYBUILDER);
|
||||
$query->setProject($project);
|
||||
$query->setOrderGlobalsFirst(true);
|
||||
$query->setOrderBy('name');
|
||||
|
||||
if (null === $activity && $project === null) {
|
||||
$query->setGlobalsOnly(true);
|
||||
}
|
||||
|
||||
return $this->findByQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ActivityQuery $query
|
||||
* @return QueryBuilder|Pagerfanta|array
|
||||
*/
|
||||
public function findByQuery(ActivityQuery $query)
|
||||
public function getQueryBuilderForFormType(ActivityFormTypeQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->select('a', 'p', 'c')
|
||||
$qb->select('a')
|
||||
->from(Activity::class, 'a')
|
||||
->leftJoin('a.project', 'p')
|
||||
->leftJoin('p.customer', 'c');
|
||||
|
||||
if ($query->isOrderGlobalsFirst()) {
|
||||
$qb->orderBy('a.project', 'ASC');
|
||||
}
|
||||
|
||||
$qb->addOrderBy('a.' . $query->getOrderBy(), $query->getOrder());
|
||||
->addOrderBy('a.project', 'DESC')
|
||||
->addOrderBy('a.name', 'ASC')
|
||||
;
|
||||
|
||||
$where = $qb->expr()->andX();
|
||||
|
||||
if (ActivityQuery::SHOW_VISIBLE == $query->getVisibility()) {
|
||||
$where->add('a.visible = :visible');
|
||||
if (!$query->isExclusiveVisibility()) {
|
||||
$where->add('a.visible = :visible');
|
||||
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
|
||||
|
||||
if (!$query->isGlobalsOnly()) {
|
||||
$qb
|
||||
->addSelect('p')
|
||||
->addSelect('c')
|
||||
->leftJoin('a.project', 'p')
|
||||
->leftJoin('p.customer', 'c');
|
||||
|
||||
$where->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('c.visible', ':customer_visible'),
|
||||
$qb->expr()->isNull('c.visible')
|
||||
)
|
||||
);
|
||||
$where->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('p.visible', ':project_visible'),
|
||||
$qb->expr()->isNull('p.visible')
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('project_visible', true, \PDO::PARAM_BOOL);
|
||||
$qb->setParameter('customer_visible', true, \PDO::PARAM_BOOL);
|
||||
}
|
||||
|
||||
if ($query->isGlobalsOnly()) {
|
||||
$where->add($qb->expr()->isNull('a.project'));
|
||||
} elseif (null !== $query->getProject()) {
|
||||
$where->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('a.project', ':project'),
|
||||
$qb->expr()->isNull('a.project')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('project', $query->getProject());
|
||||
}
|
||||
|
||||
if (null !== $query->getActivityToIgnore()) {
|
||||
$qb->andWhere($qb->expr()->neq('a.id', ':ignored'));
|
||||
$qb->setParameter('ignored', $query->getActivityToIgnore());
|
||||
}
|
||||
|
||||
$or = $qb->expr()->orX();
|
||||
|
||||
// this must always be the last part before the or
|
||||
$or->add($where);
|
||||
|
||||
// this must always be the last part of the query
|
||||
/* @var Activity $entity */
|
||||
if (null !== $query->getActivity()) {
|
||||
$or->add($qb->expr()->eq('a.id', ':activity'));
|
||||
$qb->setParameter('activity', $query->getActivity());
|
||||
}
|
||||
|
||||
if ($or->count() > 0) {
|
||||
$qb->andWhere($or);
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function getQueryBuilderForQuery(ActivityQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb
|
||||
->select('a')
|
||||
->from(Activity::class, 'a')
|
||||
->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])) {
|
||||
if (!$query->isGlobalsOnly()) {
|
||||
$where->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('c.visible', ':visible'),
|
||||
$qb->expr()->eq('c.visible', ':customer_visible'),
|
||||
$qb->expr()->isNull('c.visible')
|
||||
)
|
||||
);
|
||||
$where->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('p.visible', ':visible'),
|
||||
$qb->expr()->eq('p.visible', ':project_visible'),
|
||||
$qb->expr()->isNull('p.visible')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('project_visible', true, \PDO::PARAM_BOOL);
|
||||
$qb->setParameter('customer_visible', true, \PDO::PARAM_BOOL);
|
||||
}
|
||||
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
|
||||
} elseif (ActivityQuery::SHOW_HIDDEN == $query->getVisibility()) {
|
||||
|
||||
$where->add('a.visible = :visible');
|
||||
$qb->setParameter('visible', false, \PDO::PARAM_BOOL);
|
||||
|
||||
if (ActivityQuery::SHOW_VISIBLE === $query->getVisibility()) {
|
||||
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
|
||||
} elseif (ActivityQuery::SHOW_HIDDEN === $query->getVisibility()) {
|
||||
$qb->setParameter('visible', false, \PDO::PARAM_BOOL);
|
||||
}
|
||||
}
|
||||
|
||||
if ($query->isGlobalsOnly()) {
|
||||
@@ -167,29 +223,48 @@ class ActivityRepository extends AbstractRepository
|
||||
$qb->setParameter('customer', $query->getCustomer());
|
||||
}
|
||||
|
||||
if (!empty($query->getIgnoredEntities())) {
|
||||
$qb->andWhere('a.id NOT IN(:ignored)');
|
||||
$qb->setParameter('ignored', $query->getIgnoredEntities());
|
||||
if ($where->count() > 0) {
|
||||
$qb->andWhere($where);
|
||||
}
|
||||
|
||||
$or = $qb->expr()->orX();
|
||||
return $qb;
|
||||
}
|
||||
|
||||
// this must always be the last part before the or
|
||||
$or->add($where);
|
||||
public function getPagerfantaForQuery(ActivityQuery $query): Pagerfanta
|
||||
{
|
||||
$paginator = new Pagerfanta($this->getPaginatorForQuery($query));
|
||||
$paginator->setMaxPerPage($query->getPageSize());
|
||||
$paginator->setCurrentPage($query->getPage());
|
||||
|
||||
// this must always be the last part of the query
|
||||
/** @var Activity $entity */
|
||||
$entity = $query->getHiddenEntity();
|
||||
if (null !== $entity) {
|
||||
$or->add($qb->expr()->eq('a.id', ':activity'));
|
||||
$qb->setParameter('activity', $entity);
|
||||
}
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
if ($or->count() > 0) {
|
||||
$qb->andWhere($or);
|
||||
}
|
||||
protected function getPaginatorForQuery(ActivityQuery $query): PaginatorInterface
|
||||
{
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
$qb
|
||||
->resetDQLPart('select')
|
||||
->resetDQLPart('orderBy')
|
||||
->select($qb->expr()->countDistinct('a.id'))
|
||||
;
|
||||
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
||||
|
||||
return $this->getBaseQueryResult($qb, $query);
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
|
||||
return new LoaderPaginator(new ActivityLoader($qb->getEntityManager()), $qb, $counter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ActivityQuery $query
|
||||
* @return Activity[]
|
||||
*/
|
||||
public function getActivitiesForQuery(ActivityQuery $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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,10 +12,11 @@ namespace App\Repository;
|
||||
use App\Configuration\ConfigLoaderInterface;
|
||||
use App\Entity\Configuration;
|
||||
use App\Form\Model\SystemConfiguration;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\Query;
|
||||
|
||||
class ConfigurationRepository extends AbstractRepository implements ConfigLoaderInterface
|
||||
class ConfigurationRepository extends EntityRepository implements ConfigLoaderInterface
|
||||
{
|
||||
/**
|
||||
* @param string $prefix
|
||||
|
||||
@@ -14,13 +14,18 @@ use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Model\CustomerStatistic;
|
||||
use App\Repository\Loader\CustomerLoader;
|
||||
use App\Repository\Paginator\LoaderPaginator;
|
||||
use App\Repository\Paginator\PaginatorInterface;
|
||||
use App\Repository\Query\CustomerFormTypeQuery;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
|
||||
class CustomerRepository extends AbstractRepository
|
||||
class CustomerRepository extends EntityRepository
|
||||
{
|
||||
/**
|
||||
* @param Customer $customer
|
||||
@@ -34,15 +39,6 @@ class CustomerRepository extends AbstractRepository
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return null|Customer
|
||||
*/
|
||||
public function getById($id)
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|bool $visible
|
||||
* @return int
|
||||
@@ -114,51 +110,88 @@ class CustomerRepository extends AbstractRepository
|
||||
/**
|
||||
* Returns a query builder that is used for CustomerType and your own 'query_builder' option.
|
||||
*
|
||||
* @param Customer|null $entity
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
* @param CustomerFormTypeQuery $query
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function builderForEntityType(Customer $entity = null)
|
||||
{
|
||||
$query = new CustomerQuery();
|
||||
$query->setHiddenEntity($entity);
|
||||
$query->setResultType(CustomerQuery::RESULT_TYPE_QUERYBUILDER);
|
||||
$query->setOrderBy('name');
|
||||
|
||||
return $this->findByQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CustomerQuery $query
|
||||
* @return QueryBuilder|Pagerfanta|array
|
||||
*/
|
||||
public function findByQuery(CustomerQuery $query)
|
||||
public function getQueryBuilderForFormType(CustomerFormTypeQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->select('c')
|
||||
->from(Customer::class, 'c')
|
||||
->orderBy('c.name', 'ASC');
|
||||
|
||||
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
|
||||
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
|
||||
|
||||
$customer = $query->getCustomer();
|
||||
if (null !== $customer) {
|
||||
$qb->orWhere('c.id = :customer')->setParameter('customer', $customer);
|
||||
}
|
||||
|
||||
if (null !== $query->getCustomerToIgnore()) {
|
||||
$qb->andWhere($qb->expr()->neq('c.id', ':ignored'));
|
||||
$qb->setParameter('ignored', $query->getCustomerToIgnore());
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function getQueryBuilderForQuery(CustomerQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->select('c', 'meta')
|
||||
->from(Customer::class, 'c')
|
||||
->leftJoin('c.meta', 'meta')
|
||||
->orderBy('c.' . $query->getOrderBy(), $query->getOrder());
|
||||
|
||||
if (CustomerQuery::SHOW_VISIBLE == $query->getVisibility()) {
|
||||
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
|
||||
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
|
||||
|
||||
/** @var Customer $entity */
|
||||
$entity = $query->getHiddenEntity();
|
||||
if (null !== $entity) {
|
||||
$qb->orWhere('c.id = :customer')->setParameter('customer', $entity);
|
||||
}
|
||||
} elseif (CustomerQuery::SHOW_HIDDEN == $query->getVisibility()) {
|
||||
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
|
||||
$qb->setParameter('visible', false, \PDO::PARAM_BOOL);
|
||||
}
|
||||
|
||||
if (!empty($query->getIgnoredEntities())) {
|
||||
$qb->andWhere('c.id NOT IN(:ignored)');
|
||||
$qb->setParameter('ignored', $query->getIgnoredEntities());
|
||||
}
|
||||
return $qb;
|
||||
}
|
||||
|
||||
return $this->getBaseQueryResult($qb, $query);
|
||||
public function getPagerfantaForQuery(CustomerQuery $query): Pagerfanta
|
||||
{
|
||||
$paginator = new Pagerfanta($this->getPaginatorForQuery($query));
|
||||
$paginator->setMaxPerPage($query->getPageSize());
|
||||
$paginator->setCurrentPage($query->getPage());
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
protected function getPaginatorForQuery(CustomerQuery $query): PaginatorInterface
|
||||
{
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
$qb
|
||||
->resetDQLPart('select')
|
||||
->resetDQLPart('orderBy')
|
||||
->select($qb->expr()->countDistinct('c.id'))
|
||||
;
|
||||
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
||||
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
|
||||
return new LoaderPaginator(new CustomerLoader($qb->getEntityManager()), $qb, $counter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CustomerQuery $query
|
||||
* @return Customer[]
|
||||
*/
|
||||
public function getCustomersForQuery(CustomerQuery $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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,15 +11,15 @@ namespace App\Repository;
|
||||
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Repository\Query\BaseQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
|
||||
/**
|
||||
* Class InvoiceTemplateRepository
|
||||
*/
|
||||
class InvoiceTemplateRepository extends AbstractRepository
|
||||
class InvoiceTemplateRepository extends EntityRepository
|
||||
{
|
||||
use RepositoryTrait;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
@@ -50,7 +50,7 @@ class InvoiceTemplateRepository extends AbstractRepository
|
||||
|
||||
$qb->select('t')
|
||||
->from(InvoiceTemplate::class, 't')
|
||||
->orderBy('t.id');
|
||||
->orderBy('t.name');
|
||||
|
||||
return $this->getBaseQueryResult($qb, $query);
|
||||
}
|
||||
|
||||
74
src/Repository/Loader/ActivityIdLoader.php
Normal file
74
src/Repository/Loader/ActivityIdLoader.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?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\Activity;
|
||||
use App\Entity\Project;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class ActivityIdLoader 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();
|
||||
$activities = $qb->select('PARTIAL a.{id}', 'project')
|
||||
->from(Activity::class, 'a')
|
||||
->leftJoin('a.project', 'project')
|
||||
->andWhere($qb->expr()->isNotNull('a.project'))
|
||||
->andWhere($qb->expr()->in('a.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL a.{id}', 'meta')
|
||||
->from(Activity::class, 'a')
|
||||
->leftJoin('a.meta', 'meta')
|
||||
->andWhere($qb->expr()->in('a.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
if (!empty($activities)) {
|
||||
$projectIds = array_map(function (Activity $activity) {
|
||||
if (null === $activity->getProject()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $activity->getProject()->getId();
|
||||
}, $activities);
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL p.{id}', 'customer')
|
||||
->from(Project::class, 'p')
|
||||
->leftJoin('p.customer', 'customer')
|
||||
->andWhere($qb->expr()->in('p.id', $projectIds))
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Repository/Loader/ActivityLoader.php
Normal file
38
src/Repository/Loader/ActivityLoader.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\Activity;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class ActivityLoader implements LoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var ActivityIdLoader
|
||||
*/
|
||||
private $loader;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->loader = new ActivityIdLoader($entityManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Activity[] $activities
|
||||
*/
|
||||
public function loadResults(array $activities): void
|
||||
{
|
||||
$ids = array_map(function (Activity $activity) {
|
||||
return $activity->getId();
|
||||
}, $activities);
|
||||
|
||||
$this->loader->loadResults($ids);
|
||||
}
|
||||
}
|
||||
46
src/Repository/Loader/CustomerIdLoader.php
Normal file
46
src/Repository/Loader/CustomerIdLoader.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\Customer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class CustomerIdLoader 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 c.{id}', 'meta')
|
||||
->from(Customer::class, 'c')
|
||||
->leftJoin('c.meta', 'meta')
|
||||
->andWhere($qb->expr()->in('c.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
38
src/Repository/Loader/CustomerLoader.php
Normal file
38
src/Repository/Loader/CustomerLoader.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\Customer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class CustomerLoader implements LoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var CustomerIdLoader
|
||||
*/
|
||||
private $loader;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->loader = new CustomerIdLoader($entityManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer[] $customers
|
||||
*/
|
||||
public function loadResults(array $customers): void
|
||||
{
|
||||
$ids = array_map(function (Customer $customer) {
|
||||
return $customer->getId();
|
||||
}, $customers);
|
||||
|
||||
$this->loader->loadResults($ids);
|
||||
}
|
||||
}
|
||||
54
src/Repository/Loader/ProjectIdLoader.php
Normal file
54
src/Repository/Loader/ProjectIdLoader.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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\Project;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class ProjectIdLoader 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 p.{id}', 'customer')
|
||||
->from(Project::class, 'p')
|
||||
->leftJoin('p.customer', 'customer')
|
||||
->andWhere($qb->expr()->in('p.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL p.{id}', 'meta')
|
||||
->from(Project::class, 'p')
|
||||
->leftJoin('p.meta', 'meta')
|
||||
->andWhere($qb->expr()->in('p.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
38
src/Repository/Loader/ProjectLoader.php
Normal file
38
src/Repository/Loader/ProjectLoader.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\Project;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final class ProjectLoader implements LoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var ProjectIdLoader
|
||||
*/
|
||||
private $loader;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->loader = new ProjectIdLoader($entityManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Project[] $projects
|
||||
*/
|
||||
public function loadResults(array $projects): void
|
||||
{
|
||||
$ids = array_map(function (Project $project) {
|
||||
return $project->getId();
|
||||
}, $projects);
|
||||
|
||||
$this->loader->loadResults($ids);
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,11 @@
|
||||
|
||||
namespace App\Repository\Paginator;
|
||||
|
||||
use App\Repository\Loader\TimesheetLoader;
|
||||
use App\Repository\Loader\LoaderInterface;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Pagerfanta\Adapter\AdapterInterface;
|
||||
|
||||
final class TimesheetPaginator implements AdapterInterface
|
||||
final class LoaderPaginator implements PaginatorInterface
|
||||
{
|
||||
/**
|
||||
* @var QueryBuilder
|
||||
@@ -25,15 +24,15 @@ final class TimesheetPaginator implements AdapterInterface
|
||||
*/
|
||||
private $results = 0;
|
||||
/**
|
||||
* @var TimesheetLoader
|
||||
* @var LoaderInterface
|
||||
*/
|
||||
private $loader;
|
||||
|
||||
public function __construct(QueryBuilder $query, int $results)
|
||||
public function __construct(LoaderInterface $loader, QueryBuilder $query, int $results)
|
||||
{
|
||||
$this->loader = $loader;
|
||||
$this->query = $query;
|
||||
$this->results = $results;
|
||||
$this->loader = new TimesheetLoader($query->getEntityManager());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,15 +43,6 @@ final class TimesheetPaginator implements AdapterInterface
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
private function getResults(Query $query)
|
||||
{
|
||||
$results = $query->execute();
|
||||
|
||||
$this->loader->loadResults($results);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -66,7 +56,16 @@ final class TimesheetPaginator implements AdapterInterface
|
||||
return $this->getResults($query);
|
||||
}
|
||||
|
||||
public function getAll()
|
||||
private function getResults(Query $query)
|
||||
{
|
||||
$results = $query->execute();
|
||||
|
||||
$this->loader->loadResults($results);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function getAll(): iterable
|
||||
{
|
||||
return $this->getResults($this->query->getQuery());
|
||||
}
|
||||
22
src/Repository/Paginator/PaginatorInterface.php
Normal file
22
src/Repository/Paginator/PaginatorInterface.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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\Paginator;
|
||||
|
||||
use Pagerfanta\Adapter\AdapterInterface;
|
||||
|
||||
interface PaginatorInterface extends AdapterInterface
|
||||
{
|
||||
/**
|
||||
* Returns all available results without pagination.
|
||||
*
|
||||
* @return iterable
|
||||
*/
|
||||
public function getAll(): iterable;
|
||||
}
|
||||
@@ -10,11 +10,15 @@
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Model\ProjectStatistic;
|
||||
use App\Repository\Loader\ProjectLoader;
|
||||
use App\Repository\Paginator\LoaderPaginator;
|
||||
use App\Repository\Paginator\PaginatorInterface;
|
||||
use App\Repository\Query\ProjectFormTypeQuery;
|
||||
use App\Repository\Query\ProjectQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
@@ -23,7 +27,7 @@ use Pagerfanta\Pagerfanta;
|
||||
/**
|
||||
* Class ProjectRepository
|
||||
*/
|
||||
class ProjectRepository extends AbstractRepository
|
||||
class ProjectRepository extends EntityRepository
|
||||
{
|
||||
/**
|
||||
* @param Project $project
|
||||
@@ -37,15 +41,6 @@ class ProjectRepository extends AbstractRepository
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return null|Project
|
||||
*/
|
||||
public function getById($id)
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|bool $visible
|
||||
* @return int
|
||||
@@ -99,53 +94,28 @@ class ProjectRepository extends AbstractRepository
|
||||
/**
|
||||
* Returns a query builder that is used for ProjectType and your own 'query_builder' option.
|
||||
*
|
||||
* @param Project|int|null $entity
|
||||
* @param Customer|int|null $customer
|
||||
* @return array|QueryBuilder|Pagerfanta
|
||||
* @param ProjectFormTypeQuery $query
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function builderForEntityType($entity = null, $customer = null)
|
||||
{
|
||||
$query = new ProjectQuery();
|
||||
$query->setHiddenEntity($entity);
|
||||
$query->setCustomer($customer);
|
||||
$query->setResultType(ProjectQuery::RESULT_TYPE_QUERYBUILDER);
|
||||
$query->setOrderBy('name');
|
||||
|
||||
return $this->findByQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProjectQuery $query
|
||||
* @return QueryBuilder|Pagerfanta|array
|
||||
*/
|
||||
public function findByQuery(ProjectQuery $query)
|
||||
public function getQueryBuilderForFormType(ProjectFormTypeQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
// if we join activities, the max-per-page limit will limit the list
|
||||
// due to the raised amount of rows by projects * activities
|
||||
$qb->select('p', 'c')
|
||||
$qb
|
||||
->select('p', 'c')
|
||||
->from(Project::class, 'p')
|
||||
->join('p.customer', 'c')
|
||||
->orderBy('p.' . $query->getOrderBy(), $query->getOrder());
|
||||
->leftJoin('p.customer', 'c')
|
||||
->addOrderBy('c.name', 'ASC')
|
||||
->addOrderBy('p.name', 'ASC')
|
||||
;
|
||||
|
||||
if (ProjectQuery::SHOW_VISIBLE == $query->getVisibility()) {
|
||||
if (!$query->isExclusiveVisibility()) {
|
||||
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
|
||||
}
|
||||
$qb->andWhere($qb->expr()->eq('p.visible', ':visible'));
|
||||
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
|
||||
$qb->andWhere($qb->expr()->eq('p.visible', ':visible'));
|
||||
$qb->andWhere($qb->expr()->eq('c.visible', ':customer_visible'));
|
||||
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
|
||||
$qb->setParameter('customer_visible', true, \PDO::PARAM_BOOL);
|
||||
|
||||
$entity = $query->getHiddenEntity();
|
||||
if (null !== $entity) {
|
||||
$qb->orWhere('p.id = :project')->setParameter('project', $entity);
|
||||
}
|
||||
|
||||
// TODO check for visibility of customer
|
||||
} elseif (ProjectQuery::SHOW_HIDDEN == $query->getVisibility()) {
|
||||
$qb->andWhere($qb->expr()->eq('p.visible', ':visible'));
|
||||
$qb->setParameter('visible', false, \PDO::PARAM_BOOL);
|
||||
// TODO check for visibility of customer
|
||||
if (null !== $query->getProject()) {
|
||||
$qb->orWhere('p.id = :project')->setParameter('project', $query->getProject());
|
||||
}
|
||||
|
||||
if (null !== $query->getCustomer()) {
|
||||
@@ -153,12 +123,85 @@ class ProjectRepository extends AbstractRepository
|
||||
->setParameter('customer', $query->getCustomer());
|
||||
}
|
||||
|
||||
if (!empty($query->getIgnoredEntities())) {
|
||||
$qb->andWhere('p.id NOT IN(:ignored)');
|
||||
$qb->setParameter('ignored', $query->getIgnoredEntities());
|
||||
if (null !== $query->getProjectToIgnore()) {
|
||||
$qb->andWhere($qb->expr()->neq('p.id', ':ignored'));
|
||||
$qb->setParameter('ignored', $query->getProjectToIgnore());
|
||||
}
|
||||
|
||||
return $this->getBaseQueryResult($qb, $query);
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function getQueryBuilderForQuery(ProjectQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb
|
||||
->select('p')
|
||||
->from(Project::class, 'p')
|
||||
;
|
||||
|
||||
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'))
|
||||
;
|
||||
|
||||
if (ProjectQuery::SHOW_VISIBLE === $query->getVisibility()) {
|
||||
$qb->setParameter('visible', true, \PDO::PARAM_BOOL);
|
||||
} elseif (ProjectQuery::SHOW_HIDDEN === $query->getVisibility()) {
|
||||
$qb->setParameter('visible', false, \PDO::PARAM_BOOL);
|
||||
}
|
||||
|
||||
$qb->setParameter('customer_visible', true, \PDO::PARAM_BOOL);
|
||||
}
|
||||
|
||||
if (null !== $query->getCustomer()) {
|
||||
$qb->andWhere('p.customer = :customer')
|
||||
->setParameter('customer', $query->getCustomer());
|
||||
}
|
||||
|
||||
$qb->orderBy('p.' . $query->getOrderBy(), $query->getOrder());
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function getPagerfantaForQuery(ProjectQuery $query): Pagerfanta
|
||||
{
|
||||
$paginator = new Pagerfanta($this->getPaginatorForQuery($query));
|
||||
$paginator->setMaxPerPage($query->getPageSize());
|
||||
$paginator->setCurrentPage($query->getPage());
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
private function getPaginatorForQuery(ProjectQuery $query): PaginatorInterface
|
||||
{
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
$qb
|
||||
->resetDQLPart('select')
|
||||
->resetDQLPart('orderBy')
|
||||
->select($qb->expr()->countDistinct('p.id'))
|
||||
;
|
||||
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
||||
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
|
||||
return new LoaderPaginator(new ProjectLoader($qb->getEntityManager()), $qb, $counter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProjectQuery $query
|
||||
* @return Project[]
|
||||
*/
|
||||
public function getProjectsForQuery(ProjectQuery $query): iterable
|
||||
{
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
$results = $qb->getQuery()->execute();
|
||||
$loader = new ProjectLoader($qb->getEntityManager());
|
||||
$loader->loadResults($results);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
97
src/Repository/Query/ActivityFormTypeQuery.php
Normal file
97
src/Repository/Query/ActivityFormTypeQuery.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?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\Activity;
|
||||
use App\Entity\Project;
|
||||
|
||||
final class ActivityFormTypeQuery
|
||||
{
|
||||
/**
|
||||
* @var Activity|int|null
|
||||
*/
|
||||
private $activity;
|
||||
/**
|
||||
* @var Project|int|null
|
||||
*/
|
||||
private $project;
|
||||
/**
|
||||
* @var Activity|null
|
||||
*/
|
||||
private $activityToIgnore;
|
||||
|
||||
/**
|
||||
* @param Activity|int|null $activity
|
||||
* @param Project|int|null $project
|
||||
*/
|
||||
public function __construct($activity = null, $project = null)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
$this->project = $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Activity|int|null
|
||||
*/
|
||||
public function getActivity()
|
||||
{
|
||||
return $this->activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Activity|int|null $activity
|
||||
* @return ActivityFormTypeQuery
|
||||
*/
|
||||
public function setActivity($activity): ActivityFormTypeQuery
|
||||
{
|
||||
$this->activity = $activity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project|int|null
|
||||
*/
|
||||
public function getProject()
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Project|int|null $project
|
||||
* @return ActivityFormTypeQuery
|
||||
*/
|
||||
public function setProject($project): ActivityFormTypeQuery
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Activity|null
|
||||
*/
|
||||
public function getActivityToIgnore(): ?Activity
|
||||
{
|
||||
return $this->activityToIgnore;
|
||||
}
|
||||
|
||||
public function setActivityToIgnore(Activity $activityToIgnore): ActivityFormTypeQuery
|
||||
{
|
||||
$this->activityToIgnore = $activityToIgnore;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isGlobalsOnly(): bool
|
||||
{
|
||||
return null === $this->activity && null === $this->project;
|
||||
}
|
||||
}
|
||||
@@ -19,29 +19,16 @@ class ActivityQuery extends ProjectQuery
|
||||
/**
|
||||
* @var Project|int|null
|
||||
*/
|
||||
protected $project;
|
||||
private $project;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $orderGlobalsFirst = false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $globalsOnly = false;
|
||||
private $globalsOnly = false;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isOrderGlobalsFirst(): bool
|
||||
public function __construct()
|
||||
{
|
||||
return $this->orderGlobalsFirst;
|
||||
}
|
||||
|
||||
public function setOrderGlobalsFirst(bool $orderGlobalsFirst): ActivityQuery
|
||||
{
|
||||
$this->orderGlobalsFirst = $orderGlobalsFirst;
|
||||
|
||||
return $this;
|
||||
parent::__construct();
|
||||
$this->setOrderBy('name');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,4 +68,24 @@ class ActivityQuery extends ProjectQuery
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDirty(): bool
|
||||
{
|
||||
if (parent::isDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->project !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->globalsOnly !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,29 +25,25 @@ class BaseQuery
|
||||
public const RESULT_TYPE_QUERYBUILDER = 'QueryBuilder';
|
||||
|
||||
/**
|
||||
* @var object|null
|
||||
* @var int
|
||||
*/
|
||||
protected $hiddenEntity;
|
||||
private $page = self::DEFAULT_PAGE;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $page = self::DEFAULT_PAGE;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $pageSize = self::DEFAULT_PAGESIZE;
|
||||
private $pageSize = self::DEFAULT_PAGESIZE;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $orderBy = 'id';
|
||||
private $orderBy = 'id';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $order = self::ORDER_ASC;
|
||||
private $order = self::ORDER_ASC;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $resultType = self::RESULT_TYPE_PAGER;
|
||||
private $resultType = self::RESULT_TYPE_PAGER;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
@@ -68,10 +64,7 @@ class BaseQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPageSize()
|
||||
public function getPageSize(): int
|
||||
{
|
||||
return $this->pageSize;
|
||||
}
|
||||
@@ -82,17 +75,14 @@ class BaseQuery
|
||||
*/
|
||||
public function setPageSize($pageSize)
|
||||
{
|
||||
if (!empty($pageSize) && (int) $pageSize > 0) {
|
||||
if ($pageSize !== null && (int) $pageSize > 0) {
|
||||
$this->pageSize = (int) $pageSize;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOrderBy()
|
||||
public function getOrderBy(): string
|
||||
{
|
||||
return $this->orderBy;
|
||||
}
|
||||
@@ -110,10 +100,7 @@ class BaseQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOrder()
|
||||
public function getOrder(): string
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
@@ -132,6 +119,7 @@ class BaseQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.0
|
||||
* @return string
|
||||
*/
|
||||
public function getResultType()
|
||||
@@ -140,6 +128,7 @@ class BaseQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.0
|
||||
* @param string $resultType
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException
|
||||
@@ -158,21 +147,20 @@ class BaseQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object|null
|
||||
* Returns whether the query has changed fields, compared to the original state.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getHiddenEntity()
|
||||
public function isDirty(): bool
|
||||
{
|
||||
return $this->hiddenEntity;
|
||||
}
|
||||
if ($this->page !== self::DEFAULT_PAGE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object|string|null $hiddenEntity
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function setHiddenEntity($hiddenEntity)
|
||||
{
|
||||
$this->hiddenEntity = $hiddenEntity;
|
||||
if ($this->pageSize !== self::DEFAULT_PAGESIZE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
69
src/Repository/Query/CustomerFormTypeQuery.php
Normal file
69
src/Repository/Query/CustomerFormTypeQuery.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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\Customer;
|
||||
|
||||
/**
|
||||
* Can be used for advanced queries with the: CustomerRepository
|
||||
*/
|
||||
final class CustomerFormTypeQuery
|
||||
{
|
||||
/**
|
||||
* @var Customer|int|null
|
||||
*/
|
||||
private $customer;
|
||||
/**
|
||||
* @var Customer|null
|
||||
*/
|
||||
private $customerToIgnore;
|
||||
|
||||
/**
|
||||
* @param Customer|int|null $customer
|
||||
*/
|
||||
public function __construct($customer = null)
|
||||
{
|
||||
$this->customer = $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer|int|null
|
||||
*/
|
||||
public function getCustomer()
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer|int|null $customer
|
||||
* @return $this
|
||||
*/
|
||||
public function setCustomer($customer): CustomerFormTypeQuery
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer|null
|
||||
*/
|
||||
public function getCustomerToIgnore(): ?Customer
|
||||
{
|
||||
return $this->customerToIgnore;
|
||||
}
|
||||
|
||||
public function setCustomerToIgnore(Customer $customerToIgnore): CustomerFormTypeQuery
|
||||
{
|
||||
$this->customerToIgnore = $customerToIgnore;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -9,34 +9,13 @@
|
||||
|
||||
namespace App\Repository\Query;
|
||||
|
||||
use App\Entity\Customer;
|
||||
|
||||
/**
|
||||
* Can be used for advanced queries with the: CustomerRepository
|
||||
*/
|
||||
class CustomerQuery extends VisibilityQuery
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $ignored = [];
|
||||
|
||||
/**
|
||||
* @param Customer|int $customer
|
||||
* @return $this
|
||||
*/
|
||||
public function addIgnoredEntity($customer)
|
||||
public function __construct()
|
||||
{
|
||||
$this->ignored[] = $customer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getIgnoredEntities()
|
||||
{
|
||||
return $this->ignored;
|
||||
$this->setOrderBy('name');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class ExportQuery extends TimesheetQuery
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
|
||||
@@ -19,14 +19,7 @@ class InvoiceQuery extends TimesheetQuery
|
||||
/**
|
||||
* @var InvoiceTemplate
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* TODO can this be removed ???
|
||||
*
|
||||
* @var InvoiceTemplate[]
|
||||
*/
|
||||
protected $templates = [];
|
||||
private $template;
|
||||
|
||||
/**
|
||||
* @return InvoiceTemplate
|
||||
|
||||
92
src/Repository/Query/ProjectFormTypeQuery.php
Normal file
92
src/Repository/Query/ProjectFormTypeQuery.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?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\Customer;
|
||||
use App\Entity\Project;
|
||||
|
||||
final class ProjectFormTypeQuery
|
||||
{
|
||||
/**
|
||||
* @var Customer|int|null
|
||||
*/
|
||||
private $customer;
|
||||
/**
|
||||
* @var Project|int|null
|
||||
*/
|
||||
private $project;
|
||||
/**
|
||||
* @var Project|null
|
||||
*/
|
||||
private $projectToIgnore;
|
||||
|
||||
/**
|
||||
* @param Project|int|null $project
|
||||
* @param Customer|int|null $customer
|
||||
*/
|
||||
public function __construct($project = null, $customer = null)
|
||||
{
|
||||
$this->project = $project;
|
||||
$this->customer = $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer|int|null
|
||||
*/
|
||||
public function getCustomer()
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer|int|null $customer
|
||||
* @return $this
|
||||
*/
|
||||
public function setCustomer($customer): ProjectFormTypeQuery
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project|int|null
|
||||
*/
|
||||
public function getProject()
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Project|int|null $project
|
||||
* @return ProjectFormTypeQuery
|
||||
*/
|
||||
public function setProject($project): ProjectFormTypeQuery
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project|null
|
||||
*/
|
||||
public function getProjectToIgnore(): ?Project
|
||||
{
|
||||
return $this->projectToIgnore;
|
||||
}
|
||||
|
||||
public function setProjectToIgnore(Project $projectToIgnore): ProjectFormTypeQuery
|
||||
{
|
||||
$this->projectToIgnore = $projectToIgnore;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -14,35 +14,17 @@ use App\Entity\Customer;
|
||||
/**
|
||||
* Can be used for advanced queries with the: ProjectRepository
|
||||
*/
|
||||
class ProjectQuery extends VisibilityQuery
|
||||
class ProjectQuery extends CustomerQuery
|
||||
{
|
||||
/**
|
||||
* @var Customer|int|null
|
||||
*/
|
||||
protected $customer;
|
||||
private $customer;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $ignored = [];
|
||||
|
||||
/**
|
||||
* @param mixed $entity
|
||||
* @return $this
|
||||
*/
|
||||
public function addIgnoredEntity($entity)
|
||||
public function __construct()
|
||||
{
|
||||
$this->ignored[] = $entity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getIgnoredEntities()
|
||||
{
|
||||
return $this->ignored;
|
||||
parent::__construct();
|
||||
$this->setOrderBy('name');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,4 +45,20 @@ class ProjectQuery extends VisibilityQuery
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDirty(): bool
|
||||
{
|
||||
if (parent::isDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->customer !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +25,6 @@ class TimesheetQuery extends ActivityQuery
|
||||
public const STATE_EXPORTED = 4;
|
||||
public const STATE_NOT_EXPORTED = 5;
|
||||
|
||||
/**
|
||||
* Overwritten for different default order
|
||||
* @var string
|
||||
*/
|
||||
protected $order = self::ORDER_DESC;
|
||||
/**
|
||||
* Overwritten for different default order
|
||||
* @var string
|
||||
*/
|
||||
protected $orderBy = 'begin';
|
||||
/**
|
||||
* @var User|null
|
||||
*/
|
||||
@@ -62,6 +52,9 @@ class TimesheetQuery extends ActivityQuery
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setOrder(self::ORDER_DESC);
|
||||
$this->setOrderBy('begin');
|
||||
$this->dateRange = new DateRange();
|
||||
}
|
||||
|
||||
@@ -245,4 +238,40 @@ class TimesheetQuery extends ActivityQuery
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDirty(): bool
|
||||
{
|
||||
if (parent::isDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->activity !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!empty($this->tags)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->user !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->state !== self::STATE_ALL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->exported !== self::STATE_ALL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->dateRange->getBegin() !== null || $this->dateRange->getEnd() !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,16 @@ class VisibilityQuery extends BaseQuery
|
||||
public const SHOW_HIDDEN = 2;
|
||||
public const SHOW_BOTH = 3;
|
||||
|
||||
public const ALLOWED_VISIBILITY_STATES = [
|
||||
self::SHOW_BOTH,
|
||||
self::SHOW_VISIBLE,
|
||||
self::SHOW_HIDDEN,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $visibility = self::SHOW_VISIBLE;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $exclusiveVisibility = false;
|
||||
private $visibility = self::SHOW_VISIBLE;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
@@ -46,7 +48,7 @@ class VisibilityQuery extends BaseQuery
|
||||
}
|
||||
|
||||
$visibility = (int) $visibility;
|
||||
if (in_array($visibility, [self::SHOW_BOTH, self::SHOW_VISIBLE, self::SHOW_HIDDEN], true)) {
|
||||
if (in_array($visibility, self::ALLOWED_VISIBILITY_STATES, true)) {
|
||||
$this->visibility = $visibility;
|
||||
}
|
||||
|
||||
@@ -54,23 +56,18 @@ class VisibilityQuery extends BaseQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isExclusiveVisibility()
|
||||
public function isDirty(): bool
|
||||
{
|
||||
return $this->exclusiveVisibility;
|
||||
}
|
||||
if (parent::isDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to true, this will ONLY filter the visibility on the main queried object.
|
||||
*
|
||||
* @param bool $exclusiveVisibility
|
||||
* @return $this
|
||||
*/
|
||||
public function setExclusiveVisibility($exclusiveVisibility)
|
||||
{
|
||||
$this->exclusiveVisibility = (bool) $exclusiveVisibility;
|
||||
if ($this->visibility !== self::SHOW_VISIBLE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,12 @@ use Pagerfanta\Adapter\DoctrineORMAdapter;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
|
||||
/**
|
||||
* Trait RepositoryTrait
|
||||
* @deprecated since 1.0
|
||||
*/
|
||||
trait RepositoryTrait
|
||||
{
|
||||
/**
|
||||
* @deprecated since 1.0
|
||||
* @param QueryBuilder $qb
|
||||
* @param BaseQuery $query
|
||||
* @return QueryBuilder|Pagerfanta|array
|
||||
@@ -42,7 +43,7 @@ trait RepositoryTrait
|
||||
* @param int $maxPerPage
|
||||
* @return Pagerfanta
|
||||
*/
|
||||
protected function getPager(Query $query, $page = 1, $maxPerPage = 25)
|
||||
private function getPager(Query $query, $page = 1, $maxPerPage = 25)
|
||||
{
|
||||
$paginator = new Pagerfanta(new DoctrineORMAdapter($query, false));
|
||||
$paginator->setMaxPerPage($maxPerPage);
|
||||
|
||||
@@ -10,9 +10,12 @@
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Repository\Query\TagQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
class TagRepository extends AbstractRepository
|
||||
class TagRepository extends EntityRepository
|
||||
{
|
||||
use RepositoryTrait;
|
||||
|
||||
/**
|
||||
* Find ids of the given tagNames separated by comma
|
||||
* @param string $tagNames
|
||||
|
||||
@@ -17,7 +17,8 @@ use App\Model\Statistic\Month;
|
||||
use App\Model\Statistic\Year;
|
||||
use App\Model\TimesheetStatistic;
|
||||
use App\Repository\Loader\TimesheetLoader;
|
||||
use App\Repository\Paginator\TimesheetPaginator;
|
||||
use App\Repository\Paginator\LoaderPaginator;
|
||||
use App\Repository\Paginator\PaginatorInterface;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use DateTime;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
@@ -349,24 +350,23 @@ class TimesheetRepository extends EntityRepository
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->select('t', 'a', 'p', 'c')
|
||||
$qb->select('t')
|
||||
->from(Timesheet::class, 't')
|
||||
->join('t.activity', 'a')
|
||||
->join('t.project', 'p')
|
||||
->join('p.customer', 'c')
|
||||
->leftJoin('t.tags', 'tags')
|
||||
->andWhere($qb->expr()->isNotNull('t.begin'))
|
||||
->andWhere($qb->expr()->isNull('t.end'))
|
||||
->orderBy('t.begin', 'DESC');
|
||||
|
||||
$params = [];
|
||||
|
||||
if (null !== $user) {
|
||||
$qb->andWhere('t.user = :user');
|
||||
$params['user'] = $user;
|
||||
$qb->setParameter('user', $user);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->execute($params);
|
||||
$results = $qb->getQuery()->getResult();
|
||||
|
||||
$loader = new TimesheetLoader($qb->getEntityManager());
|
||||
$loader->loadResults($results);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,36 +414,42 @@ class TimesheetRepository extends EntityRepository
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
protected function getPaginatorForQuery(TimesheetQuery $query): TimesheetPaginator
|
||||
protected function getPaginatorForQuery(TimesheetQuery $query): PaginatorInterface
|
||||
{
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
$qb->select($qb->expr()->countDistinct('t.id'))->resetDQLPart('orderBy');
|
||||
$qb
|
||||
->resetDQLPart('select')
|
||||
->resetDQLPart('orderBy')
|
||||
->select($qb->expr()->countDistinct('t.id'))
|
||||
;
|
||||
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
||||
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
$qb->select('t');
|
||||
|
||||
$paginator = new TimesheetPaginator($qb, $counter);
|
||||
|
||||
return $paginator;
|
||||
return new LoaderPaginator(new TimesheetLoader($qb->getEntityManager()), $qb, $counter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetQuery $query
|
||||
* @return Timesheet[]
|
||||
*/
|
||||
public function getTimesheetsForQuery(TimesheetQuery $query): array
|
||||
public function getTimesheetsForQuery(TimesheetQuery $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();
|
||||
}
|
||||
|
||||
protected function getQueryBuilderForQuery(TimesheetQuery $query): QueryBuilder
|
||||
private function getQueryBuilderForQuery(TimesheetQuery $query): QueryBuilder
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
|
||||
$qb->from(Timesheet::class, 't');
|
||||
$qb
|
||||
->select('t')
|
||||
->from(Timesheet::class, 't')
|
||||
;
|
||||
|
||||
if (null !== $query->getUser()) {
|
||||
$qb->andWhere('t.user = :user')
|
||||
|
||||
@@ -11,15 +11,25 @@ namespace App\Repository;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
|
||||
|
||||
class UserRepository extends AbstractRepository implements UserLoaderInterface
|
||||
class UserRepository extends EntityRepository implements UserLoaderInterface
|
||||
{
|
||||
use RepositoryTrait;
|
||||
|
||||
public function getById($id): ?User
|
||||
{
|
||||
@trigger_error('UserRepository::getById is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getUserById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return null|User
|
||||
*/
|
||||
public function getById($id)
|
||||
public function getUserById($id): ?User
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user