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 */ 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 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 (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]; } }