diff --git a/.changes/unreleased/Feature-20230623-122530.yaml b/.changes/unreleased/Feature-20230623-122530.yaml
new file mode 100644
index 000000000..922750ea8
--- /dev/null
+++ b/.changes/unreleased/Feature-20230623-122530.yaml
@@ -0,0 +1,5 @@
+kind: Feature
+body: '[activity list] add filtering for activities list'
+time: 2023-06-23T12:25:30.49643551+02:00
+custom:
+ Issue: ""
diff --git a/.changes/unreleased/Feature-20230623-122702.yaml b/.changes/unreleased/Feature-20230623-122702.yaml
new file mode 100644
index 000000000..e1d1b0e1f
--- /dev/null
+++ b/.changes/unreleased/Feature-20230623-122702.yaml
@@ -0,0 +1,6 @@
+kind: Feature
+body: '[activity list] in person context, show also the activities from the accompanying
+ periods where the person participates'
+time: 2023-06-23T12:27:02.159041095+02:00
+custom:
+ Issue: ""
diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php
index 444c663fc..07e4fcf62 100644
--- a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php
+++ b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php
@@ -18,11 +18,16 @@ use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Repository\ActivityTypeCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
+use Chill\ActivityBundle\Repository\ActivityUserJobRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
+use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
+use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
+use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
+use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent;
@@ -47,68 +52,25 @@ use function array_key_exists;
final class ActivityController extends AbstractController
{
- private AccompanyingPeriodRepository $accompanyingPeriodRepository;
-
- private ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
-
- private ActivityRepository $activityRepository;
-
- private ActivityTypeCategoryRepository $activityTypeCategoryRepository;
-
- private ActivityTypeRepositoryInterface $activityTypeRepository;
-
- private CenterResolverManagerInterface $centerResolver;
-
- private EntityManagerInterface $entityManager;
-
- private EventDispatcherInterface $eventDispatcher;
-
- private LocationRepository $locationRepository;
-
- private LoggerInterface $logger;
-
- private PersonRepository $personRepository;
-
- private SerializerInterface $serializer;
-
- private ThirdPartyRepository $thirdPartyRepository;
-
- private TranslatorInterface $translator;
-
- private UserRepositoryInterface $userRepository;
-
public function __construct(
- ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
- ActivityTypeRepositoryInterface $activityTypeRepository,
- ActivityTypeCategoryRepository $activityTypeCategoryRepository,
- PersonRepository $personRepository,
- ThirdPartyRepository $thirdPartyRepository,
- LocationRepository $locationRepository,
- ActivityRepository $activityRepository,
- AccompanyingPeriodRepository $accompanyingPeriodRepository,
- EntityManagerInterface $entityManager,
- EventDispatcherInterface $eventDispatcher,
- LoggerInterface $logger,
- SerializerInterface $serializer,
- UserRepositoryInterface $userRepository,
- CenterResolverManagerInterface $centerResolver,
- TranslatorInterface $translator
+ private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
+ private readonly ActivityTypeRepositoryInterface $activityTypeRepository,
+ private readonly ActivityTypeCategoryRepository $activityTypeCategoryRepository,
+ private readonly PersonRepository $personRepository,
+ private readonly ThirdPartyRepository $thirdPartyRepository,
+ private readonly LocationRepository $locationRepository,
+ private readonly ActivityRepository $activityRepository,
+ private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository,
+ private readonly EntityManagerInterface $entityManager,
+ private readonly EventDispatcherInterface $eventDispatcher,
+ private readonly LoggerInterface $logger,
+ private readonly SerializerInterface $serializer,
+ private readonly UserRepositoryInterface $userRepository,
+ private readonly CenterResolverManagerInterface $centerResolver,
+ private readonly TranslatorInterface $translator,
+ private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
+ private readonly TranslatableStringHelperInterface $translatableStringHelper,
) {
- $this->activityACLAwareRepository = $activityACLAwareRepository;
- $this->activityTypeRepository = $activityTypeRepository;
- $this->activityTypeCategoryRepository = $activityTypeCategoryRepository;
- $this->personRepository = $personRepository;
- $this->thirdPartyRepository = $thirdPartyRepository;
- $this->locationRepository = $locationRepository;
- $this->activityRepository = $activityRepository;
- $this->accompanyingPeriodRepository = $accompanyingPeriodRepository;
- $this->entityManager = $entityManager;
- $this->eventDispatcher = $eventDispatcher;
- $this->logger = $logger;
- $this->serializer = $serializer;
- $this->userRepository = $userRepository;
- $this->centerResolver = $centerResolver;
- $this->translator = $translator;
}
/**
@@ -292,11 +254,27 @@ final class ActivityController extends AbstractController
// TODO: add pagination
[$person, $accompanyingPeriod] = $this->getEntity($request);
+ $filter = $this->buildFilterOrder($person ?? $accompanyingPeriod);
+
+ $filterArgs = [
+ 'my_activities' => $filter->getSingleCheckboxData('my_activities'),
+ 'types' => $filter->getEntityChoiceData('activity_types'),
+ 'jobs' => $filter->getEntityChoiceData('jobs'),
+ 'before' => $filter->getDateRangeData('activity_date')['to'],
+ 'after' => $filter->getDateRangeData('activity_date')['from'],
+ ];
if ($person instanceof Person) {
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $person);
$activities = $this->activityACLAwareRepository
- ->findByPerson($person, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']);
+ ->findByPerson(
+ $person,
+ ActivityVoter::SEE,
+ 0,
+ null,
+ ['date' => 'DESC', 'id' => 'DESC'],
+ $filterArgs
+ );
$event = new PrivacyEvent($person, [
'element_class' => Activity::class,
@@ -309,7 +287,14 @@ final class ActivityController extends AbstractController
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod);
$activities = $this->activityACLAwareRepository
- ->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']);
+ ->findByAccompanyingPeriod(
+ $accompanyingPeriod,
+ ActivityVoter::SEE,
+ 0,
+ null,
+ ['date' => 'DESC', 'id' => 'DESC'],
+ $filterArgs
+ );
$view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig';
}
@@ -320,10 +305,39 @@ final class ActivityController extends AbstractController
'activities' => $activities,
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
+ 'filter' => $filter,
]
);
}
+ private function buildFilterOrder(AccompanyingPeriod|Person $associated): FilterOrderHelper
+ {
+
+ $filterBuilder = $this->filterOrderHelperFactory->create(self::class);
+ $types = $this->activityACLAwareRepository->findActivityTypeByAssociated($associated);
+ $jobs = $this->activityACLAwareRepository->findUserJobByAssociated($associated);
+
+ $filterBuilder
+ ->addDateRange('activity_date', 'activity.date')
+ ->addSingleCheckbox('my_activities', 'activity_filter.My activities')
+ ->addEntityChoice('activity_types', 'activity_filter.Types', \Chill\ActivityBundle\Entity\ActivityType::class, $types, [
+ 'choice_label' => function (\Chill\ActivityBundle\Entity\ActivityType $activityType) {
+ $text = match ($activityType->hasCategory()) {
+ true => $this->translatableStringHelper->localize($activityType->getCategory()->getName()) . ' > ',
+ false => '',
+ };
+
+ return $text . $this->translatableStringHelper->localize($activityType->getName());
+ }
+ ])
+ ->addEntityChoice('jobs', 'activity_filter.Jobs', UserJob::class, $jobs, [
+ 'choice_label' => fn (UserJob $u) => $this->translatableStringHelper->localize($u->getLabel())
+ ])
+ ;
+
+ return $filterBuilder->build();
+ }
+
public function newAction(Request $request): Response
{
$view = null;
diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php
index 1f2039a2c..1fd0a57d6 100644
--- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php
+++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php
@@ -18,67 +18,159 @@ use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Entity\Scope;
-use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
-use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
+use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Entity\UserJob;
+use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
+use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
-use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
-use Symfony\Component\Security\Core\Role\Role;
+use Doctrine\ORM\QueryBuilder;
+use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Security;
use function count;
use function in_array;
-final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
+final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
{
- private AuthorizationHelper $authorizationHelper;
-
- private CenterResolverDispatcherInterface $centerResolverDispatcher;
-
- private EntityManagerInterface $em;
-
- private ActivityRepository $repository;
-
- private Security $security;
-
- private TokenStorageInterface $tokenStorage;
-
public function __construct(
- AuthorizationHelper $authorizationHelper,
- CenterResolverDispatcherInterface $centerResolverDispatcher,
- TokenStorageInterface $tokenStorage,
- ActivityRepository $repository,
- EntityManagerInterface $em,
- Security $security
+ private AuthorizationHelperForCurrentUserInterface $authorizationHelper,
+ private CenterResolverManagerInterface $centerResolverManager,
+ private ActivityRepository $repository,
+ private EntityManagerInterface $em,
+ private Security $security,
+ private RequestStack $requestStack,
) {
- $this->authorizationHelper = $authorizationHelper;
- $this->centerResolverDispatcher = $centerResolverDispatcher;
- $this->tokenStorage = $tokenStorage;
- $this->repository = $repository;
- $this->em = $em;
- $this->security = $security;
}
- public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
+ public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array
{
- $user = $this->security->getUser();
- $center = $this->centerResolverDispatcher->resolveCenter($period);
+ $qb = $this->buildBaseQuery($filters);
- if (0 === count($orderBy)) {
- $orderBy = ['date' => 'DESC'];
+ $qb->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period);
+
+ foreach ($orderBy as $field => $order) {
+ $qb->addOrderBy('a.' . $field, $order);
}
- $scopes = $this->authorizationHelper
- ->getReachableCircles($user, $role, $center);
+ $qb->setFirstResult(0)->setMaxResults(1000);
- return $this->em->getRepository(Activity::class)
- ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy);
+ return $qb->getQuery()->getResult();
}
+ public function buildBaseQuery(array $filters): QueryBuilder
+ {
+ $qb = $this->repository
+ ->createQueryBuilder('a')
+ ;
+
+ if (($filters['my_activities'] ?? false) and ($user = $this->security->getUser()) instanceof User) {
+ $qb->andWhere(
+ $qb->expr()->orX(
+ 'a.createdBy = :user',
+ 'a.user = :user',
+ ':user MEMBER OF a.users'
+ )
+ )->setParameter('user', $user);
+ }
+
+ if ([] !== ($types = $filters['types'] ?? [])) {
+ $qb->andWhere('a.activityType IN (:types)')->setParameter('types', $types);
+ }
+
+ if ([] !== ($jobs = $filters['jobs'] ?? [])) {
+ $qb
+ ->leftJoin('a.createdBy', 'creator')
+ ->leftJoin('a.user', 'activity_u')
+ ->andWhere(
+ $qb->expr()->orX(
+ 'creator.userJob IN (:jobs)',
+ 'activity_u.userJob IN (:jobs)',
+ 'EXISTS (SELECT 1 FROM ' . User::class . ' activity_user WHERE activity_user MEMBER OF a.users AND activity_user.userJob IN (:jobs))'
+ )
+ )
+ ->setParameter('jobs', $jobs);
+ }
+
+ if (null !== ($after = $filters['after'] ?? null)) {
+ $qb->andWhere('a.date >= :after')->setParameter('after', $after);
+ }
+
+ if (null !== ($before = $filters['before'] ?? null)) {
+ $qb->andWhere('a.date <= :before')->setParameter('before', $before);
+ }
+
+ return $qb;
+ }
+
+ /**
+ * @param AccompanyingPeriod|Person $associated
+ * @return array
+ */
+ public function findActivityTypeByAssociated(AccompanyingPeriod|Person $associated): array
+ {
+ $in = $this->em->createQueryBuilder();
+ $in
+ ->select('1')
+ ->from(Activity::class, 'a');
+
+ if ($associated instanceof Person) {
+ $in = $this->filterBaseQueryByPerson($in, $associated, ActivityVoter::SEE);
+ } else {
+ $in->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $associated);
+ }
+
+ // join between the embedded exist query and the main query
+ $in->andWhere('a.activityType = t');
+
+ $qb = $this->em->createQueryBuilder()->setParameters($in->getParameters());
+ $qb
+ ->select('t')
+ ->from(ActivityType::class, 't')
+ ->where(
+ $qb->expr()->exists($in->getDQL())
+ );
+
+ return $qb->getQuery()->getResult();
+ }
+
+ public function findUserJobByAssociated(Person|AccompanyingPeriod $associated): array
+ {
+ $in = $this->em->createQueryBuilder();
+ $in->select('IDENTITY(u.userJob)')
+ ->from(User::class, 'u')
+ ->join(
+ Activity::class,
+ 'a',
+ Join::WITH,
+ 'a.createdBy = u OR a.user = u OR u MEMBER OF a.users'
+ );
+
+ if ($associated instanceof Person) {
+ $in = $this->filterBaseQueryByPerson($in, $associated, ActivityVoter::SEE);
+ } else {
+ $in->andWhere('a.accompanyingPeriod = :associated');
+ $in->setParameter('associated', $associated);
+ }
+
+ $qb = $this->em->createQueryBuilder()->setParameters($in->getParameters());
+
+ $qb->select('ub', 'JSON_EXTRACT(ub.label, :lang) AS HIDDEN lang')
+ ->from(UserJob::class, 'ub')
+ ->where($qb->expr()->in('ub.id', $in->getDQL()))
+ ->setParameter('lang', $this->requestStack->getCurrentRequest()->getLocale())
+ ->orderBy('lang')
+ ;
+
+ return $qb->getQuery()->getResult();
+ }
+
+
public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array
{
$rsm = new ResultSetMappingBuilder($this->em);
@@ -159,25 +251,66 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
return $nq->getResult(AbstractQuery::HYDRATE_ARRAY);
}
- /**
- * @param array $orderBy
- *
- * @return Activity[]|array
- */
- public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
+ public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): array
{
- $user = $this->security->getUser();
- $center = $this->centerResolverDispatcher->resolveCenter($person);
+ $qb = $this->buildBaseQuery($filters);
- if (0 === count($orderBy)) {
- $orderBy = ['date' => 'DESC'];
+ $qb = $this->filterBaseQueryByPerson($qb, $person, $role);
+
+ foreach ($orderBy as $field => $direction) {
+ $qb->addOrderBy('a.' . $field, $direction);
}
- $reachableScopes = $this->authorizationHelper
- ->getReachableCircles($user, $role, $center);
+ return $qb->getQuery()->getResult();
+ }
- return $this->em->getRepository(Activity::class)
- ->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start);
+ private function filterBaseQueryByPerson(QueryBuilder $qb, Person $person, string $role): QueryBuilder
+ {
+ $orX = $qb->expr()->orX();
+ $counter = 0;
+ foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
+ $scopes = $this->authorizationHelper->getReachableScopes($role, $center);
+
+ if ([] === $scopes) {
+ continue;
+ }
+
+ $orX->add(sprintf('a.person = :person AND a.scope IN (:scopes_%d)', $counter));
+ $qb->setParameter(sprintf('scopes_%d', $counter), $scopes);
+ $qb->setParameter('person', $person);
+ $counter++;
+ }
+
+ foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
+ if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) {
+ continue;
+ }
+
+ $and = $qb->expr()->andX(
+ sprintf('a.accompanyingPeriod = :period_%d', $counter),
+ sprintf('a.date >= :participation_start_%d', $counter)
+ );
+
+ $qb
+ ->setParameter(sprintf('period_%d', $counter), $participation->getAccompanyingPeriod())
+ ->setParameter(sprintf('participation_start_%d', $counter), $participation->getStartDate());
+
+ if (null !== $participation->getEndDate()) {
+ $and->add(sprintf('a.date < :participation_end_%d', $counter));
+ $qb
+ ->setParameter(sprintf('participation_end_%d', $counter), $participation->getEndDate());
+ }
+ $orX->add($and);
+ $counter++;
+ }
+
+ if (0 === $orX->count()) {
+ $qb->andWhere('FALSE = TRUE');
+ } else {
+ $qb->andWhere($orX);
+ }
+
+ return $qb;
}
public function queryTimelineIndexer(string $context, array $args = []): array
@@ -226,7 +359,6 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
// acls:
$reachableCenters = $this->authorizationHelper->getReachableCenters(
- $this->tokenStorage->getToken()->getUser(),
ActivityVoter::SEE
);
@@ -251,7 +383,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
continue;
}
// we get all the reachable scopes for this center
- $reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), ActivityVoter::SEE, $center);
+ $reachableScopes = $this->authorizationHelper->getReachableScopes(ActivityVoter::SEE, $center);
// we get the ids for those scopes
$reachablesScopesId = array_map(
static fn (Scope $scope) => $scope->getId(),
diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php
index 8cdb83524..a046ad218 100644
--- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php
+++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php
@@ -11,15 +11,22 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Repository;
+use Chill\ActivityBundle\Entity\Activity;
+use Chill\ActivityBundle\Entity\ActivityType;
+use Chill\MainBundle\Entity\UserJob;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
interface ActivityACLAwareRepositoryInterface
{
/**
- * @return Activity[]|array
+ * Return all the activities associated to an accompanying period and that the user is allowed to apply the given role.
+ *
+ *
+ * @param array{my_activities?: bool, types?: array, jobs?: array, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
+ * @return array
*/
- public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
+ public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array;
/**
* Return a list of activities, simplified as array (not object).
@@ -31,7 +38,28 @@ interface ActivityACLAwareRepositoryInterface
public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array;
/**
- * @return Activity[]|array
+ * @param array{my_activities?: bool, types?: array, jobs?: array, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
+ * @return array
*/
- public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
+ public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array;
+
+
+ /**
+ * Return a list of the type for the activities associated to person or accompanying period
+ *
+ * @return array
+ */
+ public function findActivityTypeByAssociated(AccompanyingPeriod|Person $associated): array;
+
+ /**
+ * Return a list of the user job for the activities associated to person or accompanying period
+ *
+ * Associated mean the job:
+ * - of the creator;
+ * - of the user (activity.user)
+ * - of all the users
+ *
+ * @return array
+ */
+ public function findUserJobByAssociated(AccompanyingPeriod|Person $associated): array;
}
diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepository.php
index fd5d52fce..10bd1e651 100644
--- a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepository.php
+++ b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepository.php
@@ -11,9 +11,13 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Repository;
+use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
+use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
+use Doctrine\ORM\Query\Expr\Join;
final class ActivityTypeRepository implements ActivityTypeRepositoryInterface
{
diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php
index 2148a02f5..574faea22 100644
--- a/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php
+++ b/src/Bundle/ChillActivityBundle/Repository/ActivityTypeRepositoryInterface.php
@@ -12,12 +12,14 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\ActivityType;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
+use Chill\PersonBundle\Entity\Person;
use Doctrine\Persistence\ObjectRepository;
interface ActivityTypeRepositoryInterface extends ObjectRepository
{
/**
- * @return array|ActivityType[]
+ * @return array
*/
public function findAllActive(): array;
}
diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig
index 79c946f17..17ae0598b 100644
--- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig
+++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig
@@ -86,6 +86,9 @@
{% else %}
+
+ {{ filter|chill_render_filter_order_helper }}
+
{% for activity in activities %}
{% include 'ChillActivityBundle:Activity:_list_item.html.twig' with {
diff --git a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php
new file mode 100644
index 000000000..e7b2117d4
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php
@@ -0,0 +1,325 @@
+authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class);
+ $this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class);
+ $this->activityRepository = self::$container->get(ActivityRepository::class);
+ $this->entityManager = self::$container->get(EntityManagerInterface::class);
+ $this->security = self::$container->get(Security::class);
+
+ $this->requestStack = $requestStack = new RequestStack();
+ $request = $this->prophesize(Request::class);
+ $request->getLocale()->willReturn('fr');
+ $request->getDefaultLocale()->willReturn('fr');
+ $requestStack->push($request->reveal());
+ }
+
+ /**
+ * @dataProvider provideDataFindByAccompanyingPeriod
+ */
+ public function testFindByAccompanyingPeriod(AccompanyingPeriod $period, User $user, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): void
+ {
+ $security = $this->prophesize(Security::class);
+ $security->isGranted($role, $period)->willReturn(true);
+ $security->getUser()->willReturn($user);
+
+ $repository = new ActivityACLAwareRepository(
+ $this->authorizationHelperForCurrentUser,
+ $this->centerResolverManager,
+ $this->activityRepository,
+ $this->entityManager,
+ $security->reveal(),
+ $this->requestStack
+ );
+
+ $actual = $repository->findByAccompanyingPeriod($period, $role, $start, $limit, $orderBy, $filters);
+
+ self::assertIsArray($actual);
+ }
+
+ /**
+ * @dataProvider provideDataFindByAccompanyingPeriod
+ */
+ public function testFindActivityTypeByAccompanyingPeriod(AccompanyingPeriod $period, User $user, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): void
+ {
+ $security = $this->prophesize(Security::class);
+ $security->isGranted($role, $period)->willReturn(true);
+ $security->getUser()->willReturn($user);
+
+ $repository = new ActivityACLAwareRepository(
+ $this->authorizationHelperForCurrentUser,
+ $this->centerResolverManager,
+ $this->activityRepository,
+ $this->entityManager,
+ $security->reveal(),
+ $this->requestStack
+ );
+
+ $actual = $repository->findActivityTypeByAssociated($period);
+
+ self::assertIsArray($actual);
+ }
+
+ /**
+ * @dataProvider provideDataFindByPerson
+ */
+ public function testFindActivityTypeByPerson(Person $person, User $user, array $centers, array $scopes, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): void
+ {
+ $role = ActivityVoter::SEE;
+ $centerResolver = $this->prophesize(CenterResolverManagerInterface::class);
+ $centerResolver->resolveCenters($person)->willReturn($centers);
+
+ $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
+ $authorizationHelper->getReachableScopes($role, Argument::type(Center::class))
+ ->willReturn($scopes);
+
+ $security = $this->prophesize(Security::class);
+ $security->isGranted($role, Argument::type(AccompanyingPeriod::class))->willReturn(true);
+ $security->getUser()->willReturn($user);
+
+ $repository = new ActivityACLAwareRepository(
+ $authorizationHelper->reveal(),
+ $centerResolver->reveal(),
+ $this->activityRepository,
+ $this->entityManager,
+ $security->reveal(),
+ $this->requestStack
+ );
+
+ $actual = $repository->findByPerson($person, $role, $start, $limit, $orderBy, $filters);
+
+ self::assertIsArray($actual);
+ }
+
+ /**
+ * @dataProvider provideDataFindByPerson
+ */
+ public function testFindByPerson(Person $person, User $user, array $centers, array $scopes, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): void
+ {
+ $centerResolver = $this->prophesize(CenterResolverManagerInterface::class);
+ $centerResolver->resolveCenters($person)->willReturn($centers);
+
+ $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
+ $authorizationHelper->getReachableScopes($role, Argument::type(Center::class))
+ ->willReturn($scopes);
+
+ $security = $this->prophesize(Security::class);
+ $security->isGranted($role, Argument::type(AccompanyingPeriod::class))->willReturn(true);
+ $security->getUser()->willReturn($user);
+
+ $repository = new ActivityACLAwareRepository(
+ $authorizationHelper->reveal(),
+ $centerResolver->reveal(),
+ $this->activityRepository,
+ $this->entityManager,
+ $security->reveal(),
+ $this->requestStack
+ );
+
+ $actual = $repository->findByPerson($person, $role, $start, $limit, $orderBy, $filters);
+
+ self::assertIsArray($actual);
+ }
+
+ public function provideDataFindByPerson(): iterable
+ {
+ $this->setUp();
+
+ /** @var Person $person */
+ if (null === $person = $this->entityManager->createQueryBuilder()
+ ->select('p')->from(Person::class, 'p')->setMaxResults(1)
+ ->getQuery()->getSingleResult()) {
+ throw new \RuntimeException("person not found");
+ }
+
+ /** @var AccompanyingPeriod $period1 */
+ if (null === $period1 = $this->entityManager
+ ->createQueryBuilder()
+ ->select('a')
+ ->from(AccompanyingPeriod::class, 'a')
+ ->setMaxResults(1)
+ ->getQuery()
+ ->getSingleResult()) {
+ throw new \RuntimeException("no period found");
+ }
+
+ /** @var AccompanyingPeriod $period2 */
+ if (null === $period2 = $this->entityManager
+ ->createQueryBuilder()
+ ->select('a')
+ ->from(AccompanyingPeriod::class, 'a')
+ ->where('a.id > :pid')
+ ->setParameter('pid', $period1->getId())
+ ->setMaxResults(1)
+ ->getQuery()
+ ->getSingleResult()) {
+ throw new \RuntimeException("no second period found");
+ }
+ // add a period
+ $period1->addPerson($person);
+ $period2->addPerson($person);
+ $period1->getParticipationsContainsPerson($person)->first()->setEndDate(
+ (new \DateTime('now'))->add(new \DateInterval('P1M'))
+ );
+
+ if ([] === $types = $this->entityManager
+ ->createQueryBuilder()
+ ->select('t')
+ ->from(ActivityType::class, 't')
+ ->setMaxResults(2)
+ ->getQuery()
+ ->getResult()) {
+ throw new \RuntimeException("no types");
+ }
+
+ if ([] === $jobs = $this->entityManager
+ ->createQueryBuilder()
+ ->select('j')
+ ->from(UserJob::class, 'j')
+ ->setMaxResults(2)
+ ->getQuery()
+ ->getResult()
+ ) {
+ throw new \RuntimeException("no jobs found");
+ }
+
+ if (null === $user = $this->entityManager
+ ->createQueryBuilder()
+ ->select('u')
+ ->from(User::class, 'u')
+ ->setMaxResults(1)
+ ->getQuery()
+ ->getSingleResult()
+ ) {
+ throw new \RuntimeException("no user found");
+ }
+
+ if ([] === $centers = $this->entityManager->createQueryBuilder()
+ ->select('c')->from(Center::class, 'c')->setMaxResults(2)->getQuery()
+ ->getResult()) {
+ throw new \RuntimeException("no centers found");
+ }
+
+ if ([] === $scopes = $this->entityManager->createQueryBuilder()
+ ->select('s')->from(Scope::class, 's')->setMaxResults(2)->getQuery()
+ ->getResult()) {
+ throw new \RuntimeException("no scopes found");
+ }
+
+ yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], []];
+ yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['my_activities' => true]];
+ yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['types' => $types]];
+ yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['jobs' => $jobs]];
+ yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]];
+ yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]];
+ yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]];
+ }
+
+ public function provideDataFindByAccompanyingPeriod(): iterable
+ {
+ $this->setUp();
+
+ if (null === $period = $this->entityManager
+ ->createQueryBuilder()
+ ->select('a')
+ ->from(AccompanyingPeriod::class, 'a')
+ ->setMaxResults(1)
+ ->getQuery()
+ ->getSingleResult()) {
+ throw new \RuntimeException("no period found");
+ }
+
+ if ([] === $types = $this->entityManager
+ ->createQueryBuilder()
+ ->select('t')
+ ->from(ActivityType::class, 't')
+ ->setMaxResults(2)
+ ->getQuery()
+ ->getResult()) {
+ throw new \RuntimeException("no types");
+ }
+
+ if ([] === $jobs = $this->entityManager
+ ->createQueryBuilder()
+ ->select('j')
+ ->from(UserJob::class, 'j')
+ ->setMaxResults(2)
+ ->getQuery()
+ ->getResult()
+ ) {
+ throw new \RuntimeException("no jobs found");
+ }
+
+ if (null === $user = $this->entityManager
+ ->createQueryBuilder()
+ ->select('u')
+ ->from(User::class, 'u')
+ ->setMaxResults(1)
+ ->getQuery()
+ ->getSingleResult()
+ ) {
+ throw new \RuntimeException("no user found");
+ }
+
+ yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], []];
+ yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['my_activities' => true]];
+ yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['types' => $types]];
+ yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['jobs' => $jobs]];
+ yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]];
+ yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]];
+ yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]];
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
index 042fccc69..1229494bb 100644
--- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
@@ -83,12 +83,20 @@ Third persons: Tiers non-pro.
Others persons: Usagers
Third parties: Tiers professionnels
Users concerned: T(M)S
+
activity:
+ date: Date de l'échange
Insert a document: Insérer un document
Remove a document: Supprimer le document
comment: Commentaire
No documents: Aucun document
+# activity filter in list page
+activity_filter:
+ My activities: Mes échanges (où j'interviens)
+ Types: Par type d'échange
+ Jobs: Par métier impliqué
+
#timeline
'%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"'