mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
464 lines
18 KiB
PHP
464 lines
18 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;
|
|
|
|
use function count;
|
|
use function in_array;
|
|
|
|
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(
|
|
'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<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(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);
|
|
|
|
$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
|
|
|
|
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')
|
|
|
|
// 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 (count($reachableCenters) === 0) {
|
|
// 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];
|
|
}
|
|
}
|