mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-30 08:49:05 +00:00 
			
		
		
		
	first impl for global timeline: apply on activities
This commit is contained in:
		| @@ -38,7 +38,7 @@ use Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency; | ||||
|  * Class Activity | ||||
|  * | ||||
|  * @package Chill\ActivityBundle\Entity | ||||
|  * @ORM\Entity() | ||||
|  * @ORM\Entity(repositoryClass="Chill\ActivityBundle\Repository\ActivityRepository") | ||||
|  * @ORM\Table(name="activity") | ||||
|  * @ORM\HasLifecycleCallbacks() | ||||
|  * @UserCircleConsistency( | ||||
|   | ||||
| @@ -0,0 +1,246 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * Copyright (C) 2021, Champs Libres Cooperative SCRLFS, | ||||
|  * <http://www.champs-libres.coop>, <info@champs-libres.coop> | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\ActivityBundle\Repository; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Chill\ActivityBundle\Repository\ActivityRepository; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\MainBundle\Entity\Scope; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Doctrine\ORM\Query\Expr\Orx; | ||||
| use Chill\MainBundle\Security\Authorization\AuthorizationHelper; | ||||
| use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||||
| use Symfony\Component\Security\Core\Role\Role; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
|  | ||||
| final class ActivityACLAwareRepository  | ||||
| { | ||||
|     private AuthorizationHelper $authorizationHelper; | ||||
|  | ||||
|     private TokenStorageInterface $tokenStorage; | ||||
|  | ||||
|     private ActivityRepository $repository; | ||||
|  | ||||
|     private EntityManagerInterface $em; | ||||
|  | ||||
|     public function __construct( | ||||
|         AuthorizationHelper $authorizationHelper, | ||||
|         TokenStorageInterface $tokenStorage, | ||||
|         ActivityRepository $repository, | ||||
|         EntityManagerInterface $em | ||||
|     ) { | ||||
|         $this->authorizationHelper = $authorizationHelper; | ||||
|         $this->tokenStorage = $tokenStorage; | ||||
|         $this->repository = $repository; | ||||
|         $this->em = $em; | ||||
|     } | ||||
|  | ||||
|     public function queryTimelineIndexer(string $context, array $args = []): array | ||||
|     { | ||||
|         $metadataActivity = $this->em->getClassMetadata(Activity::class); | ||||
|  | ||||
|         $from = $this->getFromClauseCenter($args); | ||||
|         $where = $this->getWhereClause($context, $args); | ||||
|  | ||||
|         return [ | ||||
|            'id' => $metadataActivity->getTableName() | ||||
|                 .'.'.$metadataActivity->getColumnName('id'), | ||||
|            'type' => 'activity', | ||||
|            'date' => $metadataActivity->getTableName() | ||||
|                 .'.'.$metadataActivity->getColumnName('date'), | ||||
|            'FROM' => $from, | ||||
|            'WHERE' => $where | ||||
|        ]; | ||||
|     } | ||||
|  | ||||
|     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 = []; | ||||
|  | ||||
|         // condition will be: | ||||
|         // FROM activity JOIN person            -- not set by us | ||||
|         // ON activity.person_id = person.id    -- not set by us | ||||
|         // WHERE                                -- not set by us | ||||
|         // activity.person_id = ? AND           -- only if $context = person | ||||
|         // (                                    -- begin loop through centers, center#0 | ||||
|         //   person.center_id = ?                | ||||
|         //   AND (                              -- begin loop for scopes within centers | ||||
|         //      activity.scope_id = ?           -- scope#0 | ||||
|         //      OR                              -- if scope#i where i > 0 | ||||
|         //      activity.scope_id = ?           -- scope#1 | ||||
|         //      ) | ||||
|         //  ) | ||||
|         //  OR                                  -- if center#i where i > 0 | ||||
|         // (                                    -- begin loop through centers, center#1 | ||||
|         //   person.center_id = ?                | ||||
|         //   AND (                              -- begin loop for scopes within centers | ||||
|         //      activity.scope_id = ?           -- scope#0 | ||||
|         //      OR                              -- if scope#i where i > 0 | ||||
|         //      activity.scope_id = ?           -- scope#1 | ||||
|         //      ) | ||||
|         //  ) | ||||
|          | ||||
|         $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: | ||||
|         $role = new Role(ActivityVoter::SEE); | ||||
|         $reachableCenters = $this->authorizationHelper->getReachableCenters($this->tokenStorage->getToken()->getUser(),  | ||||
|                 $role); | ||||
|          | ||||
|         if (count($reachableCenters) === 0) { | ||||
|             // insert a dummy condition | ||||
|             return 'FALSE = TRUE'; | ||||
|         } | ||||
|  | ||||
|         if ($context === 'person') {  | ||||
|             // we start with activities having the person_id linked to person  | ||||
|             $where .= sprintf('%s = ? AND ', $activityToPerson); | ||||
|             $parameters[] = $person->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'])) { | ||||
|                 continue; | ||||
|             } | ||||
|             // we get all the reachable scopes for this center | ||||
|             $reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), $role, $center); | ||||
|             // we get the ids for those scopes | ||||
|             $reachablesScopesId = array_map( | ||||
|                 function(Scope $scope) { return $scope->getId(); },  | ||||
|                 $reachableScopes | ||||
|             ); | ||||
|  | ||||
|             // if not the first center | ||||
|             if ($centersI > 0) { | ||||
|                 $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 ($scopesI > 0) { | ||||
|                     $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]; | ||||
|     } | ||||
|  | ||||
| } | ||||
| /* | ||||
|         $qb = $this->repository->createQueryBuilder('a'); | ||||
|         $qb->select(['a.id', "'activity'", 'a.date']); | ||||
|         $qb->join('a.person', 'p'); | ||||
|  | ||||
|         switch($context) { | ||||
|             case 'center':  | ||||
|                 $qb->where($this->queryTimelineIndexerWhereForCenter($qb, $args['centers'])); | ||||
|                 break; | ||||
|             default: | ||||
|                 throw new \LogicException('context not supported'); | ||||
|         } | ||||
|  | ||||
|         if ($from) { | ||||
|             $qb->andWhere($qb->gt('a.date', ':from')); | ||||
|             $qb->setParameter('from', $from); | ||||
|         } | ||||
|  | ||||
|         if ($to) { | ||||
|             $qb->andWhere($qb->gt('a.date', ':to')); | ||||
|             $qb->setParameter('to', $to); | ||||
|         } | ||||
|  | ||||
|         return $qb->getQuery(); | ||||
|     } | ||||
|  | ||||
|     private function queryTimelineIndexerWhereForCenter(QueryBuilder $qb, array $centers): Orx | ||||
|     { | ||||
|         $i = 0; | ||||
|         $orx = $qb->expr()->orX(); | ||||
|  | ||||
|         foreach ($centers as $center) { | ||||
|             $andx = $qb->expr()->andX(); | ||||
|             $andx->add($qb->expr()->eq('p.center', ":center_$i")); | ||||
|             $qb->setParameter("center_$i", $center); | ||||
|             $i++; | ||||
|  | ||||
|             $scopes = $this->authorizationHelper->getReachableCircles( | ||||
|                 $this->tokenStorage->getToken()->getUser(), | ||||
|                 new Role(ActivityVoter::SEE_DETAILS), | ||||
|                 $center, | ||||
|             ); | ||||
|              | ||||
|             foreach ($scopes as $scope) { | ||||
|                 $andx->add($qb->expr()->eq('a.scope', ":scope_$i")); | ||||
|                 $qb->setParameter("scope_$i", $scope); | ||||
|                 $i++; | ||||
|             } | ||||
|  | ||||
|             $orx->add($andx); | ||||
|         } | ||||
|  | ||||
|         return $orx; | ||||
|     } | ||||
| } */ | ||||
| @@ -0,0 +1,42 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * Copyright (C) 2021, Champs Libres Cooperative SCRLFS, | ||||
|  * <http://www.champs-libres.coop>, <info@champs-libres.coop> | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\ActivityBundle\Repository; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | ||||
| use Doctrine\Persistence\ManagerRegistry; | ||||
|  | ||||
| /** | ||||
|  * @method AccompanyingPeriodParticipation|null find($id, $lockMode = null, $lockVersion = null) | ||||
|  * @method AccompanyingPeriodParticipation|null findOneBy(array $criteria, array $orderBy = null) | ||||
|  * @method AccompanyingPeriodParticipation[]    findAll() | ||||
|  * @method AccompanyingPeriodParticipation[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) | ||||
|  */ | ||||
| class ActivityRepository extends ServiceEntityRepository | ||||
| { | ||||
|     public function __construct(ManagerRegistry $registry) | ||||
|     { | ||||
|         parent::__construct($registry, Activity::class); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,11 +1,11 @@ | ||||
| {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} | ||||
|  | ||||
| <div> | ||||
|     <h3>{{ activity.date|format_date('long') }}<span class="activity"> / {{ 'Activity'|trans }}</span></h3> | ||||
|   <h3>{% if 'person' != context %}{{ activity.person|chill_entity_render_box({'addLink': true}) }} / {% endif %}{{ activity.date|format_date('long') }}<span class="activity"> / {{ 'Activity'|trans }}</span></h3> | ||||
|     <div class="statement"> | ||||
|         <span class="statement">{{ '%user% has done an %activity_type%'|trans( | ||||
|            { | ||||
|              '%user%' : user, | ||||
|              '%user%' : activity.user, | ||||
|              '%activity_type%': activity.type.name|localize_translatable_string, | ||||
|              '%date%' : activity.date|format_date('long') } | ||||
|         ) }}</span> | ||||
| @@ -29,13 +29,13 @@ | ||||
|  | ||||
|     <ul class="record_actions"> | ||||
|         <li> | ||||
|             <a href="{{ path('chill_activity_activity_show', { 'person_id': person.id, 'id': activity.id} ) }}" class="sc-button bt-view"> | ||||
|             <a href="{{ path('chill_activity_activity_show', { 'person_id': activity.person.id, 'id': activity.id} ) }}" class="sc-button bt-view"> | ||||
|                 {{ 'Show the activity'|trans }} | ||||
|             </a> | ||||
|         </li> | ||||
|         {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %} | ||||
|         <li> | ||||
|             <a href="{{ path('chill_activity_activity_edit', { 'person_id': person.id, 'id': activity.id} ) }}" class="sc-button bt-edit"> | ||||
|             <a href="{{ path('chill_activity_activity_edit', { 'person_id': activity.person.id, 'id': activity.id} ) }}" class="sc-button bt-edit"> | ||||
|                 {{ 'Edit the activity'|trans }} | ||||
|             </a> | ||||
|         </li> | ||||
|   | ||||
| @@ -21,6 +21,7 @@ | ||||
| namespace Chill\ActivityBundle\Timeline; | ||||
|  | ||||
| use Chill\MainBundle\Timeline\TimelineProviderInterface; | ||||
| use Chill\ActivityBundle\Repository\ActivityACLAwareRepository; | ||||
| use Doctrine\ORM\EntityManager; | ||||
| use Chill\MainBundle\Security\Authorization\AuthorizationHelper; | ||||
| use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||||
| @@ -55,6 +56,10 @@ class TimelineActivityProvider implements TimelineProviderInterface | ||||
|      * @var \Chill\MainBundle\Entity\User  | ||||
|      */ | ||||
|     protected $user; | ||||
|  | ||||
|     protected ActivityACLAwareRepository $aclAwareRepository; | ||||
|  | ||||
|     private const SUPPORTED_CONTEXTS = [ 'center', 'person']; | ||||
|      | ||||
|     /** | ||||
|      * TimelineActivityProvider constructor. | ||||
| @@ -66,11 +71,13 @@ class TimelineActivityProvider implements TimelineProviderInterface | ||||
|     public function __construct( | ||||
|         EntityManager $em, | ||||
|         AuthorizationHelper $helper, | ||||
|         TokenStorageInterface $storage | ||||
|         TokenStorageInterface $storage, | ||||
|         ActivityACLAwareRepository $aclAwareRepository | ||||
|     ) | ||||
|     { | ||||
|         $this->em = $em; | ||||
|         $this->helper = $helper; | ||||
|         $this->aclAwareRepository = $aclAwareRepository; | ||||
|          | ||||
|         if (!$storage->getToken()->getUser() instanceof \Chill\MainBundle\Entity\User) | ||||
|         { | ||||
| @@ -86,10 +93,13 @@ class TimelineActivityProvider implements TimelineProviderInterface | ||||
|      */ | ||||
|     public function fetchQuery($context, array $args) | ||||
|     { | ||||
|         $this->checkContext($context); | ||||
|         //$this->checkContext($context); | ||||
|         // | ||||
|         if ('center' === $context) { | ||||
|             return $this->aclAwareRepository->queryTimelineIndexer($context, $args); | ||||
|         } | ||||
|          | ||||
|         $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); | ||||
|         $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); | ||||
|          | ||||
|         return array( | ||||
|            'id' => $metadataActivity->getTableName() | ||||
| @@ -102,10 +112,40 @@ class TimelineActivityProvider implements TimelineProviderInterface | ||||
|                    $args['person']) | ||||
|         ); | ||||
|     } | ||||
|      | ||||
|     private function getWhereClause(ClassMetadata $metadataActivity,  | ||||
|             ClassMetadata $metadataPerson, Person $person) | ||||
|  | ||||
|     private function getFromClause(string $context) | ||||
|     { | ||||
|         switch ($context) { | ||||
|             case 'person':  | ||||
|                 return $this->getFromClausePerson($metadataActivity, $metadataPerson); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function getWhereClause(string $context, array $args) | ||||
|     { | ||||
|         switch ($context) { | ||||
|             case 'person':  | ||||
|                 return $this->getWhereClause($args['person']); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @var $centers array|Center[] | ||||
|      */ | ||||
|     private function getWhereClauseForCenter(array $centers) | ||||
|     { | ||||
|         $clause = ""; | ||||
|         $role = new Role('CHILL_ACTIVITY_SEE'); | ||||
|  | ||||
|  | ||||
|     } | ||||
|      | ||||
|     private function getWhereClauseForPerson(Person $person) | ||||
|     { | ||||
|         $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); | ||||
|         $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); | ||||
|  | ||||
|         $role = new Role('CHILL_ACTIVITY_SEE'); | ||||
|         $reachableCenters = $this->helper->getReachableCenters($this->user,  | ||||
|                 $role); | ||||
| @@ -144,9 +184,25 @@ class TimelineActivityProvider implements TimelineProviderInterface | ||||
|         return $whereClause; | ||||
|     } | ||||
|      | ||||
|     private function getFromClause(ClassMetadata $metadataActivity, | ||||
|             ClassMetadata $metadataPerson) | ||||
|     private function getFromClausePerson() | ||||
|     { | ||||
|         $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); | ||||
|         $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); | ||||
|         $associationMapping = $metadataActivity->getAssociationMapping('person'); | ||||
|          | ||||
|         return $metadataActivity->getTableName().' JOIN ' | ||||
|             .$metadataPerson->getTableName().' ON ' | ||||
|             .$metadataPerson->getTableName().'.'. | ||||
|                 $associationMapping['joinColumns'][0]['referencedColumnName'] | ||||
|             .' = ' | ||||
|             .$associationMapping['joinColumns'][0]['name'] | ||||
|             ; | ||||
|     } | ||||
|      | ||||
|     private function getFromClauseCenter() | ||||
|     { | ||||
|         $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); | ||||
|         $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); | ||||
|         $associationMapping = $metadataActivity->getAssociationMapping('person'); | ||||
|          | ||||
|         return $metadataActivity->getTableName().' JOIN ' | ||||
| @@ -183,14 +239,13 @@ class TimelineActivityProvider implements TimelineProviderInterface | ||||
|     { | ||||
|         $this->checkContext($context); | ||||
|          | ||||
|         return array( | ||||
|         return [ | ||||
|            'template' => 'ChillActivityBundle:Timeline:activity_person_context.html.twig', | ||||
|            'template_data' => array( | ||||
|            'template_data' => [ | ||||
|               'activity' => $entity, | ||||
|               'person' => $args['person'], | ||||
|               'user' => $entity->getUser() | ||||
|            ) | ||||
|         ); | ||||
|               'context' => $context | ||||
|             ] | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -210,7 +265,7 @@ class TimelineActivityProvider implements TimelineProviderInterface | ||||
|      */ | ||||
|     private function checkContext($context) | ||||
|     { | ||||
|         if ($context !== 'person') { | ||||
|         if (FALSE === \in_array($context, self::SUPPORTED_CONTEXTS)) { | ||||
|             throw new \LogicException("The context '$context' is not " | ||||
|                   . "supported. Currently only 'person' is supported"); | ||||
|         } | ||||
|   | ||||
| @@ -22,6 +22,8 @@ services: | ||||
|             - '@doctrine.orm.entity_manager' | ||||
|             - '@chill.main.security.authorization.helper' | ||||
|             - '@security.token_storage' | ||||
|             - '@Chill\ActivityBundle\Repository\ActivityACLAwareRepository' | ||||
|         public: true | ||||
|         tags: | ||||
|             - { name: chill.timeline, context: 'person' } | ||||
|             - { name: chill.timeline, context: 'center' } | ||||
|   | ||||
| @@ -1,18 +1,32 @@ | ||||
| --- | ||||
| services: | ||||
|     chill_activity.repository.activity_type: | ||||
|         class: Doctrine\ORM\EntityRepository | ||||
|         factory: ['@doctrine.orm.entity_manager', getRepository] | ||||
|         arguments: | ||||
|             - 'Chill\ActivityBundle\Entity\ActivityType' | ||||
|              | ||||
|  | ||||
|     chill_activity.repository.reason: | ||||
|         class: Doctrine\ORM\EntityRepository | ||||
|         factory: ['@doctrine.orm.entity_manager', getRepository] | ||||
|         arguments: | ||||
|             - 'Chill\ActivityBundle\Entity\ActivityReason' | ||||
|              | ||||
|  | ||||
|     chill_activity.repository.reason_category: | ||||
|         class: Doctrine\ORM\EntityRepository | ||||
|         factory: ['@doctrine.orm.entity_manager', getRepository] | ||||
|         arguments: | ||||
|             - 'Chill\ActivityBundle\Entity\ActivityReasonCategory' | ||||
|  | ||||
|     Chill\ActivityBundle\Repository\ActivityRepository: | ||||
|         tags: [doctrine.repository_service] | ||||
|         arguments: | ||||
|             - '@Doctrine\Persistence\ManagerRegistry' | ||||
|  | ||||
|     Chill\ActivityBundle\Repository\ActivityACLAwareRepository: | ||||
|         arguments: | ||||
|             $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' | ||||
|             $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' | ||||
|             $repository: '@Chill\ActivityBundle\Repository\ActivityRepository' | ||||
|             $em: '@Doctrine\ORM\EntityManagerInterface' | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,91 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * Copyright (C) 2015 Champs-Libres Coopérative <info@champs-libres.coop> | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Controller; | ||||
|  | ||||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||||
| use Symfony\Component\EventDispatcher\EventDispatcherInterface; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| use Symfony\Component\HttpFoundation\Request; | ||||
| use Chill\MainBundle\Timeline\TimelineBuilder; | ||||
| use Chill\MainBundle\Pagination\PaginatorFactory; | ||||
| use Chill\PersonBundle\Security\Authorization\PersonVoter; | ||||
| use Chill\MainBundle\Security\Authorization\AuthorizationHelper; | ||||
| use Symfony\Component\Security\Core\Role\Role; | ||||
| use Symfony\Component\Routing\Annotation\Route; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| class TimelineCenterController extends AbstractController | ||||
| { | ||||
|      | ||||
|     protected TimelineBuilder $timelineBuilder; | ||||
|      | ||||
|     protected PaginatorFactory $paginatorFactory; | ||||
|  | ||||
|     private Security $security; | ||||
|      | ||||
|     public function __construct( | ||||
|         TimelineBuilder $timelineBuilder, | ||||
|         PaginatorFactory $paginatorFactory, | ||||
|         Security $security | ||||
|     ) { | ||||
|         $this->timelineBuilder = $timelineBuilder; | ||||
|         $this->paginatorFactory = $paginatorFactory; | ||||
|         $this->security = $security; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @Route("/{_locale}/center/timeline", | ||||
|      *    name="chill_center_timeline", | ||||
|      *    methods={"GET"} | ||||
|      *  ) | ||||
|      */  | ||||
|     public function centerAction(Request $request) | ||||
|     { | ||||
|         // collect reachable center for each group | ||||
|         $user = $this->security->getUser(); | ||||
|         $centers = []; | ||||
|         foreach ($user->getGroupCenters() as $group) { | ||||
|             $centers[] = $group->getCenter(); | ||||
|         } | ||||
|  | ||||
|         if (0 === count($centers)) { | ||||
|             throw $this->createNotFoundException(); | ||||
|         } | ||||
|          | ||||
|         $nbItems = $this->timelineBuilder->countItems('center',  | ||||
|             [ 'centers' => $centers ] | ||||
|             ); | ||||
|          | ||||
|         $paginator = $this->paginatorFactory->create($nbItems); | ||||
|          | ||||
|         return $this->render('@ChillMain/Timeline/index.html.twig', array | ||||
|             ( | ||||
|                 'timeline' => $this->timelineBuilder->getTimelineHTML( | ||||
|                     'center',  | ||||
|                     [ 'centers' => $centers ], | ||||
|                     $paginator->getCurrentPage()->getFirstItemNumber(), | ||||
|                     $paginator->getItemsPerPage() | ||||
|                     ), | ||||
|                 'nb_items' => $nbItems, | ||||
|                 'paginator' => $paginator | ||||
|             ) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| <div class="timeline"> | ||||
|     {% for result in results %} | ||||
|         <div class="timeline-item {% if loop.index0 is even %}even{% else %}odd{% endif %}"> | ||||
|             {% include result.template with result.template_data %} | ||||
|         </div> | ||||
|     {% endfor %} | ||||
| </div> | ||||
| @@ -1,7 +1,15 @@ | ||||
| <div class="timeline"> | ||||
|     {% for result in results %} | ||||
|         <div class="timeline-item {% if loop.index0 is even %}even{% else %}odd{% endif %}"> | ||||
|             {% include result.template with result.template_data %} | ||||
|         </div> | ||||
|     {% endfor %} | ||||
| </div> | ||||
| {% extends "@ChillMain/layout.html.twig"  %} | ||||
|  | ||||
| {% block content %} | ||||
|   <div id="container content"> | ||||
|     <div class="grid-8 centered"> | ||||
|       <h1>{{ 'Global timeline'|trans }}</h1> | ||||
|  | ||||
|       {{ timeline|raw }} | ||||
|        | ||||
|       {% if nb_items > paginator.getItemsPerPage %} | ||||
|       {{ chill_pagination(paginator) }} | ||||
|       {% endif %} | ||||
|     </div> | ||||
|   </div> | ||||
|   {% endblock content %} | ||||
|   | ||||
| @@ -23,6 +23,8 @@ use Doctrine\ORM\Query\ResultSetMapping; | ||||
| use Doctrine\DBAL\Types\Type; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Symfony\Component\DependencyInjection\ContainerAwareInterface; | ||||
| use Doctrine\ORM\Query; | ||||
| use Doctrine\ORM\NativeQuery; | ||||
|  | ||||
| /** | ||||
|  * Build timeline | ||||
| @@ -78,14 +80,14 @@ class TimelineBuilder implements ContainerAwareInterface | ||||
|      */ | ||||
|     public function getTimelineHTML($context, array $args, $firstItem = 0, $number = 20) | ||||
|     { | ||||
|         $union = $this->buildUnionQuery($context, $args); | ||||
|         list($union, $parameters) = $this->buildUnionQuery($context, $args); | ||||
|          | ||||
|         //add ORDER BY clause and LIMIT | ||||
|         $query = $union . sprintf(' ORDER BY date DESC LIMIT %d OFFSET %d', | ||||
|             $number, $firstItem); | ||||
|          | ||||
|         // run query and handle results | ||||
|         $fetched = $this->runUnionQuery($query); | ||||
|         $fetched = $this->runUnionQuery($query, $parameters); | ||||
|         $entitiesByKey = $this->getEntities($fetched, $context); | ||||
|          | ||||
|         return $this->render($fetched, $entitiesByKey, $context, $args); | ||||
| @@ -100,16 +102,18 @@ class TimelineBuilder implements ContainerAwareInterface | ||||
|      */ | ||||
|     public function countItems($context, array $args) | ||||
|     { | ||||
|         $union = $this->buildUnionQuery($context, $args); | ||||
|          | ||||
|         // embed the union query inside a count query | ||||
|         $count = sprintf('SELECT COUNT(sq.id) AS total FROM (%s) as sq', $union); | ||||
|          | ||||
|         $rsm = (new ResultSetMapping()) | ||||
|             ->addScalarResult('total', 'total', Type::INTEGER); | ||||
|  | ||||
|         list($select, $parameters) = $this->buildUnionQuery($context, $args); | ||||
|  | ||||
|         // embed the union query inside a count query | ||||
|         $countQuery = sprintf('SELECT COUNT(sq.id) AS total FROM (%s) as sq', $select); | ||||
|  | ||||
|         $nq = $this->em->createNativeQuery($countQuery, $rsm); | ||||
|         $nq->setParameters($parameters); | ||||
|          | ||||
|         return $this->em->createNativeQuery($count, $rsm) | ||||
|             ->getSingleScalarResult(); | ||||
|         return $nq->getSingleScalarResult(); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
| @@ -154,40 +158,56 @@ class TimelineBuilder implements ContainerAwareInterface | ||||
|      * | ||||
|      * @uses self::buildSelectQuery to build individual SELECT queries | ||||
|      * | ||||
|      * @param string $context | ||||
|      * @param mixed $args | ||||
|      * @param int $page | ||||
|      * @param int $number | ||||
|      * @return string | ||||
|      * @throws \LogicException if no builder have been defined for this context | ||||
|      * @return array, where first element is the query, the second one an array with the parameters | ||||
|      */ | ||||
|     private function buildUnionQuery($context, array $args) | ||||
|     private function buildUnionQuery(string $context, array $args): array | ||||
|     { | ||||
|         //append SELECT queries with UNION keyword between them | ||||
|         $union = ''; | ||||
|         $parameters = []; | ||||
|  | ||||
|         foreach($this->getProvidersByContext($context) as $provider) { | ||||
|             $select = $this->buildSelectQuery($provider, $context, $args); | ||||
|             $append = ($union === '') ?  $select : ' UNION '.$select; | ||||
|             $data = $provider->fetchQuery($context, $args); | ||||
|             list($select, $selectParameters) = $this->buildSelectQuery($data); | ||||
|             $append = empty($union) ?  $select : ' UNION '.$select; | ||||
|             $union .= $append; | ||||
|             $parameters = array_merge($parameters, $selectParameters); | ||||
|         } | ||||
|          | ||||
|         return $union; | ||||
|         return [$union, $parameters]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Hack to replace the arbitrary "AS" statement in DQL | ||||
|      * into proper SQL query | ||||
|      * TODO remove | ||||
|     private function replaceASInDQL(string $dql): string | ||||
|     { | ||||
|         $pattern = '/^(SELECT\s+[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s{0,},\s{0,}[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s{0,},\s{0,}[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s+FROM.*)/'; | ||||
|         $replacements = '${1} AS id ${3} AS type ${5} AS date ${7}'; | ||||
|  | ||||
|         $s = \preg_replace($pattern, $replacements, $dql, 1); | ||||
|  | ||||
|         if (NULL === $s) { | ||||
|             throw new \RuntimeException('Could not replace the "AS" statement produced by '. | ||||
|                 'DQL with normal SQL AS: '.$dql); | ||||
|         } | ||||
|  | ||||
|         return $s; | ||||
|     } | ||||
|      */ | ||||
|      | ||||
|     /** | ||||
|      * return the SQL SELECT query as a string, | ||||
|      * | ||||
|      * @uses TimelineProfiderInterface::fetchQuery use the fetchQuery function | ||||
|      * @param \Chill\MainBundle\Timeline\TimelineProviderInterface $provider | ||||
|      * @param string $context | ||||
|      * @param mixed[] $args | ||||
|      * @return string | ||||
|      */ | ||||
|     private function buildSelectQuery(TimelineProviderInterface $provider, $context, array $args) | ||||
|     private function buildSelectQuery(array $data): array | ||||
|     { | ||||
|         $data = $provider->fetchQuery($context, $args); | ||||
|          | ||||
|         return sprintf( | ||||
|         $parameters = []; | ||||
|  | ||||
|         $sql = sprintf( | ||||
|                 'SELECT %s AS id, ' | ||||
|                 . '%s AS "date", ' | ||||
|                 . "'%s' AS type " | ||||
| @@ -197,16 +217,19 @@ class TimelineBuilder implements ContainerAwareInterface | ||||
|                 $data['date'], | ||||
|                 $data['type'], | ||||
|                 $data['FROM'], | ||||
|                 $data['WHERE']); | ||||
|                 is_string($data['WHERE']) ? $data['WHERE'] : $data['WHERE'][0] | ||||
|             ); | ||||
|  | ||||
|         return [$sql, $data['WHERE'][1]]; | ||||
|   | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * run the UNION query and return result as an array | ||||
|      * | ||||
|      * @param string $query | ||||
|      * @return array | ||||
|      * @return array an array with the results | ||||
|      */ | ||||
|     private function runUnionQuery($query) | ||||
|     private function runUnionQuery(string $query, array $parameters): array | ||||
|     { | ||||
|         $resultSetMapping = (new ResultSetMapping()) | ||||
|                 ->addScalarResult('id', 'id') | ||||
| @@ -214,7 +237,8 @@ class TimelineBuilder implements ContainerAwareInterface | ||||
|                 ->addScalarResult('date', 'date'); | ||||
|          | ||||
|         return $this->em->createNativeQuery($query, $resultSetMapping) | ||||
|                 ->getArrayResult(); | ||||
|             ->setParameters($parameters) | ||||
|             ->getArrayResult(); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
| @@ -274,7 +298,7 @@ class TimelineBuilder implements ContainerAwareInterface | ||||
|         } | ||||
|          | ||||
|         return $this->container->get('templating') | ||||
|                 ->render('@ChillMain/Timeline/index.html.twig', array( | ||||
|                 ->render('@ChillMain/Timeline/chain_timelines.html.twig', array( | ||||
|                     'results' => $timelineEntries | ||||
|                 )); | ||||
|          | ||||
|   | ||||
| @@ -1,3 +1,7 @@ | ||||
| chill_main_controllers: | ||||
|     resource: '../Controller/' | ||||
|     type: annotation | ||||
|  | ||||
| chill_main_admin_permissionsgroup: | ||||
|     resource: "@ChillMainBundle/config/routes/permissionsgroup.yaml" | ||||
|     prefix:   "{_locale}/admin/permissionsgroup" | ||||
|   | ||||
| @@ -4,4 +4,7 @@ services: | ||||
|         arguments: | ||||
|             - "@doctrine.orm.entity_manager" | ||||
|         calls: | ||||
|             - [ setContainer, ["@service_container"]] | ||||
|             - [ setContainer, ["@service_container"]] | ||||
|     # alias: | ||||
|     Chill\MainBundle\Timeline\TimelineBuilder: '@chill_main.timeline_builder' | ||||
|  | ||||
|   | ||||
| @@ -27,32 +27,17 @@ use Symfony\Component\HttpFoundation\Request; | ||||
| use Chill\MainBundle\Timeline\TimelineBuilder; | ||||
| use Chill\MainBundle\Pagination\PaginatorFactory; | ||||
| use Chill\PersonBundle\Security\Authorization\PersonVoter; | ||||
| use Chill\MainBundle\Security\Authorization\AuthorizationHelper; | ||||
| use Symfony\Component\Security\Core\Role\Role; | ||||
|  | ||||
| /** | ||||
|  * Class TimelinePersonController | ||||
|  * | ||||
|  * @package Chill\PersonBundle\Controller | ||||
|  * @author Julien Fastré <julien.fastre@champs-libres.coop> | ||||
|  */ | ||||
| class TimelinePersonController extends AbstractController | ||||
| { | ||||
|      | ||||
|     /** | ||||
|      * @var EventDispatcherInterface | ||||
|      */ | ||||
|     protected $eventDispatcher; | ||||
|     protected EventDispatcherInterface $eventDispatcher; | ||||
|      | ||||
|     /** | ||||
|      * | ||||
|      * @var TimelineBuilder | ||||
|      */ | ||||
|     protected $timelineBuilder; | ||||
|     protected TimelineBuilder $timelineBuilder; | ||||
|      | ||||
|     /** | ||||
|      * | ||||
|      * @var PaginatorFactory | ||||
|      */ | ||||
|     protected $paginatorFactory; | ||||
|     protected PaginatorFactory $paginatorFactory; | ||||
|      | ||||
|     /** | ||||
|      * TimelinePersonController constructor. | ||||
| @@ -62,11 +47,13 @@ class TimelinePersonController extends AbstractController | ||||
|     public function __construct( | ||||
|         EventDispatcherInterface $eventDispatcher, | ||||
|         TimelineBuilder $timelineBuilder, | ||||
|         PaginatorFactory $paginatorFactory | ||||
|         PaginatorFactory $paginatorFactory, | ||||
|         AuthorizationHelper $authorizationHelper | ||||
|     ) { | ||||
|         $this->eventDispatcher = $eventDispatcher; | ||||
|         $this->timelineBuilder = $timelineBuilder; | ||||
|         $this->paginatorFactory = $paginatorFactory; | ||||
|         $this->authorizationHelper = $authorizationHelper; | ||||
|     } | ||||
|      | ||||
|      | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| <span class="chill-entity chill-entity__person"> | ||||
|   {% if addLink and is_granted('CHILL_PERSON_SEE', person) %} | ||||
|     {% set showLink = true %} | ||||
|     <a href="{{ chill_path_add_return_path('chill_person_view', { 'person_id': person.id }) }}"> | ||||
|   {% endif %} | ||||
|     <span class="chill-entity__person__first-name"> {{ person.firstName }}</span> | ||||
|     <span class="chill-entity__person__last-name">{{ person.lastName }}</span> | ||||
|     {% if addAltNames %} | ||||
|       {% for n in person.altNames %} | ||||
|         {% if loop.first %}({% else %} {% endif %} | ||||
|           <span class="chill-entity__person__alt-name chill-entity__person__altname--{{ n.key }}"> | ||||
|             {{ n.label }} | ||||
|           </span> | ||||
|         {% if loop.last %}){% endif %} | ||||
|       {% endfor %} | ||||
|     {% endif %} | ||||
|   {% if showLink is defined %}</a>{% endif %} | ||||
| </span> | ||||
| @@ -23,6 +23,8 @@ namespace Chill\PersonBundle\Templating\Entity; | ||||
| use Chill\MainBundle\Templating\Entity\AbstractChillEntityRender; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; | ||||
| use Symfony\Component\Templating\EngineInterface; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Render a Person | ||||
| @@ -30,15 +32,16 @@ use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; | ||||
|  */ | ||||
| class PersonRender extends AbstractChillEntityRender | ||||
| { | ||||
|     /** | ||||
|      * | ||||
|      * @var ConfigPersonAltNamesHelper | ||||
|      */ | ||||
|     protected $configAltNamesHelper; | ||||
|     private ConfigPersonAltNamesHelper $configAltNamesHelper; | ||||
|  | ||||
|     private EngineInterface $engine; | ||||
|      | ||||
|     public function __construct(ConfigPersonAltNamesHelper $configAltNamesHelper) | ||||
|     { | ||||
|     public function __construct( | ||||
|         ConfigPersonAltNamesHelper $configAltNamesHelper, | ||||
|         EngineInterface $engine | ||||
|     ) { | ||||
|         $this->configAltNamesHelper = $configAltNamesHelper; | ||||
|         $this->engine = $engine; | ||||
|     } | ||||
|      | ||||
|     /** | ||||
| @@ -49,13 +52,13 @@ class PersonRender extends AbstractChillEntityRender | ||||
|      */ | ||||
|     public function renderBox($person, array $options): string | ||||
|     { | ||||
|         return  | ||||
|             $this->getDefaultOpeningBox('person'). | ||||
|             '<span class="chill-entity__person__first-name">'.$person->getFirstName().'</span>'. | ||||
|             ' <span class="chill-entity__person__last-name">'.$person->getLastName().'</span>'. | ||||
|             $this->addAltNames($person, true). | ||||
|             $this->getDefaultClosingBox() | ||||
|             ; | ||||
|         return $this->engine->render('@ChillPerson/Entity/person.html.twig', | ||||
|             [ | ||||
|                 'person' => $person, | ||||
|                 'addAltNames' => $this->configAltNamesHelper->hasAltNames(), | ||||
|                 'addLink' => $options['addLink'] ?? false | ||||
|             ] | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -69,7 +72,7 @@ class PersonRender extends AbstractChillEntityRender | ||||
|         return $person->getFirstName().' '.$person->getLastName() | ||||
|             .$this->addAltNames($person, false); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     protected function addAltNames(Person $person, bool $addSpan) | ||||
|     { | ||||
|         $str = ''; | ||||
|   | ||||
| @@ -16,6 +16,7 @@ services: | ||||
|             $eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface' | ||||
|             $timelineBuilder: '@chill_main.timeline_builder' | ||||
|             $paginatorFactory: '@chill_main.paginator_factory' | ||||
|             $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' | ||||
|         tags: ['controller.service_arguments'] | ||||
|  | ||||
|     Chill\PersonBundle\Controller\AccompanyingPeriodController: | ||||
|   | ||||
| @@ -2,6 +2,7 @@ services: | ||||
|     Chill\PersonBundle\Templating\Entity\PersonRender: | ||||
|         arguments: | ||||
|             $configAltNamesHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper' | ||||
|             $engine: '@Symfony\Component\Templating\EngineInterface' | ||||
|         tags: | ||||
|             - 'chill.render_entity' | ||||
|              | ||||
|   | ||||
		Reference in New Issue
	
	Block a user