mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-28 21:16:13 +00:00
This commit resolves issue 259 where the filtering of activities differed within the document generation and in the list of activities for an accompanying period. This amendment to the Chill Activity Bundle ensures consistent behavior. Additionally, new test methods and query adjustments were applied to the ActivityACLAwareRepository for better functionality.
483 lines
19 KiB
PHP
483 lines
19 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* Chill is a software for social workers
|
|
*
|
|
* For the full copyright and license information, please view
|
|
* the LICENSE file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Chill\ActivityBundle\Repository;
|
|
|
|
use Chill\ActivityBundle\Entity\Activity;
|
|
use Chill\ActivityBundle\Entity\ActivityPresence;
|
|
use Chill\ActivityBundle\Entity\ActivityType;
|
|
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
|
use Chill\MainBundle\Entity\Location;
|
|
use Chill\MainBundle\Entity\LocationType;
|
|
use Chill\MainBundle\Entity\Scope;
|
|
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\NonUniqueResultException;
|
|
use Doctrine\ORM\NoResultException;
|
|
use Doctrine\ORM\Query\Expr\Join;
|
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
|
use Doctrine\ORM\QueryBuilder;
|
|
use Symfony\Component\HttpFoundation\RequestStack;
|
|
use Symfony\Component\Security\Core\Security;
|
|
|
|
final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
|
|
{
|
|
public function __construct(
|
|
private AuthorizationHelperForCurrentUserInterface $authorizationHelper,
|
|
private CenterResolverManagerInterface $centerResolverManager,
|
|
private ActivityRepository $repository,
|
|
private EntityManagerInterface $em,
|
|
private Security $security,
|
|
private RequestStack $requestStack,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @throws NonUniqueResultException
|
|
* @throws NoResultException
|
|
*/
|
|
public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int
|
|
{
|
|
$qb = $this->buildBaseQuery($filters);
|
|
|
|
$qb
|
|
->select('COUNT(a)')
|
|
->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period);
|
|
|
|
return $qb->getQuery()->getSingleScalarResult();
|
|
}
|
|
|
|
public function countByPerson(Person $person, string $role, array $filters = []): int
|
|
{
|
|
$qb = $this->buildBaseQuery($filters);
|
|
|
|
$qb = $this->filterBaseQueryByPerson($qb, $person, $role);
|
|
|
|
$qb->select('COUNT(a)');
|
|
|
|
return $qb->getQuery()->getSingleScalarResult();
|
|
}
|
|
|
|
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array
|
|
{
|
|
$qb = $this->buildBaseQuery($filters);
|
|
|
|
$qb->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period);
|
|
|
|
foreach ($orderBy as $field => $order) {
|
|
$qb->addOrderBy('a.'.$field, $order);
|
|
}
|
|
|
|
if (null !== $start) {
|
|
$qb->setFirstResult($start);
|
|
}
|
|
if (null !== $limit) {
|
|
$qb->setMaxResults($limit);
|
|
}
|
|
|
|
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(
|
|
$qb->expr()->exists(
|
|
sprintf(
|
|
'SELECT 1 FROM %s ujh_creator WHERE ujh_creator.user = a.createdBy '
|
|
.'AND ujh_creator.job IN (:jobs) AND a.createdAt > ujh_creator.startDate '
|
|
.'AND (ujh_creator.endDate IS NULL or ujh_creator.endDate > a.date)',
|
|
User\UserJobHistory::class
|
|
)
|
|
),
|
|
$qb->expr()->exists(
|
|
sprintf(
|
|
'SELECT 1 FROM %s ujh_u WHERE ujh_u.user = a.user '
|
|
.'AND ujh_u.job IN (:jobs) AND a.createdAt > ujh_u.startDate '
|
|
.'AND (ujh_u.endDate IS NULL or ujh_u.endDate > a.date)',
|
|
User\UserJobHistory::class
|
|
)
|
|
),
|
|
$qb->expr()->exists(
|
|
sprintf(
|
|
'SELECT 1 FROM %s ujh_users WHERE ujh_users.user MEMBER OF a.users '
|
|
.'AND ujh_users.job IN (:jobs) AND a.createdAt > ujh_users.startDate '
|
|
.'AND (ujh_users.endDate IS NULL or ujh_users.endDate > a.date)',
|
|
User\UserJobHistory::class
|
|
)
|
|
),
|
|
)
|
|
)
|
|
->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;
|
|
}
|
|
|
|
/**
|
|
* @return array<ActivityType>
|
|
*/
|
|
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(AccompanyingPeriod|Person $associated): array
|
|
{
|
|
$in = $this->em->createQueryBuilder();
|
|
$in->select('IDENTITY(u.job)')
|
|
->distinct()
|
|
->from(User\UserJobHistory::class, 'u')
|
|
->join(
|
|
Activity::class,
|
|
'a',
|
|
Join::WITH,
|
|
'a.createdBy = u.user OR a.user = u.user OR u.user MEMBER OF a.users AND a.date >= u.startDate ANd (u.endDate IS NULL or u.endDate > a.date)'
|
|
);
|
|
|
|
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);
|
|
|
|
$sql = '
|
|
SELECT
|
|
a.id AS activity_id,
|
|
date,
|
|
CASE WHEN durationtime IS NOT NULL THEN (EXTRACT(EPOCH from durationtime) / 60)::int ELSE 0 END AS durationtimeminute,
|
|
attendee_id,
|
|
comment_comment,
|
|
emergency,
|
|
sentreceived,
|
|
CASE WHEN traveltime IS NOT NULL THEN (EXTRACT(EPOCH from traveltime) / 60)::int ELSE 0 END AS traveltimeminute,
|
|
t.id AS type_id, t.name as type_name,
|
|
p.id AS presence_id, p.name AS presence_name,
|
|
location.id AS location_id, location.address_id, location.name AS location_name, location.phonenumber1, location.phonenumber2, location.email,
|
|
location.locationtype_id, locationtype.title AS locationtype_title,
|
|
users.userids AS userids,
|
|
thirdparties.thirdpartyids,
|
|
persons.personids,
|
|
actions.socialactionids,
|
|
issues.socialissueids,
|
|
a.user_id
|
|
|
|
FROM activity a
|
|
LEFT JOIN chill_main_location location ON a.location_id = location.id
|
|
LEFT JOIN chill_main_location_type locationtype ON location.locationtype_id = locationtype.id
|
|
LEFT JOIN activitytpresence p ON a.attendee_id = p.id
|
|
LEFT JOIN activitytype t ON a.type_id = t.id
|
|
LEFT JOIN LATERAL (SELECT jsonb_agg(user_id) userids, activity_id FROM activity_user AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS users ON TRUE
|
|
LEFT JOIN LATERAL (SELECT jsonb_agg(thirdparty_id) thirdpartyids, activity_id FROM activity_thirdparty AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS thirdparties ON TRUE
|
|
LEFT JOIN LATERAL (SELECT jsonb_agg(person_id) personids, activity_id FROM activity_person AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS persons ON TRUE
|
|
LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE
|
|
LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE
|
|
|
|
WHERE accompanyingperiod_id = ?
|
|
ORDER BY a.date DESC, a.id DESC
|
|
LIMIT ?
|
|
';
|
|
|
|
$rsm
|
|
->addEntityResult(Activity::class, 'a')
|
|
->addFieldResult('a', 'activity_id', 'id')
|
|
->addFieldResult('a', 'date', 'date')
|
|
->addFieldResult('a', 'comment', 'comment')
|
|
->addFieldResult('a', 'sentreceived', 'sentReceived')
|
|
->addFieldResult('a', 'emergency', 'emergency')
|
|
->addJoinedEntityResult(Location::class, 'location', 'a', 'location')
|
|
->addFieldResult('location', 'location_id', 'id')
|
|
->addFieldResult('location', 'location_name', 'name')
|
|
->addFieldResult('location', 'phonenumber1', 'phonenumber1')
|
|
->addFieldResult('location', 'phonenumber2', 'phonenumber2')
|
|
->addFieldResult('location', 'email', 'email')
|
|
->addJoinedEntityResult(LocationType::class, 'locationType', 'location', 'locationType')
|
|
->addFieldResult('locationType', 'locationtype_id', 'id')
|
|
->addFieldResult('locationType', 'locationtype_title', 'title')
|
|
->addJoinedEntityResult(ActivityType::class, 'activityType', 'a', 'activityType')
|
|
->addFieldResult('activityType', 'type_id', 'id')
|
|
->addFieldResult('activityType', 'type_name', 'name')
|
|
->addJoinedEntityResult(ActivityPresence::class, 'activityPresence', 'a', 'attendee')
|
|
->addFieldResult('activityPresence', 'presence_id', 'id')
|
|
->addFieldResult('activityPresence', 'presence_name', 'name')
|
|
->addScalarResult('user_id', 'userId', Types::INTEGER)
|
|
|
|
// results which cannot be mapped into entity
|
|
->addScalarResult('comment_comment', 'comment', Types::TEXT)
|
|
->addScalarResult('userids', 'userIds', Types::JSON)
|
|
->addScalarResult('thirdpartyids', 'thirdPartyIds', Types::JSON)
|
|
->addScalarResult('personids', 'personIds', Types::JSON)
|
|
->addScalarResult('socialactionids', 'socialActionIds', Types::JSON)
|
|
->addScalarResult('socialissueids', 'socialIssueIds', Types::JSON)
|
|
->addScalarResult('durationtimeminute', 'durationTimeMinute', Types::INTEGER)
|
|
->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER);
|
|
|
|
$nq = $this->em->createNativeQuery($sql, $rsm);
|
|
|
|
$nq->setParameter(0, $period->getId())->setParameter(1, $limit);
|
|
|
|
return $nq->getResult(AbstractQuery::HYDRATE_ARRAY);
|
|
}
|
|
|
|
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): array
|
|
{
|
|
$qb = $this->buildBaseQuery($filters);
|
|
|
|
$qb = $this->filterBaseQueryByPerson($qb, $person, $role);
|
|
|
|
foreach ($orderBy as $field => $direction) {
|
|
$qb->addOrderBy('a.'.$field, $direction);
|
|
}
|
|
|
|
if (null !== $start) {
|
|
$qb->setFirstResult($start);
|
|
}
|
|
if (null !== $limit) {
|
|
$qb->setMaxResults($limit);
|
|
}
|
|
|
|
return $qb->getQuery()->getResult();
|
|
}
|
|
|
|
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
|
|
{
|
|
$metadataActivity = $this->em->getClassMetadata(Activity::class);
|
|
|
|
$from = $this->getFromClauseCenter($args);
|
|
[$where, $parameters] = $this->getWhereClause($context, $args);
|
|
|
|
return [
|
|
'id' => $metadataActivity->getTableName()
|
|
.'.'.$metadataActivity->getColumnName('id'),
|
|
'type' => 'activity',
|
|
'date' => $metadataActivity->getTableName()
|
|
.'.'.$metadataActivity->getColumnName('date'),
|
|
'FROM' => $from,
|
|
'WHERE' => $where,
|
|
'parameters' => $parameters,
|
|
];
|
|
}
|
|
|
|
private function getFromClauseCenter(array $args): string
|
|
{
|
|
$metadataActivity = $this->em->getClassMetadata(Activity::class);
|
|
$metadataPerson = $this->em->getClassMetadata(Person::class);
|
|
$associationMapping = $metadataActivity->getAssociationMapping('person');
|
|
|
|
return $metadataActivity->getTableName().' JOIN '
|
|
.$metadataPerson->getTableName().' ON '
|
|
.$metadataPerson->getTableName().'.'.
|
|
$associationMapping['joinColumns'][0]['referencedColumnName']
|
|
.' = '
|
|
.$associationMapping['joinColumns'][0]['name'];
|
|
}
|
|
|
|
private function getWhereClause(string $context, array $args): array
|
|
{
|
|
$where = '';
|
|
$parameters = [];
|
|
|
|
$metadataActivity = $this->em->getClassMetadata(Activity::class);
|
|
$metadataPerson = $this->em->getClassMetadata(Person::class);
|
|
$activityToPerson = $metadataActivity->getAssociationMapping('person')['joinColumns'][0]['name'];
|
|
$activityToScope = $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'];
|
|
$personToCenter = $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'];
|
|
|
|
// acls:
|
|
$reachableCenters = $this->authorizationHelper->getReachableCenters(
|
|
ActivityVoter::SEE
|
|
);
|
|
|
|
if (0 === \count($reachableCenters)) {
|
|
// insert a dummy condition
|
|
return 'FALSE = TRUE';
|
|
}
|
|
|
|
if ('person' === $context) {
|
|
// we start with activities having the person_id linked to person
|
|
$where .= sprintf('%s = ? AND ', $activityToPerson);
|
|
$parameters[] = $args['context']->getId();
|
|
}
|
|
|
|
// we add acl (reachable center and scopes)
|
|
$where .= '('; // first loop for the for centers
|
|
$centersI = 0; // like centers#i
|
|
|
|
foreach ($reachableCenters as $center) {
|
|
// we pass if not in centers
|
|
if (!\in_array($center, $args['centers'], true)) {
|
|
continue;
|
|
}
|
|
// we get all the reachable scopes for this center
|
|
$reachableScopes = $this->authorizationHelper->getReachableScopes(ActivityVoter::SEE, $center);
|
|
// we get the ids for those scopes
|
|
$reachablesScopesId = array_map(
|
|
static fn (Scope $scope) => $scope->getId(),
|
|
$reachableScopes
|
|
);
|
|
|
|
// if not the first center
|
|
if (0 < $centersI) {
|
|
$where .= ') OR (';
|
|
}
|
|
|
|
// condition for the center
|
|
$where .= sprintf(' %s.%s = ? ', $metadataPerson->getTableName(), $personToCenter);
|
|
$parameters[] = $center->getId();
|
|
|
|
// begin loop for scopes
|
|
$where .= ' AND (';
|
|
$scopesI = 0; // like scope#i
|
|
|
|
foreach ($reachablesScopesId as $scopeId) {
|
|
if (0 < $scopesI) {
|
|
$where .= ' OR ';
|
|
}
|
|
$where .= sprintf(' %s.%s = ? ', $metadataActivity->getTableName(), $activityToScope);
|
|
$parameters[] = $scopeId;
|
|
++$scopesI;
|
|
}
|
|
// close loop for scopes
|
|
$where .= ') ';
|
|
++$centersI;
|
|
}
|
|
// close loop for centers
|
|
$where .= ')';
|
|
|
|
return [$where, $parameters];
|
|
}
|
|
}
|