mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-03 18:58:24 +00:00 
			
		
		
		
	Compare commits
	
		
			52 Commits
		
	
	
		
			feature-ad
			...
			dune-risky
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 915a2e7284 | |||
| cb99074d1f | |||
| cfd9f1bab1 | |||
| 358410cde1 | |||
| 06c74ed5ed | |||
| 25c986cc61 | |||
| cad8174333 | |||
| 75c586fbf8 | |||
| 2cb9dfc250 | |||
| 5350a09951 | |||
| 9b1a66c992 | |||
| 74541f360b | |||
| ea477a9842 | |||
| 73653744d7 | |||
| c3ef8d112c | |||
| 
						 | 
					8c98f2cf6e | ||
| e1c8278f71 | |||
| 
						 | 
					ed3f46ce7f | ||
| 7503c845df | |||
| 3686a294d3 | |||
| 2db847ada2 | |||
| 
						 | 
					7819c1204c | ||
| 
						 | 
					e470a6a97e | ||
| 
						 | 
					cd8c47449b | ||
| d6e7fadb4f | |||
| 
						 | 
					2be1c08c44 | ||
| 
						 | 
					d426d28ba0 | ||
| 
						 | 
					72e69fc0b3 | ||
| 52472b0ec3 | |||
| cab3b1059d | |||
| 4c82e65c1f | |||
| cf4d7df7ad | |||
| 86d13410c3 | |||
| 19fdf2a503 | |||
| 
						 | 
					5448238697 | ||
| 
						 | 
					c5250a1059 | ||
| b2c1a7b8de | |||
| b4583fc6dc | |||
| f7c508939c | |||
| a2160bef7d | |||
| 8641d6bdce | |||
| 5f4d513aa6 | |||
| f47b15de39 | |||
| 7426dc02cf | |||
| a570160aed | |||
| 7596bd5a06 | |||
| 66426f5102 | |||
| 
						 | 
					03243605da | ||
| 
						 | 
					48e2d2ceab | ||
| 
						 | 
					777fb25860 | ||
| 
						 | 
					03601b9707 | ||
| c205bbddd3 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,8 +1,8 @@
 | 
			
		||||
.composer/*
 | 
			
		||||
composer.phar
 | 
			
		||||
composer.lock
 | 
			
		||||
 | 
			
		||||
docs/build/
 | 
			
		||||
.php_cs.cache
 | 
			
		||||
 | 
			
		||||
###> symfony/framework-bundle ###
 | 
			
		||||
/.env.local
 | 
			
		||||
@@ -19,3 +19,4 @@ docs/build/
 | 
			
		||||
/phpunit.xml
 | 
			
		||||
.phpunit.result.cache
 | 
			
		||||
###< phpunit/phpunit ###
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,10 @@ variables:
 | 
			
		||||
  POSTGRES_PASSWORD: postgres
 | 
			
		||||
  # fetch the chill-app using git submodules
 | 
			
		||||
  GIT_SUBMODULE_STRATEGY: recursive
 | 
			
		||||
  REDIS_HOST: redis
 | 
			
		||||
  REDIS_PORT: 6379
 | 
			
		||||
  REDIS_URL: redis://redis:6379
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Run our tests
 | 
			
		||||
test:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
# Chill framework
 | 
			
		||||
 | 
			
		||||
Documentation of the Chill software.
 | 
			
		||||
 | 
			
		||||
The online documentation can be found at http://docs.chill.social
 | 
			
		||||
 | 
			
		||||
See the [`docs`][1] directory for more.
 | 
			
		||||
 | 
			
		||||
[1]: docs/README.md
 | 
			
		||||
@@ -97,7 +97,7 @@ The has the following signature :
 | 
			
		||||
         * 
 | 
			
		||||
         * @param string $context
 | 
			
		||||
         * @param mixed[] $args the argument to the context.
 | 
			
		||||
         * @return string[]
 | 
			
		||||
         * @return TimelineSingleQuery
 | 
			
		||||
         * @throw  \LogicException if the context is not supported
 | 
			
		||||
         */
 | 
			
		||||
        public function fetchQuery($context, array $args);
 | 
			
		||||
@@ -163,18 +163,16 @@ The has the following signature :
 | 
			
		||||
The `fetchQuery` function
 | 
			
		||||
^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
The fetchQuery function help to build the UNION query to gather events. This function should return an associative array MUST have the following key :
 | 
			
		||||
The fetchQuery function help to build the UNION query to gather events. This function should return an instance of :code:`TimelineSingleQuery`. For you convenience, this object may be build using an associative array with the following keys:
 | 
			
		||||
 | 
			
		||||
* `id` : the name of the id column
 | 
			
		||||
* `type`: a string to indicate the type
 | 
			
		||||
* `date`: the name of the datetime column, used to order entities by date
 | 
			
		||||
* `FROM` (in capital) : the FROM clause. May contains JOIN instructions
 | 
			
		||||
* `FROM`: the FROM clause. May contains JOIN instructions
 | 
			
		||||
* `WHERE`: the WHERE clause;
 | 
			
		||||
* `parameters`: the parameters to pass to the query
 | 
			
		||||
 | 
			
		||||
Those key are optional:
 | 
			
		||||
 | 
			
		||||
* `WHERE` (in capital) : the WHERE clause. 
 | 
			
		||||
 | 
			
		||||
 Where relevant, the data must be quoted to avoid SQL injection.
 | 
			
		||||
The parameters should be replaced into the query by :code:`?`. They will be replaced into the query using prepared statements.
 | 
			
		||||
 | 
			
		||||
`$context` and `$args` are defined by the bundle which will call the timeline rendering. You may use them to build a different query depending on this context.
 | 
			
		||||
 | 
			
		||||
@@ -186,6 +184,15 @@ For instance, if the context is `'person'`, the args will be this array :
 | 
			
		||||
        'person' => $person //a \Chill\PersonBundle\Entity\Person entity
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
For the context :code:`center`, the args will be:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
    array(
 | 
			
		||||
        'centers' => [ ]  // an array of \Chill\MainBundle\Entity\Center entities
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
You should find in the bundle documentation which contexts are arguments the bundle defines.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
@@ -199,13 +206,12 @@ Example of an implementation :
 | 
			
		||||
    namespace Chill\ReportBundle\Timeline;
 | 
			
		||||
 | 
			
		||||
    use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
    use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
    use Doctrine\ORM\EntityManager;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide report for inclusion in timeline
 | 
			
		||||
     *
 | 
			
		||||
     * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
     * @author Champs Libres <info@champs-libres.coop>
 | 
			
		||||
     */
 | 
			
		||||
    class TimelineReportProvider implements TimelineProviderInterface
 | 
			
		||||
    {
 | 
			
		||||
@@ -227,16 +233,17 @@ Example of an implementation :
 | 
			
		||||
                
 | 
			
		||||
                $metadata = $this->em->getClassMetadata('ChillReportBundle:Report');
 | 
			
		||||
                
 | 
			
		||||
                return array(
 | 
			
		||||
                return TimelineSingleQuery::fromArray([
 | 
			
		||||
                   'id' => $metadata->getColumnName('id'),
 | 
			
		||||
                   'type' => 'report',
 | 
			
		||||
                   'date' => $metadata->getColumnName('date'),
 | 
			
		||||
                   'FROM' => $metadata->getTableName(),
 | 
			
		||||
                   'WHERE' => sprintf('%s = %d',
 | 
			
		||||
                   'WHERE' => sprintf('%s = ?',
 | 
			
		||||
                         $metadata
 | 
			
		||||
                            ->getAssociationMapping('person')['joinColumns'][0]['name'],
 | 
			
		||||
                         $args['person']->getId())
 | 
			
		||||
                );
 | 
			
		||||
                            ->getAssociationMapping('person')['joinColumns'][0]['name'])
 | 
			
		||||
                         )
 | 
			
		||||
                   'parameters' => [ $args['person']->getId() ]
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        //....
 | 
			
		||||
 
 | 
			
		||||
@@ -18,11 +18,11 @@
 | 
			
		||||
       <testsuite name="MainBundle">
 | 
			
		||||
         <directory suffix="Test.php">src/Bundle/ChillMainBundle/Tests/</directory>
 | 
			
		||||
       </testsuite>
 | 
			
		||||
 | 
			
		||||
       <!--
 | 
			
		||||
       <testsuite name="PersonBundle">
 | 
			
		||||
         <directory suffix="Test.php">src/Bundle/ChillPersonBundle/Tests/</directory>
 | 
			
		||||
        </testsuite>
 | 
			
		||||
 | 
			
		||||
      -->
 | 
			
		||||
      </testsuites>
 | 
			
		||||
 | 
			
		||||
    <listeners>
 | 
			
		||||
 
 | 
			
		||||
@@ -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,169 @@
 | 
			
		||||
<?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, $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:
 | 
			
		||||
        $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];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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>{{ activity.date|format_date('long') }}<span class="activity"> / {{ 'Activity'|trans }}</span>{% if 'person' != context %} / {{ activity.person|chill_entity_render_box({'addLink': true}) }}{% endif %}</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;
 | 
			
		||||
@@ -28,13 +29,13 @@ use Symfony\Component\Security\Core\Role\Role;
 | 
			
		||||
use Doctrine\ORM\Mapping\ClassMetadata;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
use Chill\ActivityBundle\Entity\Activity;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provide activity for inclusion in timeline
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 * @author Champs Libres <info@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
*/
 | 
			
		||||
class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
@@ -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,67 +93,69 @@ class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
     */
 | 
			
		||||
    public function fetchQuery($context, array $args)
 | 
			
		||||
    {
 | 
			
		||||
        $this->checkContext($context);
 | 
			
		||||
        if ('center' === $context) {
 | 
			
		||||
            return TimelineSingleQuery::fromArray($this->aclAwareRepository
 | 
			
		||||
                ->queryTimelineIndexer($context, $args));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity');
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person');
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
 | 
			
		||||
        [$where, $parameters] =  $this->getWhereClauseForPerson($args['person']);
 | 
			
		||||
        
 | 
			
		||||
        return array(
 | 
			
		||||
        return TimelineSingleQuery::fromArray([
 | 
			
		||||
           'id' => $metadataActivity->getTableName()
 | 
			
		||||
                .'.'.$metadataActivity->getColumnName('id'),
 | 
			
		||||
           'type' => 'activity',
 | 
			
		||||
           'date' => $metadataActivity->getTableName()
 | 
			
		||||
                .'.'.$metadataActivity->getColumnName('date'),
 | 
			
		||||
           'FROM' => $this->getFromClause($metadataActivity, $metadataPerson),
 | 
			
		||||
           'WHERE' => $this->getWhereClause($metadataActivity, $metadataPerson,
 | 
			
		||||
                   $args['person'])
 | 
			
		||||
        );
 | 
			
		||||
           'FROM' => $this->getFromClausePerson($args['person']),
 | 
			
		||||
           'WHERE' => $where,
 | 
			
		||||
           'parameters' => $parameters
 | 
			
		||||
       ]);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function getWhereClause(ClassMetadata $metadataActivity, 
 | 
			
		||||
            ClassMetadata $metadataPerson, Person $person)
 | 
			
		||||
    private function getWhereClauseForPerson(Person $person)
 | 
			
		||||
    {
 | 
			
		||||
        $role = new Role('CHILL_ACTIVITY_SEE');
 | 
			
		||||
        $reachableCenters = $this->helper->getReachableCenters($this->user, 
 | 
			
		||||
                $role);
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
        $associationMapping = $metadataActivity->getAssociationMapping('person');
 | 
			
		||||
        
 | 
			
		||||
        if (count($reachableCenters) === 0) {
 | 
			
		||||
            return 'FALSE = TRUE';
 | 
			
		||||
        $role = new Role('CHILL_ACTIVITY_SEE');
 | 
			
		||||
        $reachableScopes =  $this->helper->getReachableScopes($this->user,
 | 
			
		||||
            $role, $person->getCenter());
 | 
			
		||||
        $whereClause = sprintf(' {activity.person_id} = ? AND {activity.scope_id} IN ({scopes_ids}) ');
 | 
			
		||||
        $scopes_ids = [];
 | 
			
		||||
 | 
			
		||||
        // first parameter: activity.person_id 
 | 
			
		||||
        $parameters[] = $person->getId();
 | 
			
		||||
 | 
			
		||||
        // loop on reachable scopes 
 | 
			
		||||
        foreach ($reachableScopes as $scope) {
 | 
			
		||||
            if (\in_array($scope->getId(), $scopes_ids)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $scopes_ids[] = '?'; 
 | 
			
		||||
            $parameters[] = $scope->getId();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // we start with activities having the person_id linked to person 
 | 
			
		||||
        // (currently only context "person" is supported)
 | 
			
		||||
        $whereClause = sprintf('%s = %d',
 | 
			
		||||
                 $associationMapping['joinColumns'][0]['name'],
 | 
			
		||||
                 $person->getId());
 | 
			
		||||
        
 | 
			
		||||
        // we add acl (reachable center and scopes)
 | 
			
		||||
        $centerAndScopeLines = array();
 | 
			
		||||
        foreach ($reachableCenters as $center) {
 | 
			
		||||
            $reachablesScopesId = array_map(
 | 
			
		||||
                    function(Scope $scope) { return $scope->getId(); },
 | 
			
		||||
                    $this->helper->getReachableScopes($this->user, $role, 
 | 
			
		||||
                        $person->getCenter())
 | 
			
		||||
                );
 | 
			
		||||
                    
 | 
			
		||||
            $centerAndScopeLines[] = sprintf('(%s = %d AND %s IN (%s))',
 | 
			
		||||
                    $metadataPerson->getTableName().'.'.
 | 
			
		||||
                        $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'],
 | 
			
		||||
                    $center->getId(),
 | 
			
		||||
                    $metadataActivity->getTableName().'.'.
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            \strtr(
 | 
			
		||||
                $whereClause,
 | 
			
		||||
                [
 | 
			
		||||
                    '{activity.person_id}' => $associationMapping['joinColumns'][0]['name'],
 | 
			
		||||
                    '{activity.scope_id}' => $metadataActivity->getTableName().'.'.
 | 
			
		||||
                        $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'],
 | 
			
		||||
                    implode(',', $reachablesScopesId));
 | 
			
		||||
            
 | 
			
		||||
        }
 | 
			
		||||
        $whereClause .= ' AND ('.implode(' OR ', $centerAndScopeLines).')';
 | 
			
		||||
        
 | 
			
		||||
        return $whereClause;
 | 
			
		||||
                    '{scopes_ids}' => \implode(", ", $scopes_ids)
 | 
			
		||||
,
 | 
			
		||||
                ]
 | 
			
		||||
            ),
 | 
			
		||||
            $parameters
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function getFromClause(ClassMetadata $metadataActivity,
 | 
			
		||||
            ClassMetadata $metadataPerson)
 | 
			
		||||
    private function getFromClausePerson()
 | 
			
		||||
    {
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $associationMapping = $metadataActivity->getAssociationMapping('person');
 | 
			
		||||
        
 | 
			
		||||
        return $metadataActivity->getTableName().' JOIN '
 | 
			
		||||
@@ -157,14 +166,14 @@ class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
            .$associationMapping['joinColumns'][0]['name']
 | 
			
		||||
            ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function getEntities(array $ids)
 | 
			
		||||
    {
 | 
			
		||||
        $activities = $this->em->getRepository('ChillActivityBundle:Activity')
 | 
			
		||||
        $activities = $this->em->getRepository(Activity::class)
 | 
			
		||||
              ->findBy(array('id' => $ids));
 | 
			
		||||
        
 | 
			
		||||
        $result = array();
 | 
			
		||||
@@ -183,14 +192,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 +218,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'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,13 +22,12 @@ class ChoiceWithOtherType extends AbstractType
 | 
			
		||||
     */
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        //add an 'other' entry in choices array
 | 
			
		||||
        $options['choices'][$this->otherValueLabel] = '_other';
 | 
			
		||||
        //ChoiceWithOther must always be expanded
 | 
			
		||||
        $options['expanded'] = true;
 | 
			
		||||
        // adding a default value for choice
 | 
			
		||||
        $options['empty_data'] = null;
 | 
			
		||||
        $options['empty_data'] = $options['multiple'] ? [] : null;
 | 
			
		||||
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('_other', TextType::class, array('required' => false))
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ namespace Chill\EventBundle\Timeline;
 | 
			
		||||
use Chill\EventBundle\Entity\Event;
 | 
			
		||||
use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
use Doctrine\ORM\EntityManager;
 | 
			
		||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | 
			
		||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
 | 
			
		||||
@@ -88,13 +89,14 @@ class TimelineEventProvider implements TimelineProviderInterface
 | 
			
		||||
        $metadataParticipation = $this->em->getClassMetadata('ChillEventBundle:Participation');
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person');
 | 
			
		||||
        
 | 
			
		||||
        $query = array(
 | 
			
		||||
        $query = TimelineSingleQuery::fromArray([
 | 
			
		||||
            'id' => $metadataEvent->getTableName().'.'.$metadataEvent->getColumnName('id'),
 | 
			
		||||
            'type' => 'event',
 | 
			
		||||
            'date' => $metadataEvent->getTableName().'.'.$metadataEvent->getColumnName('date'),
 | 
			
		||||
            'FROM' => $this->getFromClause($metadataEvent, $metadataParticipation, $metadataPerson),
 | 
			
		||||
            'WHERE' => $this->getWhereClause($metadataEvent, $metadataParticipation, $metadataPerson, $args['person'])
 | 
			
		||||
        );
 | 
			
		||||
            'WHERE' => $this->getWhereClause($metadataEvent, $metadataParticipation, $metadataPerson, $args['person']),
 | 
			
		||||
            'parameters' => []
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
        return $query;
 | 
			
		||||
    }
 | 
			
		||||
@@ -238,4 +240,4 @@ class TimelineEventProvider implements TimelineProviderInterface
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -47,11 +47,11 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * widget factory
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @var WidgetFactoryInterface[]
 | 
			
		||||
     */
 | 
			
		||||
    protected $widgetFactories = array();
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param WidgetFactoryInterface $factory
 | 
			
		||||
     */
 | 
			
		||||
@@ -59,7 +59,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
    {
 | 
			
		||||
        $this->widgetFactories[] = $factory;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return WidgetFactoryInterface[]
 | 
			
		||||
     */
 | 
			
		||||
@@ -67,7 +67,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
    {
 | 
			
		||||
        return $this->widgetFactories;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     * @param array $configs
 | 
			
		||||
@@ -79,31 +79,31 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
        // configuration for main bundle
 | 
			
		||||
        $configuration = $this->getConfiguration($configs, $container);
 | 
			
		||||
        $config = $this->processConfiguration($configuration, $configs);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main.installation_name',
 | 
			
		||||
            $config['installation_name']);
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main.available_languages',
 | 
			
		||||
            $config['available_languages']);
 | 
			
		||||
        
 | 
			
		||||
        $container->setParameter('chill_main.routing.resources', 
 | 
			
		||||
            $config['routing']['resources']); 
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main.routing.resources',
 | 
			
		||||
            $config['routing']['resources']);
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main.pagination.item_per_page',
 | 
			
		||||
            $config['pagination']['item_per_page']);
 | 
			
		||||
        
 | 
			
		||||
        $container->setParameter('chill_main.notifications', 
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main.notifications',
 | 
			
		||||
            $config['notifications']);
 | 
			
		||||
        
 | 
			
		||||
        $container->setParameter('chill_main.redis', 
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main.redis',
 | 
			
		||||
            $config['redis']);
 | 
			
		||||
        
 | 
			
		||||
        $container->setParameter('chill_main.phone_helper', 
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main.phone_helper',
 | 
			
		||||
            $config['phone_helper'] ?? []);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // add the key 'widget' without the key 'enable'
 | 
			
		||||
        $container->setParameter('chill_main.widgets', 
 | 
			
		||||
            isset($config['widgets']['homepage']) ? 
 | 
			
		||||
        $container->setParameter('chill_main.widgets',
 | 
			
		||||
            isset($config['widgets']['homepage']) ?
 | 
			
		||||
                array('homepage' => $config['widgets']['homepage']):
 | 
			
		||||
                array()
 | 
			
		||||
                );
 | 
			
		||||
@@ -131,10 +131,11 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
        $loader->load('services/templating.yaml');
 | 
			
		||||
        $loader->load('services/timeline.yaml');
 | 
			
		||||
        $loader->load('services/search.yaml');
 | 
			
		||||
        
 | 
			
		||||
        $loader->load('services/serializer.yaml');
 | 
			
		||||
 | 
			
		||||
        $this->configureCruds($container, $config['cruds'], $loader);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param array $config
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
@@ -144,11 +145,11 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
    {
 | 
			
		||||
        return new Configuration($this->widgetFactories, $container);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     */
 | 
			
		||||
    public function prepend(ContainerBuilder $container) 
 | 
			
		||||
    public function prepend(ContainerBuilder $container)
 | 
			
		||||
    {
 | 
			
		||||
        //add installation_name and date_format to globals
 | 
			
		||||
        $chillMainConfig = $container->getExtensionConfig($this->getAlias());
 | 
			
		||||
@@ -163,7 +164,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
            'form_themes' => array('@ChillMain/Form/fields.html.twig')
 | 
			
		||||
        );
 | 
			
		||||
        $container->prependExtensionConfig('twig', $twigConfig);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        //add DQL function to ORM (default entity_manager)
 | 
			
		||||
        $container->prependExtensionConfig('doctrine', array(
 | 
			
		||||
           'orm' => array(
 | 
			
		||||
@@ -182,7 +183,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
              )
 | 
			
		||||
           )
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        //add dbal types (default entity_manager)
 | 
			
		||||
        $container->prependExtensionConfig('doctrine', array(
 | 
			
		||||
           'dbal' => [
 | 
			
		||||
@@ -191,23 +192,23 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
               ]
 | 
			
		||||
           ]
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        //add current route to chill main
 | 
			
		||||
        $container->prependExtensionConfig('chill_main', array(
 | 
			
		||||
           'routing' => array(
 | 
			
		||||
              'resources' => array(
 | 
			
		||||
                 '@ChillMainBundle/config/routes.yaml'
 | 
			
		||||
              )
 | 
			
		||||
              
 | 
			
		||||
 | 
			
		||||
           )
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        //add a channel to log app events
 | 
			
		||||
        $container->prependExtensionConfig('monolog', array(
 | 
			
		||||
            'channels' => array('chill')
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     * @param array $config the config under 'cruds' key
 | 
			
		||||
@@ -218,31 +219,31 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
        if (count($config) === 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $loader->load('services/crud.yaml');
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main_crud_route_loader_config', $config);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $definition = new Definition();
 | 
			
		||||
        $definition
 | 
			
		||||
            ->setClass(\Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader::class)
 | 
			
		||||
            ->addArgument('%chill_main_crud_route_loader_config%')
 | 
			
		||||
            ;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $container->setDefinition('chill_main_crud_route_loader', $definition);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $alreadyExistingNames = [];
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        foreach ($config as $crudEntry) {
 | 
			
		||||
            $controller = $crudEntry['controller'];
 | 
			
		||||
            $controllerServiceName = 'cscrud_'.$crudEntry['name'].'_controller';
 | 
			
		||||
            $name = $crudEntry['name'];
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // check for existing crud names
 | 
			
		||||
            if (\in_array($name, $alreadyExistingNames)) {
 | 
			
		||||
                throw new LogicException(sprintf("the name %s is defined twice in CRUD", $name));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if (!$container->has($controllerServiceName)) {
 | 
			
		||||
                $controllerDefinition = new Definition($controller);
 | 
			
		||||
                $controllerDefinition->addTag('controller.service_arguments');
 | 
			
		||||
@@ -250,7 +251,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
                $controllerDefinition->setClass($crudEntry['controller']);
 | 
			
		||||
                $container->setDefinition($controllerServiceName, $controllerDefinition);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            $container->setParameter('chill_main_crud_config_'.$name, $crudEntry);
 | 
			
		||||
            $container->getDefinition($controllerServiceName)
 | 
			
		||||
                ->addMethodCall('setCrudConfig', ['%chill_main_crud_config_'.$name.'%']);
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer;
 | 
			
		||||
use Doctrine\Persistence\ObjectManager;
 | 
			
		||||
use Chill\MainBundle\Form\Type\Select2ChoiceType;
 | 
			
		||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Extends choice to allow adding select2 library on widget
 | 
			
		||||
@@ -41,15 +42,26 @@ class Select2CountryType extends AbstractType
 | 
			
		||||
     */
 | 
			
		||||
    private $requestStack;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var TranslatableStringHelper
 | 
			
		||||
     */
 | 
			
		||||
    protected $translatableStringHelper;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var ObjectManager
 | 
			
		||||
     */
 | 
			
		||||
    private $em;
 | 
			
		||||
 | 
			
		||||
    public function __construct(RequestStack $requestStack,ObjectManager $em)
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        RequestStack $requestStack,
 | 
			
		||||
        ObjectManager $em,
 | 
			
		||||
        TranslatableStringHelper $translatableStringHelper
 | 
			
		||||
        )
 | 
			
		||||
    {
 | 
			
		||||
        $this->requestStack = $requestStack;
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->translatableStringHelper = $translatableStringHelper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBlockPrefix()
 | 
			
		||||
@@ -75,7 +87,7 @@ class Select2CountryType extends AbstractType
 | 
			
		||||
        $choices = array();
 | 
			
		||||
 | 
			
		||||
        foreach ($countries as $c) {
 | 
			
		||||
            $choices[$c->getId()] = $c->getName()[$locale];
 | 
			
		||||
            $choices[$c->getId()] = $this->translatableStringHelper->localize($c->getName());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        asort($choices, SORT_STRING | SORT_FLAG_CASE);
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Chill\MainBundle\Form\Type\DataTransformer\MultipleObjectsToIdTransformer;
 | 
			
		||||
use Doctrine\Persistence\ObjectManager;
 | 
			
		||||
use Chill\MainBundle\Form\Type\Select2ChoiceType;
 | 
			
		||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Extends choice to allow adding select2 library on widget for languages (multiple)
 | 
			
		||||
@@ -43,10 +44,21 @@ class Select2LanguageType extends AbstractType
 | 
			
		||||
     */
 | 
			
		||||
    private $em;
 | 
			
		||||
 | 
			
		||||
    public function __construct(RequestStack $requestStack,ObjectManager $em)
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var TranslatableStringHelper
 | 
			
		||||
     */
 | 
			
		||||
    protected $translatableStringHelper;
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        RequestStack $requestStack,
 | 
			
		||||
        ObjectManager $em,
 | 
			
		||||
        TranslatableStringHelper $translatableStringHelper
 | 
			
		||||
        )
 | 
			
		||||
    {
 | 
			
		||||
        $this->requestStack = $requestStack;
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->translatableStringHelper = $translatableStringHelper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBlockPrefix()
 | 
			
		||||
@@ -72,7 +84,7 @@ class Select2LanguageType extends AbstractType
 | 
			
		||||
        $choices = array();
 | 
			
		||||
 | 
			
		||||
        foreach ($languages as $l) {
 | 
			
		||||
            $choices[$l->getId()] = $l->getName()[$locale];
 | 
			
		||||
            $choices[$l->getId()] = $this->translatableStringHelper->localize($l->getName());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        asort($choices, SORT_STRING | SORT_FLAG_CASE);
 | 
			
		||||
 
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
 
 | 
			
		||||
@@ -68,6 +68,14 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface
 | 
			
		||||
                'icons' => ['home'],
 | 
			
		||||
                'order' => 0
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        $menu->addChild($this->translator->trans('Global timeline'), [
 | 
			
		||||
            'route' => 'chill_center_timeline',
 | 
			
		||||
        ])
 | 
			
		||||
            ->setExtras([
 | 
			
		||||
                'order' => 10
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($this->authorizationChecker->isGranted(ChillExportVoter::EXPORT)) {
 | 
			
		||||
            $menu->addChild($this->translator->trans('Export Menu'), [
 | 
			
		||||
 
 | 
			
		||||
@@ -204,7 +204,7 @@ class SearchProvider
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function getResultByName($pattern, $name, $start = 0, $limit = 50,
 | 
			
		||||
          array $options = array(), $format) 
 | 
			
		||||
          array $options = array(), $format = 'html') 
 | 
			
		||||
    {
 | 
			
		||||
        $terms = $this->parse($pattern);
 | 
			
		||||
        $search = $this->getByName($name);
 | 
			
		||||
 
 | 
			
		||||
@@ -110,8 +110,6 @@ class AuthorizationHelper
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $role = ($attribute instanceof Role) ? $attribute : new Role($attribute);
 | 
			
		||||
        
 | 
			
		||||
        foreach ($user->getGroupCenters() as $groupCenter){
 | 
			
		||||
            //filter on center
 | 
			
		||||
            if ($groupCenter->getCenter()->getId() === $entity->getCenter()->getId()) {
 | 
			
		||||
@@ -119,8 +117,7 @@ class AuthorizationHelper
 | 
			
		||||
                //iterate on roleScopes
 | 
			
		||||
                foreach($permissionGroup->getRoleScopes() as $roleScope) {
 | 
			
		||||
                    //check that the role allow to reach the required role
 | 
			
		||||
                    if ($this->isRoleReached($role, 
 | 
			
		||||
                          new Role($roleScope->getRole()))){
 | 
			
		||||
                    if ($this->isRoleReached($attribute, $roleScope->getRole())) {
 | 
			
		||||
                        //if yes, we have a right on something...
 | 
			
		||||
                        // perform check on scope if necessary
 | 
			
		||||
                        if ($entity instanceof HasScopeInterface) {
 | 
			
		||||
@@ -149,12 +146,15 @@ class AuthorizationHelper
 | 
			
		||||
     * and optionnaly Scope
 | 
			
		||||
     * 
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param string|Role $role
 | 
			
		||||
     * @param null|Scope $scope
 | 
			
		||||
     * @return Center[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableCenters(User $user, Role $role, Scope $scope = null)
 | 
			
		||||
    public function getReachableCenters(User $user, $role, Scope $scope = null)
 | 
			
		||||
    {
 | 
			
		||||
        if ($role instanceof Role) {
 | 
			
		||||
            $role = $role->getRole();
 | 
			
		||||
        }
 | 
			
		||||
        $centers = array();
 | 
			
		||||
        
 | 
			
		||||
        foreach ($user->getGroupCenters() as $groupCenter){
 | 
			
		||||
@@ -162,8 +162,7 @@ class AuthorizationHelper
 | 
			
		||||
            //iterate on roleScopes
 | 
			
		||||
            foreach($permissionGroup->getRoleScopes() as $roleScope) {
 | 
			
		||||
                //check that the role is in the reachable roles
 | 
			
		||||
                if ($this->isRoleReached($role, 
 | 
			
		||||
                      new Role($roleScope->getRole()))) {
 | 
			
		||||
                if ($this->isRoleReached($role, $roleScope->getRole())) {
 | 
			
		||||
                    if ($scope === null) {
 | 
			
		||||
                        $centers[] = $groupCenter->getCenter();
 | 
			
		||||
                        break 1;
 | 
			
		||||
@@ -180,6 +179,30 @@ class AuthorizationHelper
 | 
			
		||||
        
 | 
			
		||||
        return $centers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Filter an array of centers, return only center which are reachable
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user The user
 | 
			
		||||
     * @param array $centers a list of centers which are going to be filtered
 | 
			
		||||
     * @param string|Center $role
 | 
			
		||||
     */
 | 
			
		||||
    public function filterReachableCenters(User $user, array $centers, $role): array
 | 
			
		||||
    {
 | 
			
		||||
        $results = [];
 | 
			
		||||
 | 
			
		||||
        if ($role instanceof Role) {
 | 
			
		||||
            $role = $role->getRole();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($centers as $center) {
 | 
			
		||||
            if ($this->userCanReachCenter($user, $center, $role)) {
 | 
			
		||||
                $results[] = $center;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $results;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Return all reachable scope for a given user, center and role
 | 
			
		||||
@@ -191,8 +214,12 @@ class AuthorizationHelper
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @return Scope[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableScopes(User $user, Role $role, Center $center)
 | 
			
		||||
    public function getReachableScopes(User $user, $role, Center $center)
 | 
			
		||||
    {
 | 
			
		||||
        if ($role instanceof Role) {
 | 
			
		||||
            $role = $role->getRole();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->getReachableCircles($user, $role, $center);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
@@ -200,12 +227,15 @@ class AuthorizationHelper
 | 
			
		||||
     * Return all reachable circle for a given user, center and role
 | 
			
		||||
     * 
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param string|Role $role
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @return Scope[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableCircles(User $user, Role $role, Center $center)
 | 
			
		||||
    public function getReachableCircles(User $user, $role, Center $center)
 | 
			
		||||
    {
 | 
			
		||||
        if ($role instanceof Role) {
 | 
			
		||||
            $role = $role->getRole();
 | 
			
		||||
        }
 | 
			
		||||
        $scopes = array();
 | 
			
		||||
        
 | 
			
		||||
        foreach ($user->getGroupCenters() as $groupCenter){
 | 
			
		||||
@@ -215,9 +245,7 @@ class AuthorizationHelper
 | 
			
		||||
                //iterate on roleScopes
 | 
			
		||||
                foreach($permissionGroup->getRoleScopes() as $roleScope) {
 | 
			
		||||
                    //check that the role is in the reachable roles
 | 
			
		||||
                    if ($this->isRoleReached($role, 
 | 
			
		||||
                          new Role($roleScope->getRole()))) {
 | 
			
		||||
 | 
			
		||||
                    if ($this->isRoleReached($role, $roleScope->getRole())) {
 | 
			
		||||
                        $scopes[] = $roleScope->getScope();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -269,10 +297,10 @@ class AuthorizationHelper
 | 
			
		||||
     * @param Role $parentRole The role which should give access to $childRole
 | 
			
		||||
     * @return boolean true if the child role is granted by parent role
 | 
			
		||||
     */
 | 
			
		||||
    protected function isRoleReached(Role $childRole, Role $parentRole)
 | 
			
		||||
    protected function isRoleReached($childRole, $parentRole)
 | 
			
		||||
    {
 | 
			
		||||
        $reachableRoles = $this->roleHierarchy
 | 
			
		||||
                ->getReachableRoles([$parentRole]);
 | 
			
		||||
                ->getReachableRoleNames([$parentRole]);
 | 
			
		||||
        
 | 
			
		||||
        return in_array($childRole, $reachableRoles);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS, <http://www.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\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\Center;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class CenterNormalizer implements NormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
    public function normalize($center, string $format = null, array $context = array())
 | 
			
		||||
    {
 | 
			
		||||
        /** @var Center $center */
 | 
			
		||||
        return [
 | 
			
		||||
            'id' => $center->getId(),
 | 
			
		||||
            'name' => $center->getName()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsNormalization($data, string $format = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof Center;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS, <http://www.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\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
 | 
			
		||||
class DateNormalizer implements NormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
    public function normalize($date, string $format = null, array $context = array())
 | 
			
		||||
    {
 | 
			
		||||
        /** @var \DateTimeInterface $date */
 | 
			
		||||
        return [
 | 
			
		||||
            'datetime' => $date->format(\DateTimeInterface::ISO8601),
 | 
			
		||||
            'u' => $date->getTimestamp()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsNormalization($data, string $format = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof \DateTimeInterface;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS, <http://www.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\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class UserNormalizer implements NormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
    public function normalize($user, string $format = null, array $context = array())
 | 
			
		||||
    {
 | 
			
		||||
        /** @var User $user */
 | 
			
		||||
        return [
 | 
			
		||||
            'id' => $user->getId(),
 | 
			
		||||
            'username' => $user->getUsername()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsNormalization($data, string $format = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof User;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,15 +9,7 @@ class LoginControllerTest extends WebTestCase
 | 
			
		||||
{
 | 
			
		||||
    public function testLogin()
 | 
			
		||||
    {
 | 
			
		||||
        $client = static::createClient(array(
 | 
			
		||||
            'framework' => array(
 | 
			
		||||
                'default_locale' => 'en',
 | 
			
		||||
                'translator' => array(
 | 
			
		||||
                    'fallback' => 'en'
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
            
 | 
			
		||||
        ));
 | 
			
		||||
        $client = static::createClient();
 | 
			
		||||
 | 
			
		||||
        //load login page and submit form
 | 
			
		||||
        $crawler = $client->request('GET', '/login');
 | 
			
		||||
@@ -42,17 +34,17 @@ class LoginControllerTest extends WebTestCase
 | 
			
		||||
        //on the home page, there must be a logout link
 | 
			
		||||
        $client->followRedirects(true);
 | 
			
		||||
        $crawler = $client->request('GET', '/');
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
        $this->assertRegExp('/center a_social/', $client->getResponse()
 | 
			
		||||
                                    ->getContent());
 | 
			
		||||
        $logoutLinkFilter = $crawler->filter('a:contains("Logout")');
 | 
			
		||||
        $logoutLinkFilter = $crawler->filter('a:contains("Se déconnecter")');
 | 
			
		||||
        
 | 
			
		||||
        //check there is > 0 logout link
 | 
			
		||||
        $this->assertGreaterThan(0, $logoutLinkFilter->count(), 'check that a logout link is present');
 | 
			
		||||
        
 | 
			
		||||
        //click on logout link
 | 
			
		||||
        $client->followRedirects(false);
 | 
			
		||||
        $client->click($crawler->selectLink('Logout')->link());
 | 
			
		||||
        $client->click($crawler->selectLink('Se déconnecter')->link());
 | 
			
		||||
        
 | 
			
		||||
        $this->assertTrue($client->getResponse()->isRedirect());
 | 
			
		||||
        $client->followRedirect(); #redirect to login page
 | 
			
		||||
 
 | 
			
		||||
@@ -32,21 +32,7 @@ use Chill\MainBundle\Search\SearchInterface;
 | 
			
		||||
 */
 | 
			
		||||
class SearchControllerTest extends WebTestCase
 | 
			
		||||
{
 | 
			
		||||
    /*
 | 
			
		||||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        static::bootKernel();
 | 
			
		||||
        
 | 
			
		||||
        //add a default service
 | 
			
		||||
        $this->addSearchService(
 | 
			
		||||
              $this->createDefaultSearchService('<p>I am default</p>', 10), 'default'
 | 
			
		||||
              );
 | 
			
		||||
        //add a domain service
 | 
			
		||||
        $this->addSearchService(
 | 
			
		||||
              $this->createDefaultSearchService('<p>I am domain bar</p>', 20), 'bar'
 | 
			
		||||
              );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
   
 | 
			
		||||
    /**
 | 
			
		||||
     * Test the behaviour when no domain is provided in the search pattern : 
 | 
			
		||||
     * the default search should be enabled
 | 
			
		||||
@@ -105,29 +91,6 @@ class SearchControllerTest extends WebTestCase
 | 
			
		||||
        $this->assertTrue($client->getResponse()->isNotFound());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    public function testSearchWithinSpecificSearchName()
 | 
			
		||||
    {
 | 
			
		||||
        /*
 | 
			
		||||
        //add a search service which will be supported
 | 
			
		||||
        $this->addSearchService(
 | 
			
		||||
              $this->createNonDefaultDomainSearchService("<p>I am domain foo</p>", 100, TRUE), 'foo'
 | 
			
		||||
              );
 | 
			
		||||
        
 | 
			
		||||
        $client = $this->getAuthenticatedClient();
 | 
			
		||||
        $crawler = $client->request('GET', '/fr/search', 
 | 
			
		||||
              array('q' => '@foo default search', 'name' => 'foo'));
 | 
			
		||||
 | 
			
		||||
        //$this->markTestSkipped();
 | 
			
		||||
        $this->assertEquals(0, $crawler->filter('p:contains("I am default")')->count(), 
 | 
			
		||||
              "The mocked default results are not shown");
 | 
			
		||||
        $this->assertEquals(0, $crawler->filter('p:contains("I am domain bar")')->count(),
 | 
			
		||||
              "The mocked non-default results are not shown");
 | 
			
		||||
        $this->assertEquals(1, $crawler->filter('p:contains("I am domain foo")')->count(), 
 | 
			
		||||
              "The mocked nnon default results for foo are shown");
 | 
			
		||||
        */
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function getAuthenticatedClient()
 | 
			
		||||
    {
 | 
			
		||||
        return static::createClient(array(), array(
 | 
			
		||||
 
 | 
			
		||||
@@ -37,11 +37,12 @@ class UserControllerTest extends WebTestCase
 | 
			
		||||
        
 | 
			
		||||
        $username = 'Test_user'.  uniqid();
 | 
			
		||||
        $password = 'Password1234!';
 | 
			
		||||
        dump($crawler->text());
 | 
			
		||||
        // Fill in the form and submit it
 | 
			
		||||
        $form = $crawler->selectButton('Créer')->form(array(
 | 
			
		||||
            'chill_mainbundle_user[username]'  => $username,
 | 
			
		||||
            'chill_mainbundle_user[plainPassword][password][first]' => $password,
 | 
			
		||||
            'chill_mainbundle_user[plainPassword][password][second]' => $password
 | 
			
		||||
            'chill_mainbundle_user[plainPassword][first]' => $password,
 | 
			
		||||
            'chill_mainbundle_user[plainPassword][second]' => $password
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        $this->client->submit($form);
 | 
			
		||||
@@ -119,8 +120,8 @@ class UserControllerTest extends WebTestCase
 | 
			
		||||
        $crawler = $this->client->click($link);
 | 
			
		||||
        
 | 
			
		||||
        $form = $crawler->selectButton('Changer le mot de passe')->form(array(
 | 
			
		||||
            'chill_mainbundle_user_password[password][first]' => $newPassword,
 | 
			
		||||
            'chill_mainbundle_user_password[password][second]' => $newPassword,
 | 
			
		||||
            'chill_mainbundle_user_password[new_password][first]' => $newPassword,
 | 
			
		||||
            'chill_mainbundle_user_password[new_password][second]' => $newPassword,
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $this->client->submit($form);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,179 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 * 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\Tests\DependencyInjection;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\ConfigConsistencyCompilerPass;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerBuilderInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description of ConfigConsistencyCompilerPassTest
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class ConfigConsistencyCompilerPassTest extends \PHPUnit\Framework\TestCase
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @var \Chill\MainBundle\DependencyInjection\ConfigConsistencyCompilerPass 
 | 
			
		||||
     */
 | 
			
		||||
    private $configConsistencyCompilerPass;
 | 
			
		||||
    
 | 
			
		||||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        $this->configConsistencyCompilerPass = new ConfigConsistencyCompilerPass();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Test that everything is fine is configuration is correct
 | 
			
		||||
     * 
 | 
			
		||||
     */
 | 
			
		||||
    public function testLanguagesArePresent()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this ->configConsistencyCompilerPass
 | 
			
		||||
                  ->process(
 | 
			
		||||
                        $this->mockContainer(
 | 
			
		||||
                              $this->mockTranslatorDefinition(array('fr')),
 | 
			
		||||
                              array('fr', 'nl')
 | 
			
		||||
                        )
 | 
			
		||||
                    );
 | 
			
		||||
            $this->assertTrue(TRUE, 'the config consistency can process');
 | 
			
		||||
        } catch (\Exception $ex) {
 | 
			
		||||
            $this->assertTrue(FALSE, 
 | 
			
		||||
                  'the config consistency can process');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Test that everything is fine is configuration is correct
 | 
			
		||||
     * if multiple fallback languages are present
 | 
			
		||||
     * 
 | 
			
		||||
     */
 | 
			
		||||
    public function testMultiplesLanguagesArePresent()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this ->configConsistencyCompilerPass
 | 
			
		||||
                  ->process(
 | 
			
		||||
                        $this->mockContainer(
 | 
			
		||||
                              $this->mockTranslatorDefinition(array('fr', 'nl')),
 | 
			
		||||
                              array('fr', 'nl', 'en')
 | 
			
		||||
                        )
 | 
			
		||||
                    );
 | 
			
		||||
            $this->assertTrue(TRUE, 'the config consistency can process');
 | 
			
		||||
        } catch (\Exception $ex) {
 | 
			
		||||
            $this->assertTrue(FALSE, 
 | 
			
		||||
                  'the config consistency can process');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Test that a runtime exception is throw if the available language does 
 | 
			
		||||
     * not contains the fallback locale
 | 
			
		||||
     * 
 | 
			
		||||
     * @expectedException \RuntimeException
 | 
			
		||||
     * @expectedExceptionMessageRegExp /The chill_main.available_languages parameter does not contains fallback locales./
 | 
			
		||||
     */
 | 
			
		||||
    public function testLanguageNotPresent()
 | 
			
		||||
    {
 | 
			
		||||
        $container = $this->mockContainer(
 | 
			
		||||
              $this->mockTranslatorDefinition(array('en')), array('fr')
 | 
			
		||||
              );
 | 
			
		||||
        
 | 
			
		||||
        $this->configConsistencyCompilerPass->process($container);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Test that a logic exception is thrown if the setFallbackLocale
 | 
			
		||||
     * method is not defined in translator definition
 | 
			
		||||
     * 
 | 
			
		||||
     * @expectedException \LogicException
 | 
			
		||||
     */
 | 
			
		||||
    public function testSetFallbackNotDefined()
 | 
			
		||||
    {
 | 
			
		||||
        $container = $this->mockContainer(
 | 
			
		||||
              $this->mockTranslatorDefinition(NULL), array('fr')
 | 
			
		||||
              );
 | 
			
		||||
        $this->configConsistencyCompilerPass->process($container);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @return ContainerBuilder
 | 
			
		||||
     */
 | 
			
		||||
    private function mockContainer($definition, $availableLanguages)
 | 
			
		||||
    {
 | 
			
		||||
        $container = $this
 | 
			
		||||
              ->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')
 | 
			
		||||
              ->getMock();
 | 
			
		||||
        
 | 
			
		||||
        $container->method('getParameter')
 | 
			
		||||
              ->will($this->returnCallback(
 | 
			
		||||
                        function($parameter) use ($availableLanguages) {
 | 
			
		||||
                            if ($parameter === 'chill_main.available_languages') {
 | 
			
		||||
                                return $availableLanguages;
 | 
			
		||||
                            } else {
 | 
			
		||||
                                throw new \LogicException("the parameter '$parameter' "
 | 
			
		||||
                                      . "is not defined in stub test");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    ));
 | 
			
		||||
        
 | 
			
		||||
        $container->method('findDefinition')
 | 
			
		||||
              ->will($this->returnCallback(
 | 
			
		||||
                    function($id) use ($definition) { 
 | 
			
		||||
                        if (in_array($id, array('translator', 'translator.default'))) {
 | 
			
		||||
                            return $definition;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            throw new \LogicException("the id $id is not defined in test");
 | 
			
		||||
                        }
 | 
			
		||||
                  }));
 | 
			
		||||
                  
 | 
			
		||||
        
 | 
			
		||||
        return $container;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @param type $languages
 | 
			
		||||
     * @return 'Symfony\Component\DependencyInjection\Definition'
 | 
			
		||||
     */
 | 
			
		||||
    private function mockTranslatorDefinition(array $languages = NULL)
 | 
			
		||||
    {
 | 
			
		||||
        $definition = $this
 | 
			
		||||
              ->getMockBuilder('Symfony\Component\DependencyInjection\Definition')
 | 
			
		||||
              ->getMock();
 | 
			
		||||
        
 | 
			
		||||
        if (NULL !== $languages) {
 | 
			
		||||
            $definition->method('getMethodCalls')
 | 
			
		||||
                  ->willReturn(array(
 | 
			
		||||
                    ['setFallbackLocales', array($languages)]
 | 
			
		||||
                     ));
 | 
			
		||||
        } else {
 | 
			
		||||
            $definition->method('getMethodCalls')
 | 
			
		||||
                  ->willReturn(array(['nothing', array()]));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $definition;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -624,8 +624,9 @@ class ExportManagerTest extends KernelTestCase
 | 
			
		||||
        $exportManager->addFormatter($formatter, 'spreadsheet');
 | 
			
		||||
        
 | 
			
		||||
        //ob_start();
 | 
			
		||||
        $response = $exportManager->generate('dummy', 
 | 
			
		||||
                array(PickCenterType::CENTERS_IDENTIFIERS => array($center)), 
 | 
			
		||||
        $response = $exportManager->generate(
 | 
			
		||||
                'dummy', 
 | 
			
		||||
                array($center), 
 | 
			
		||||
                array(
 | 
			
		||||
                    ExportType::FILTER_KEY => array(
 | 
			
		||||
                        'filter_foo' => array(
 | 
			
		||||
 
 | 
			
		||||
@@ -54,13 +54,20 @@ class PageTest extends KernelTestCase
 | 
			
		||||
          $number = 1, 
 | 
			
		||||
          $itemPerPage = 10,
 | 
			
		||||
          $route = 'route',
 | 
			
		||||
          array $routeParameters = array()
 | 
			
		||||
          array $routeParameters = array(),
 | 
			
		||||
          $totalItems = 100
 | 
			
		||||
    ) {
 | 
			
		||||
        $urlGenerator = $this->prophet->prophesize();
 | 
			
		||||
        $urlGenerator->willImplement(UrlGeneratorInterface::class);
 | 
			
		||||
        
 | 
			
		||||
        return new Page($number, $itemPerPage, $urlGenerator->reveal(), $route, 
 | 
			
		||||
              $routeParameters);
 | 
			
		||||
        return new Page(
 | 
			
		||||
            $number, 
 | 
			
		||||
            $itemPerPage, 
 | 
			
		||||
            $urlGenerator->reveal(), 
 | 
			
		||||
            $route, 
 | 
			
		||||
            $routeParameters,
 | 
			
		||||
            $totalItems
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function testPageNumber() {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,9 +21,10 @@ namespace Chill\MainBundle\Test\Search;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Search\SearchProvider;
 | 
			
		||||
use Chill\MainBundle\Search\SearchInterface;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SearchProviderTest extends \PHPUnit\Framework\TestCase
 | 
			
		||||
class SearchProviderTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
@@ -311,4 +312,4 @@ class SearchProviderTest extends \PHPUnit\Framework\TestCase
 | 
			
		||||
        
 | 
			
		||||
        return $mock;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ class AuthorizationHelperTest extends KernelTestCase
 | 
			
		||||
     */
 | 
			
		||||
    private function getAuthorizationHelper()
 | 
			
		||||
    {
 | 
			
		||||
        return static::$kernel->getContainer()
 | 
			
		||||
        return static::$container
 | 
			
		||||
                ->get('chill.main.security.authorization.helper')
 | 
			
		||||
                ;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -39,8 +39,7 @@ class TokenManagerTest extends KernelTestCase
 | 
			
		||||
    {
 | 
			
		||||
        self::bootKernel();
 | 
			
		||||
        
 | 
			
		||||
        $logger = self::$kernel
 | 
			
		||||
            ->getContainer()
 | 
			
		||||
        $logger = self::$container
 | 
			
		||||
            ->get('logger');
 | 
			
		||||
        
 | 
			
		||||
        $this->tokenManager = new TokenManager('secret', $logger);
 | 
			
		||||
 
 | 
			
		||||
@@ -36,53 +36,19 @@ class ChillMenuTwigFunctionTest extends KernelTestCase
 | 
			
		||||
    public static function setUpBeforeClass()
 | 
			
		||||
    {
 | 
			
		||||
        self::bootKernel(array('environment' => 'test'));
 | 
			
		||||
        static::$templating = static::$kernel
 | 
			
		||||
                ->getContainer()->get('templating');
 | 
			
		||||
        static::$templating = static::$container
 | 
			
		||||
            ->get('templating');
 | 
			
		||||
        $pathToBundle = static::$container->getParameter('kernel.bundles_metadata')['ChillMainBundle']['path'];
 | 
			
		||||
        //load templates in Tests/Resources/views
 | 
			
		||||
        static::$kernel->getContainer()->get('twig.loader')
 | 
			
		||||
                ->addPath(static::$kernel->getContainer()->getParameter('kernel.root_dir')
 | 
			
		||||
                    .'/Resources/views/', $namespace = 'tests');
 | 
			
		||||
        static::$container->get('twig.loader')
 | 
			
		||||
            ->addPath($pathToBundle.'/Resources/test/views/', $namespace = 'tests');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function testNormalMenu()
 | 
			
		||||
    {
 | 
			
		||||
        $content = static::$templating->render('@tests/menus/normalMenu.html.twig');
 | 
			
		||||
        $crawler = new Crawler($content);
 | 
			
		||||
        
 | 
			
		||||
        $ul = $crawler->filter('ul')->getNode(0);
 | 
			
		||||
        $this->assertEquals( 'ul', $ul->tagName);
 | 
			
		||||
        
 | 
			
		||||
        $lis = $crawler->filter('ul')->children();
 | 
			
		||||
        $this->assertEquals(3, count($lis));
 | 
			
		||||
        
 | 
			
		||||
        $lis->each(function(Crawler $node, $i) {
 | 
			
		||||
                $this->assertEquals('li', $node->getNode(0)->tagName);
 | 
			
		||||
                
 | 
			
		||||
                $a = $node->children()->getNode(0);
 | 
			
		||||
                $this->assertEquals('a', $a->tagName);
 | 
			
		||||
                switch($i) {
 | 
			
		||||
                    case 0: 
 | 
			
		||||
                        $this->assertEquals('/dummy?param=fake', $a->getAttribute('href'));
 | 
			
		||||
                        $this->assertEquals('active', $a->getAttribute('class'));
 | 
			
		||||
                        $this->assertEquals('test0', $a->nodeValue);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 1:
 | 
			
		||||
                        $this->assertEquals('/dummy1?param=fake', $a->getAttribute('href'));
 | 
			
		||||
                        $this->assertEmpty($a->getAttribute('class'));
 | 
			
		||||
                        $this->assertEquals('test1', $a->nodeValue);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 3:
 | 
			
		||||
                        $this->assertEquals('/dummy2/fake', $a->getAttribute('href'));
 | 
			
		||||
                        $this->assertEmpty($a->getAttribute('class'));
 | 
			
		||||
                        $this->assertEquals('test2', $a->nodeValue);
 | 
			
		||||
                }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function testMenuOverrideTemplate()
 | 
			
		||||
    {
 | 
			
		||||
        $this->markTestSkipped("this hacks seems not working now");
 | 
			
		||||
        $content = static::$templating->render('@tests/menus/overrideTemplate.html.twig');
 | 
			
		||||
        $this->assertEquals('fake template', $content);
 | 
			
		||||
        $this->assertContains('ul', $content, 
 | 
			
		||||
            "test that the file contains an ul tag"
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ class MenuComposerTest extends KernelTestCase
 | 
			
		||||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        self::bootKernel(array('environment' => 'test'));
 | 
			
		||||
        $this->menuComposer = static::$kernel->getContainer()
 | 
			
		||||
        $this->menuComposer = static::$container
 | 
			
		||||
                ->get('chill.main.menu_composer');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
@@ -42,50 +42,5 @@ class MenuComposerTest extends KernelTestCase
 | 
			
		||||
        $routes = $this->menuComposer->getRoutesFor('dummy0');
 | 
			
		||||
        
 | 
			
		||||
        $this->assertInternalType('array', $routes);
 | 
			
		||||
        $this->assertCount(3, $routes);
 | 
			
		||||
        //check that the keys are sorted
 | 
			
		||||
        $orders = array_keys($routes);
 | 
			
		||||
        foreach ($orders as $key => $order){
 | 
			
		||||
            if (array_key_exists($key + 1, $orders)) {
 | 
			
		||||
                $this->assertGreaterThan($order, $orders[$key + 1],
 | 
			
		||||
                        'Failing to assert that routes are ordered');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        //check that the array are identical, order is not important :
 | 
			
		||||
        
 | 
			
		||||
        $expected = array(
 | 
			
		||||
            50 => array(
 | 
			
		||||
                'key'   => 'chill_main_dummy_0',
 | 
			
		||||
                'label' => 'test0',
 | 
			
		||||
                'otherkey' => 'othervalue'
 | 
			
		||||
                ),
 | 
			
		||||
            51 => array(
 | 
			
		||||
                'key'   => 'chill_main_dummy_1',
 | 
			
		||||
                'label' => 'test1',
 | 
			
		||||
                'helper'=> 'great helper'
 | 
			
		||||
            ),
 | 
			
		||||
            52 => array(
 | 
			
		||||
                'key'   => 'chill_main_dummy_2',
 | 
			
		||||
                'label' => 'test2'
 | 
			
		||||
            ));
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        foreach ($expected as $order => $route ){
 | 
			
		||||
            
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        //compare arrays
 | 
			
		||||
        foreach($expected as $order => $route) {
 | 
			
		||||
            //check the key are the one expected
 | 
			
		||||
            $this->assertTrue(isset($routes[$order]));
 | 
			
		||||
            
 | 
			
		||||
            if (isset($routes[$order])){ #avoid an exception if routes with order does not exists
 | 
			
		||||
                //sort arrays. Order matters for phpunit::assertSame
 | 
			
		||||
                ksort($route);
 | 
			
		||||
                ksort($routes[$order]);
 | 
			
		||||
                $this->assertSame($route, $routes[$order]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,11 +23,11 @@ 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
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class TimelineBuilder implements ContainerAwareInterface
 | 
			
		||||
{
 | 
			
		||||
@@ -78,14 +78,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 +100,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 +156,59 @@ 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
 | 
			
		||||
     * @return array: first parameter is the sql string, second an array with parameters
 | 
			
		||||
     */
 | 
			
		||||
    private function buildSelectQuery(TimelineProviderInterface $provider, $context, array $args)
 | 
			
		||||
    private function buildSelectQuery($data): array
 | 
			
		||||
    {
 | 
			
		||||
        $data = $provider->fetchQuery($context, $args);
 | 
			
		||||
        
 | 
			
		||||
        return sprintf(
 | 
			
		||||
        return [$data->buildSql(), $data->getParameters()];
 | 
			
		||||
 | 
			
		||||
        // dead code
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
 | 
			
		||||
        $sql = sprintf(
 | 
			
		||||
                'SELECT %s AS id, '
 | 
			
		||||
                . '%s AS "date", '
 | 
			
		||||
                . "'%s' AS type "
 | 
			
		||||
@@ -197,16 +218,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 +238,8 @@ class TimelineBuilder implements ContainerAwareInterface
 | 
			
		||||
                ->addScalarResult('date', 'date');
 | 
			
		||||
        
 | 
			
		||||
        return $this->em->createNativeQuery($query, $resultSetMapping)
 | 
			
		||||
                ->getArrayResult();
 | 
			
		||||
            ->setParameters($parameters)
 | 
			
		||||
            ->getArrayResult();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
@@ -274,7 +299,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
 | 
			
		||||
                ));
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										155
									
								
								src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Timeline;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TimelineSingleQuery
 | 
			
		||||
{
 | 
			
		||||
    private ?string $id;
 | 
			
		||||
 | 
			
		||||
    private ?string $date;
 | 
			
		||||
 | 
			
		||||
    private ?string $key;
 | 
			
		||||
 | 
			
		||||
    private ?string $from;
 | 
			
		||||
 | 
			
		||||
    private ?string $where;
 | 
			
		||||
 | 
			
		||||
    private array $parameters = [];
 | 
			
		||||
 | 
			
		||||
    private bool $distinct = false;
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        string $id = null,
 | 
			
		||||
        string $date = null,
 | 
			
		||||
        string $key = null,
 | 
			
		||||
        string $from = null,
 | 
			
		||||
        string $where = null,
 | 
			
		||||
        array $parameters = []
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->id = $id;
 | 
			
		||||
        $this->date = $date;
 | 
			
		||||
        $this->key = $key;
 | 
			
		||||
        $this->from = $from;
 | 
			
		||||
        $this->where = $where;
 | 
			
		||||
        $this->parameters = $parameters;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function fromArray(array $a)
 | 
			
		||||
    {
 | 
			
		||||
        return new TimelineSingleQuery(
 | 
			
		||||
            $a['id'] ?? null,
 | 
			
		||||
            $a['date'] ?? null,
 | 
			
		||||
            $a['type'] ?? $a['key'] ?? null,
 | 
			
		||||
            $a['FROM'] ?? $a['from'] ?? null,
 | 
			
		||||
            $a['WHERE'] ?? $a['where'] ?? null,
 | 
			
		||||
            $a['parameters'] ?? null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getId(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function setId(string $id): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->id = $id;
 | 
			
		||||
    
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDate(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->date;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function setDate(string $date): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->date = $date;
 | 
			
		||||
    
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getKey(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->key;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function setKey(string $key): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->key = $key;
 | 
			
		||||
    
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFrom(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->from;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function setFrom(string $from): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->from = $from;
 | 
			
		||||
    
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getWhere(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->where;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function setWhere(string $where): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->where = $where;
 | 
			
		||||
    
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getParameters(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->parameters;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function setParameters(array $parameters): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->parameters = $parameters;
 | 
			
		||||
    
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setDistinct(bool $distinct): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->distinct = $distinct;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isDistinct(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->distinct;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildSql(): string
 | 
			
		||||
    {
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
 | 
			
		||||
        $sql = \strtr(
 | 
			
		||||
                'SELECT {distinct} {id} AS id, '
 | 
			
		||||
                . '{date} AS "date", '
 | 
			
		||||
                . "'{key}' AS type "
 | 
			
		||||
                . 'FROM {from} '
 | 
			
		||||
                . 'WHERE {where}',
 | 
			
		||||
                [
 | 
			
		||||
                    '{distinct}' => $this->distinct ? 'DISTINCT' : '',
 | 
			
		||||
                    '{id}' => $this->getId(),
 | 
			
		||||
                    '{date}' => $this->getDate(),
 | 
			
		||||
                    '{key}' => $this->getKey(),
 | 
			
		||||
                    '{from}' => $this->getFrom(),
 | 
			
		||||
                    '{where}' => $this->getWhere(),
 | 
			
		||||
                ]
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        return $sql;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -86,7 +90,3 @@ login_check:
 | 
			
		||||
 | 
			
		||||
logout:
 | 
			
		||||
    path:    /logout
 | 
			
		||||
 | 
			
		||||
chill_main_test:
 | 
			
		||||
    path: /{_locale}/main/test
 | 
			
		||||
    controller: Chill\MainBundle\Controller\DefaultController::testAction
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ services:
 | 
			
		||||
        arguments:
 | 
			
		||||
            - "@request_stack"
 | 
			
		||||
            - "@doctrine.orm.entity_manager"
 | 
			
		||||
            - "@chill.main.helper.translatable_string"
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type, alias: select2_chill_country }
 | 
			
		||||
 | 
			
		||||
@@ -31,6 +32,7 @@ services:
 | 
			
		||||
        arguments:
 | 
			
		||||
            - "@request_stack"
 | 
			
		||||
            - "@doctrine.orm.entity_manager"
 | 
			
		||||
            - "@chill.main.helper.translatable_string"
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type, alias: select2_chill_language }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								src/Bundle/ChillMainBundle/config/services/serializer.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Bundle/ChillMainBundle/config/services/serializer.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
---
 | 
			
		||||
services:
 | 
			
		||||
    Chill\MainBundle\Serializer\Normalizer\CenterNormalizer:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: 'serializer.normalizer', priority: 64 }
 | 
			
		||||
 | 
			
		||||
    Chill\MainBundle\Serializer\Normalizer\DateNormalizer:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: 'serializer.normalizer', priority: 64 }
 | 
			
		||||
 | 
			
		||||
    Chill\MainBundle\Serializer\Normalizer\UserNormalizer:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: 'serializer.normalizer', priority: 64 }
 | 
			
		||||
@@ -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'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,11 @@ Back to the list: Retour à la liste
 | 
			
		||||
#interval
 | 
			
		||||
Years: Années
 | 
			
		||||
 | 
			
		||||
# misc date
 | 
			
		||||
Since %date%: Depuis le %date%
 | 
			
		||||
since %date%: depuis le %date%
 | 
			
		||||
Until %date%: Jusqu'au %date%
 | 
			
		||||
until %date%: jusqu'au %date%
 | 
			
		||||
#elements used in software
 | 
			
		||||
centers: centres
 | 
			
		||||
Centers: Centres
 | 
			
		||||
@@ -78,6 +83,9 @@ Results %start%-%end% of %total%: Résultats %start%-%end% sur %total%
 | 
			
		||||
See all results: Voir tous les résultats
 | 
			
		||||
Advanced search: Recherche avancée
 | 
			
		||||
 | 
			
		||||
# timeline
 | 
			
		||||
Global timeline: Historique global
 | 
			
		||||
 | 
			
		||||
#admin
 | 
			
		||||
Create: Créer
 | 
			
		||||
show: voir
 | 
			
		||||
 
 | 
			
		||||
@@ -4,17 +4,21 @@ namespace Chill\PersonBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
 | 
			
		||||
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 | 
			
		||||
use Symfony\Component\HttpFoundation\JsonResponse;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
 | 
			
		||||
use Symfony\Component\Routing\Annotation\Route;
 | 
			
		||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
 | 
			
		||||
use Symfony\Component\Serializer\Serializer;
 | 
			
		||||
use Symfony\Component\Serializer\SerializerInterface;
 | 
			
		||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class AccompanyingCourseController
 | 
			
		||||
@@ -23,6 +27,21 @@ use Symfony\Component\Serializer\SerializerInterface;
 | 
			
		||||
 */
 | 
			
		||||
class AccompanyingCourseController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    protected SerializerInterface $serializer;
 | 
			
		||||
 | 
			
		||||
    protected EventDispatcherInterface $dispatcher;
 | 
			
		||||
 | 
			
		||||
    protected ValidatorInterface $validator;
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        SerializerInterface $serializer,
 | 
			
		||||
        EventDispatcherInterface $dispatcher,
 | 
			
		||||
        ValidatorInterface $validator
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->serializer = $serializer;
 | 
			
		||||
        $this->dispatcher = $dispatcher;
 | 
			
		||||
        $this->validator = $validator;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Homepage of Accompanying Course section
 | 
			
		||||
     *
 | 
			
		||||
@@ -68,45 +87,75 @@ class AccompanyingCourseController extends Controller
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sérialise temporairement quelques données pour donner à manger au composant vuejs
 | 
			
		||||
     * Get API Data for showing endpoint
 | 
			
		||||
     *
 | 
			
		||||
     * @Route(
 | 
			
		||||
     *     "/{_locale}/api/parcours/{accompanying_period_id}/show",
 | 
			
		||||
     *     name="chill_person_accompanying_course_api_show")
 | 
			
		||||
     *     "/{_locale}/person/api/1.0/accompanying-course/{accompanying_period_id}/show.{_format}",
 | 
			
		||||
     *     name="chill_person_accompanying_course_api_show"
 | 
			
		||||
     * )
 | 
			
		||||
     * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"})
 | 
			
		||||
     * @param SerializerInterface $serializer
 | 
			
		||||
     */
 | 
			
		||||
    public function showAPI(AccompanyingPeriod $accompanyingCourse): Response
 | 
			
		||||
    public function showAPI(AccompanyingPeriod $accompanyingCourse, $_format): Response
 | 
			
		||||
    {
 | 
			
		||||
        $persons = [];
 | 
			
		||||
        foreach ($accompanyingCourse->getParticipations() as $k => $participation ) {
 | 
			
		||||
            /**
 | 
			
		||||
             * @var AccompanyingPeriodParticipation $participation
 | 
			
		||||
             * @var Person $person
 | 
			
		||||
             */
 | 
			
		||||
            $person = $participation->getPerson();
 | 
			
		||||
            $persons[$k] = [
 | 
			
		||||
                'id' => $person->getId(),
 | 
			
		||||
                'firstname' => $person->getFirstName(),
 | 
			
		||||
                'lastname' => $person->getLastName(),
 | 
			
		||||
                'email' => $person->getEmail(),
 | 
			
		||||
                'phone' => $person->getPhonenumber(),
 | 
			
		||||
                'startdate' => ($participation->getStartDate()) ? $participation->getStartDate()->format('Y-m-d') : null,
 | 
			
		||||
                'enddate' => ($participation->getEndDate()) ? $participation->getEndDate()->format('Y-m-d') : null
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
        $data = [
 | 
			
		||||
            'id' => $accompanyingCourse->getId(),
 | 
			
		||||
            'remark' => $accompanyingCourse->getRemark(),
 | 
			
		||||
            'closing_motive' => $accompanyingCourse->getClosingMotive() ? $accompanyingCourse->getClosingMotive()->getName()['fr'] : null,
 | 
			
		||||
            'opening_date' => ($accompanyingCourse->getOpeningDate()) ? $accompanyingCourse->getOpeningDate()->format('Y-m-d') : null,
 | 
			
		||||
            'closing_date' => ($accompanyingCourse->getClosingDate()) ? $accompanyingCourse->getClosingDate()->format('Y-m-d') : null,
 | 
			
		||||
            'persons' => $persons
 | 
			
		||||
        ];
 | 
			
		||||
        // TODO check ACL on AccompanyingPeriod
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(
 | 
			
		||||
            AccompanyingPeriodPrivacyEvent::ACCOMPANYING_PERIOD_PRIVACY_EVENT,
 | 
			
		||||
            new AccompanyingPeriodPrivacyEvent($accompanyingCourse, [
 | 
			
		||||
            'action' => 'showApi'
 | 
			
		||||
            ])
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        switch ($_format) {
 | 
			
		||||
            case 'json':
 | 
			
		||||
                return $this->json($accompanyingCourse);
 | 
			
		||||
            default:
 | 
			
		||||
                throw new BadRequestException('Unsupported format');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $serialized = \json_encode($data);
 | 
			
		||||
        $response = new Response($serialized);
 | 
			
		||||
        $response->headers->set('Content-Type', 'application/json');
 | 
			
		||||
        return $response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get API Data for showing endpoint
 | 
			
		||||
     *
 | 
			
		||||
     * @Route(
 | 
			
		||||
     *     "/{_locale}/person/api/1.0/accompanying-course/{accompanying_period_id}/participation.{_format}",
 | 
			
		||||
     *     name="chill_person_accompanying_course_api_add_participation",
 | 
			
		||||
     *     methods={"POST"},
 | 
			
		||||
     *     format="json",
 | 
			
		||||
     *     requirements={
 | 
			
		||||
     *         "_format": "json",
 | 
			
		||||
     *     }
 | 
			
		||||
     * )
 | 
			
		||||
     * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"})
 | 
			
		||||
     */
 | 
			
		||||
    public function addParticipationAPI(Request $request, AccompanyingPeriod $accompanyingCourse, $_format): Response
 | 
			
		||||
    {
 | 
			
		||||
        switch ($_format) {
 | 
			
		||||
            case 'json':
 | 
			
		||||
                $person = $this->serializer->deserialize($request->getContent(), Person::class, $_format, [
 | 
			
		||||
 | 
			
		||||
                ]);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new BadRequestException('Unsupported format');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (NULL === $person) {
 | 
			
		||||
            throw new BadRequestException('person id not found');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO add acl
 | 
			
		||||
        $accompanyingCourse->addPerson($person);
 | 
			
		||||
        $errors = $this->validator->validate($accompanyingCourse);
 | 
			
		||||
 | 
			
		||||
        if ($errors->count() > 0) {
 | 
			
		||||
            // only format accepted
 | 
			
		||||
            return $this->json($errors);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->getDoctrine()->getManager()->flush();
 | 
			
		||||
 | 
			
		||||
        return new JsonResponse();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015-2021 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\PersonBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 | 
			
		||||
use Symfony\Component\HttpFoundation\JsonResponse;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ApiPersonController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    public function viewAction($id, $_format) 
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -41,21 +41,14 @@ use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
 | 
			
		||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class PersonController
 | 
			
		||||
 *
 | 
			
		||||
 * @package Chill\PersonBundle\Controller
 | 
			
		||||
 */
 | 
			
		||||
class PersonController extends AbstractController
 | 
			
		||||
final class PersonController extends AbstractController
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var SimilarPersonMatcher
 | 
			
		||||
     */
 | 
			
		||||
    protected $similarPersonMatcher;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var TranslatorInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $translator;
 | 
			
		||||
@@ -66,34 +59,31 @@ class PersonController extends AbstractController
 | 
			
		||||
    protected $eventDispatcher;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var PersonRepository;
 | 
			
		||||
     */
 | 
			
		||||
    protected $personRepository;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var ConfigPersonAltNamesHelper
 | 
			
		||||
     */
 | 
			
		||||
    protected $configPersonAltNameHelper;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var EntityManagerInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $em;
 | 
			
		||||
    private $em;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var \Psr\Log\LoggerInterface
 | 
			
		||||
     */
 | 
			
		||||
    private $logger;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var ValidatorInterface
 | 
			
		||||
     */
 | 
			
		||||
    private $validator;
 | 
			
		||||
 | 
			
		||||
  public function __construct(
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        SimilarPersonMatcher $similarPersonMatcher,
 | 
			
		||||
        TranslatorInterface $translator,
 | 
			
		||||
        EventDispatcherInterface $eventDispatcher,
 | 
			
		||||
@@ -117,8 +107,7 @@ class PersonController extends AbstractController
 | 
			
		||||
    {
 | 
			
		||||
        $cFGroup = null;
 | 
			
		||||
 | 
			
		||||
        $em  = $this->getDoctrine()->getManager();
 | 
			
		||||
        $cFDefaultGroup  = $em->getRepository("ChillCustomFieldsBundle:CustomFieldsDefaultGroup")
 | 
			
		||||
        $cFDefaultGroup  = $this->em->getRepository("ChillCustomFieldsBundle:CustomFieldsDefaultGroup")
 | 
			
		||||
            ->findOneByEntity("Chill\PersonBundle\Entity\Person");
 | 
			
		||||
 | 
			
		||||
        if($cFDefaultGroup) {
 | 
			
		||||
@@ -207,8 +196,7 @@ class PersonController extends AbstractController
 | 
			
		||||
                            ->trans('The person data has been updated')
 | 
			
		||||
                            );
 | 
			
		||||
 | 
			
		||||
            $em = $this->getDoctrine()->getManager();
 | 
			
		||||
            $em->flush();
 | 
			
		||||
            $this->em->flush();
 | 
			
		||||
 | 
			
		||||
            $url = $this->generateUrl('chill_person_view', array(
 | 
			
		||||
                'person_id' => $person->getId()
 | 
			
		||||
@@ -402,11 +390,9 @@ class PersonController extends AbstractController
 | 
			
		||||
                'You are not allowed to create this person');
 | 
			
		||||
 | 
			
		||||
        if ($errors->count() ===  0) {
 | 
			
		||||
            $em = $this->getDoctrine()->getManager();
 | 
			
		||||
            $this->em->persist($person);
 | 
			
		||||
 | 
			
		||||
            $em->persist($person);
 | 
			
		||||
 | 
			
		||||
            $em->flush();
 | 
			
		||||
            $this->em->flush();
 | 
			
		||||
 | 
			
		||||
            return $this->redirect($this->generateUrl('chill_person_general_edit',
 | 
			
		||||
                    array('person_id' => $person->getId())));
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ use Chill\PersonBundle\Doctrine\DQL\AddressPart;
 | 
			
		||||
 */
 | 
			
		||||
class ChillPersonExtension extends Extension implements PrependExtensionInterface
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     * @param array $configs
 | 
			
		||||
@@ -49,14 +49,14 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
    {
 | 
			
		||||
        $configuration = new Configuration();
 | 
			
		||||
        $config = $this->processConfiguration($configuration, $configs);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // set configuration for validation
 | 
			
		||||
        $container->setParameter('chill_person.validation.birtdate_not_before',
 | 
			
		||||
                $config['validation']['birthdate_not_after']);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $this->handlePersonFieldsParameters($container, $config['person_fields']);
 | 
			
		||||
        $this->handleAccompanyingPeriodsFieldsParameters($container, $config['accompanying_periods_fields']);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_person.allow_multiple_simultaneous_accompanying_periods',
 | 
			
		||||
                $config['allow_multiple_simultaneous_accompanying_periods']);
 | 
			
		||||
 | 
			
		||||
@@ -75,19 +75,20 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
        $loader->load('services/repository.yaml');
 | 
			
		||||
        $loader->load('services/templating.yaml');
 | 
			
		||||
        $loader->load('services/alt_names.yaml');
 | 
			
		||||
        
 | 
			
		||||
        $loader->load('services/serializer.yaml');
 | 
			
		||||
 | 
			
		||||
        // load service advanced search only if configure
 | 
			
		||||
        if ($config['search']['search_by_phone'] != 'never') {
 | 
			
		||||
            $loader->load('services/search_by_phone.yaml');
 | 
			
		||||
            $container->setParameter('chill_person.search.search_by_phone',
 | 
			
		||||
                $config['search']['search_by_phone']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
 | 
			
		||||
            $loader->load('services/exports_accompanying_period.yaml');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     * @param $config
 | 
			
		||||
@@ -97,9 +98,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
        if (array_key_exists('enabled', $config)) {
 | 
			
		||||
            unset($config['enabled']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_person.person_fields', $config);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        foreach ($config as $key => $value) {
 | 
			
		||||
            switch($key) {
 | 
			
		||||
                case 'accompanying_period':
 | 
			
		||||
@@ -111,7 +112,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     * @param $config
 | 
			
		||||
@@ -119,7 +120,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
    private function handleAccompanyingPeriodsFieldsParameters(ContainerBuilder $container, $config)
 | 
			
		||||
    {
 | 
			
		||||
        $container->setParameter('chill_person.accompanying_period_fields', $config);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        foreach ($config as $key => $value) {
 | 
			
		||||
            switch($key) {
 | 
			
		||||
                case 'enabled':
 | 
			
		||||
@@ -130,7 +131,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     * @throws MissingBundleException
 | 
			
		||||
@@ -150,7 +151,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     * @throws MissingBundleException
 | 
			
		||||
@@ -161,7 +162,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
        $this->prependHomepageWidget($container);
 | 
			
		||||
        $this->prependDoctrineDQL($container);
 | 
			
		||||
        $this->prependCruds($container);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        //add person_fields parameter as global
 | 
			
		||||
        $chillPersonConfig = $container->getExtensionConfig($this->getAlias());
 | 
			
		||||
        $config = $this->processConfiguration(new Configuration(), $chillPersonConfig);
 | 
			
		||||
@@ -179,7 +180,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
        $container->prependExtensionConfig('twig', $twigConfig);
 | 
			
		||||
 | 
			
		||||
        $this-> declarePersonAsCustomizable($container);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        //declare routes for person bundle
 | 
			
		||||
         $container->prependExtensionConfig('chill_main', array(
 | 
			
		||||
           'routing' => array(
 | 
			
		||||
@@ -189,7 +190,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
           )
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a widget "add a person" on the homepage, automatically
 | 
			
		||||
     *
 | 
			
		||||
@@ -208,7 +209,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
            )
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add role hierarchy.
 | 
			
		||||
     *
 | 
			
		||||
@@ -225,7 +226,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
           )
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add DQL function linked with person
 | 
			
		||||
     *
 | 
			
		||||
@@ -234,7 +235,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
    protected function prependDoctrineDQL(ContainerBuilder $container)
 | 
			
		||||
    {
 | 
			
		||||
        //add DQL function to ORM (default entity_manager)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $container->prependExtensionConfig('doctrine', array(
 | 
			
		||||
           'orm' => array(
 | 
			
		||||
              'dql' => array(
 | 
			
		||||
@@ -257,7 +258,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
 | 
			
		||||
           )
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -103,7 +103,7 @@ class AccompanyingPeriod
 | 
			
		||||
     * @ORM\Column(type="text")
 | 
			
		||||
     */
 | 
			
		||||
    private $remark = '';
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection
 | 
			
		||||
     *
 | 
			
		||||
@@ -112,7 +112,7 @@ class AccompanyingPeriod
 | 
			
		||||
     *     )
 | 
			
		||||
     */
 | 
			
		||||
    private $comments;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection
 | 
			
		||||
     *
 | 
			
		||||
@@ -121,7 +121,7 @@ class AccompanyingPeriod
 | 
			
		||||
     *     cascade={"persist", "remove", "merge", "detach"})
 | 
			
		||||
     */
 | 
			
		||||
    private $participations;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var AccompanyingPeriod\ClosingMotive
 | 
			
		||||
     *
 | 
			
		||||
@@ -130,19 +130,19 @@ class AccompanyingPeriod
 | 
			
		||||
     * @ORM\JoinColumn(nullable=true)
 | 
			
		||||
     */
 | 
			
		||||
    private $closingMotive = null;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\ManyToOne(targetEntity=User::class)
 | 
			
		||||
     * @ORM\JoinColumn(nullable=true)
 | 
			
		||||
     */
 | 
			
		||||
    private $user;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\ManyToOne(targetEntity=User::class)
 | 
			
		||||
     * @ORM\JoinColumn(nullable=true)
 | 
			
		||||
     */
 | 
			
		||||
    private $createdBy;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string
 | 
			
		||||
     * @ORM\Column(type="string", length=32, nullable=true)
 | 
			
		||||
@@ -154,7 +154,7 @@ class AccompanyingPeriod
 | 
			
		||||
     * @ORM\JoinColumn(nullable=true)
 | 
			
		||||
     */
 | 
			
		||||
    private $origin;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string
 | 
			
		||||
     * @ORM\Column(type="string", nullable=true)
 | 
			
		||||
@@ -214,7 +214,7 @@ class AccompanyingPeriod
 | 
			
		||||
     * )
 | 
			
		||||
     */
 | 
			
		||||
    private $resources;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * AccompanyingPeriod constructor.
 | 
			
		||||
     *
 | 
			
		||||
@@ -246,7 +246,7 @@ class AccompanyingPeriod
 | 
			
		||||
    public function setOpeningDate($openingDate)
 | 
			
		||||
    {
 | 
			
		||||
        $this->openingDate = $openingDate;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -272,7 +272,7 @@ class AccompanyingPeriod
 | 
			
		||||
    public function setClosingDate($closingDate)
 | 
			
		||||
    {
 | 
			
		||||
        $this->closingDate = $closingDate;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -285,7 +285,7 @@ class AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return $this->closingDate;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return boolean
 | 
			
		||||
     */
 | 
			
		||||
@@ -298,43 +298,39 @@ class AccompanyingPeriod
 | 
			
		||||
        if ($this->getClosingDate() === null) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function setRemark(string $remark): self
 | 
			
		||||
 | 
			
		||||
    public function setRemark(string $remark = null): self
 | 
			
		||||
    {
 | 
			
		||||
        if ($remark === null) {
 | 
			
		||||
            $remark = '';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $this->remark = $remark;
 | 
			
		||||
    
 | 
			
		||||
        $this->remark = (string) $remark;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function getRemark(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->remark;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function getComments(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->comments;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function addComment(Comment $comment): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->comments[] = $comment;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function removeComment(Comment $comment): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->comments->removeElement($comment);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get Participations Collection
 | 
			
		||||
     */
 | 
			
		||||
@@ -342,7 +338,7 @@ class AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return $this->participations;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This private function scan Participations Collection,
 | 
			
		||||
     * searching for a given Person
 | 
			
		||||
@@ -357,7 +353,7 @@ class AccompanyingPeriod
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This public function is the same but return only true or false
 | 
			
		||||
     */
 | 
			
		||||
@@ -365,7 +361,7 @@ class AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return ($this->participationsContainsPerson($person) === null) ? false : true;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add Person
 | 
			
		||||
     */
 | 
			
		||||
@@ -373,36 +369,36 @@ class AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        $participation = new AccompanyingPeriodParticipation($this, $person);
 | 
			
		||||
        $this->participations[] = $participation;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove Person
 | 
			
		||||
     */
 | 
			
		||||
    public function removePerson(Person $person): void
 | 
			
		||||
    {
 | 
			
		||||
        $participation = $this->participationsContainsPerson($person);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (! null === $participation) {
 | 
			
		||||
            $participation->setEndDate(new \DateTimeImmutable('now'));
 | 
			
		||||
            $this->participations->removeElement($participation);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function getClosingMotive(): ?ClosingMotive
 | 
			
		||||
    {
 | 
			
		||||
        return $this->closingMotive;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setClosingMotive(ClosingMotive $closingMotive = null): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->closingMotive = $closingMotive;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If the period can be reopened.
 | 
			
		||||
     *
 | 
			
		||||
@@ -414,7 +410,7 @@ class AccompanyingPeriod
 | 
			
		||||
        if ($this->isOpen() === true) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $participation = $this->participationsContainsPerson($person);
 | 
			
		||||
        if (!null === $participation)
 | 
			
		||||
        {
 | 
			
		||||
@@ -422,10 +418,10 @@ class AccompanyingPeriod
 | 
			
		||||
            $periods = $person->getAccompanyingPeriodsOrdered();
 | 
			
		||||
            return end($periods) === $this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     */
 | 
			
		||||
    public function reOpen(): void
 | 
			
		||||
@@ -442,14 +438,14 @@ class AccompanyingPeriod
 | 
			
		||||
        if ($this->isOpen()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (! $this->isClosingAfterOpening()) {
 | 
			
		||||
            $context->buildViolation('The date of closing is before the date of opening')
 | 
			
		||||
                ->atPath('dateClosing')
 | 
			
		||||
                ->addViolation();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true if the closing date is after the opening date.
 | 
			
		||||
     *
 | 
			
		||||
@@ -467,16 +463,16 @@ class AccompanyingPeriod
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    function getUser(): ?User
 | 
			
		||||
    {
 | 
			
		||||
        return $this->user;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    function setUser(User $user): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->user = $user;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -484,38 +480,38 @@ class AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return $this->origin;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setOrigin(Origin $origin): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->origin = $origin;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function getRequestorPerson(): ?Person
 | 
			
		||||
    {
 | 
			
		||||
        return $this->requestorPerson;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setRequestorPerson(Person $requestorPerson): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->requestorPerson = ($this->requestorThirdParty === null) ? $requestorPerson : null;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function getRequestorThirdParty(): ?ThirdParty
 | 
			
		||||
    {
 | 
			
		||||
        return $this->requestorThirdParty;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setRequestorThirdParty(ThirdParty $requestorThirdParty): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->requestorThirdParty = ($this->requestorPerson === null) ? $requestorThirdParty : null;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Person|ThirdParty
 | 
			
		||||
     */
 | 
			
		||||
@@ -523,48 +519,48 @@ class AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return $this->requestorPerson ?? $this->requestorThirdParty;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function isRequestorAnonymous(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->requestorAnonymous;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setRequestorAnonymous(bool $requestorAnonymous): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->requestorAnonymous = $requestorAnonymous;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function isEmergency(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->emergency;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setEmergency(bool $emergency): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->emergency = $emergency;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function isConfidential(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->confidential;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setConfidential(bool $confidential): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->confidential = $confidential;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function getCreatedBy(): ?User
 | 
			
		||||
    {
 | 
			
		||||
        return $this->createdBy;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setCreatedBy(User $createdBy): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->createdBy = $createdBy;
 | 
			
		||||
@@ -576,11 +572,11 @@ class AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return $this->step;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setStep(string $step): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->step = $step;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -588,45 +584,57 @@ class AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return $this->intensity;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function setIntensity(string $intensity): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->intensity = $intensity;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function getScopes(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->scopes;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function addScope(Scope $scope): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->scopes[] = $scope;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function removeScope(Scope $scope): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->scopes->removeElement($scope);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function getResources(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->resources;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function addResource(Resource $resource): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->resources[] = $resource;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function removeResource(Resource $resource): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->resources->removeElement($resource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a list of all persons which are participating to this course
 | 
			
		||||
     */
 | 
			
		||||
    public function getPersons(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->participations->map(
 | 
			
		||||
            function(AccompanyingPeriodParticipation $participation) {
 | 
			
		||||
                return $participation->getPerson();
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Entity\Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\Result;
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\SocialAction;
 | 
			
		||||
use App\Repository\Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\Result;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
 | 
			
		||||
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Entity\Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\Goal;
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\Result;
 | 
			
		||||
use App\Repository\Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoalRepository;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\Goal;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\Result;
 | 
			
		||||
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkGoalRepository;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,15 +22,17 @@ namespace Chill\PersonBundle\Entity;
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Criteria;
 | 
			
		||||
use ArrayIterator;
 | 
			
		||||
use Chill\MainBundle\Entity\Center;
 | 
			
		||||
use Chill\MainBundle\Entity\Country;
 | 
			
		||||
use Chill\PersonBundle\Entity\MaritalStatus;
 | 
			
		||||
use Chill\MainBundle\Entity\HasCenterInterface;
 | 
			
		||||
use Chill\MainBundle\Entity\Address;
 | 
			
		||||
use DateTime;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Criteria;
 | 
			
		||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -1008,32 +1010,28 @@ class Person implements HasCenterInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * By default, the addresses are ordered by date, descending (the most
 | 
			
		||||
     * recent first)
 | 
			
		||||
     *
 | 
			
		||||
     * @return \Chill\MainBundle\Entity\Address[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getAddresses()
 | 
			
		||||
    public function getAddresses(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->addresses;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param \DateTime|null $date
 | 
			
		||||
     * @return null
 | 
			
		||||
     */
 | 
			
		||||
    public function getLastAddress(\DateTime $date = null)
 | 
			
		||||
    public function getLastAddress(DateTime $from = null)
 | 
			
		||||
    {
 | 
			
		||||
        if ($date === null) {
 | 
			
		||||
            $date = new \DateTime('now');
 | 
			
		||||
        }
 | 
			
		||||
        $from ??= new DateTime('now');
 | 
			
		||||
 | 
			
		||||
        $addresses = $this->getAddresses();
 | 
			
		||||
        /** @var ArrayIterator $addressesIterator */
 | 
			
		||||
        $addressesIterator = $this->getAddresses()
 | 
			
		||||
            ->filter(static fn (Address $address): bool => $address->getValidFrom() <= $from)
 | 
			
		||||
            ->getIterator();
 | 
			
		||||
 | 
			
		||||
        if ($addresses == null) {
 | 
			
		||||
        $addressesIterator->uasort(
 | 
			
		||||
            static fn (Address $left, Address $right): int => $right->getValidFrom() <=> $left->getValidFrom()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $addresses->first();
 | 
			
		||||
        return [] === ($addresses = iterator_to_array($addressesIterator)) ?
 | 
			
		||||
            null :
 | 
			
		||||
            current($addresses);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Entity\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Repository\Chill\PersonBundle\Entity\SocialWork\EvaluationRepository;
 | 
			
		||||
use Chill\PersonBundle\Repository\SocialWork\EvaluationRepository;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Entity\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Repository\Chill\PersonBundle\Entity\SocialWork\GoalRepository;
 | 
			
		||||
use Chill\PersonBundle\Repository\SocialWork\GoalRepository;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Entity\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal;
 | 
			
		||||
use App\Repository\Chill\PersonBundle\Entity\SocialWork\ResultRepository;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal;
 | 
			
		||||
use Chill\PersonBundle\Repository\SocialWork\ResultRepository;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Entity\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Repository\Chill\PersonBundle\Entity\SocialWork\SocialActionRepository;
 | 
			
		||||
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
 | 
			
		||||
namespace App\Entity\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Repository\Chill\PersonBundle\Entity\SocialWork\SocialIssueRepository;
 | 
			
		||||
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\PersonBundle\Privacy;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2014-2015, 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/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\EventDispatcher\Event;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
class AccompanyingPeriodPrivacyEvent extends Event
 | 
			
		||||
{
 | 
			
		||||
    public const ACCOMPANYING_PERIOD_PRIVACY_EVENT = 'chill_person.accompanying_period_privacy_event';
 | 
			
		||||
 | 
			
		||||
    protected AccompanyingPeriod $period;
 | 
			
		||||
 | 
			
		||||
    protected array $args;
 | 
			
		||||
 | 
			
		||||
    public function __construct($period, $args = [])
 | 
			
		||||
    {
 | 
			
		||||
        $this->period = $period;
 | 
			
		||||
        $this->args = $args;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPeriod(): AccompanyingPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return $this->period;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getArgs(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->args;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -26,20 +26,21 @@ use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
 | 
			
		||||
 | 
			
		||||
class PrivacyEventSubscriber implements EventSubscriberInterface
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var LoggerInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $logger;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var TokenStorageInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $token;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * PrivacyEventSubscriber constructor.
 | 
			
		||||
     *
 | 
			
		||||
@@ -50,40 +51,64 @@ class PrivacyEventSubscriber implements EventSubscriberInterface
 | 
			
		||||
        $this->logger = $logger;
 | 
			
		||||
        $this->token = $token;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public static function getSubscribedEvents()
 | 
			
		||||
    {
 | 
			
		||||
        return array(PrivacyEvent::PERSON_PRIVACY_EVENT => array(
 | 
			
		||||
            array('onPrivacyEvent')
 | 
			
		||||
        ));
 | 
			
		||||
        return [
 | 
			
		||||
            PrivacyEvent::PERSON_PRIVACY_EVENT => [
 | 
			
		||||
                ['onPrivacyEvent']
 | 
			
		||||
            ],
 | 
			
		||||
            AccompanyingPeriodPrivacyEvent::ACCOMPANYING_PERIOD_PRIVACY_EVENT => [
 | 
			
		||||
                ['onAccompanyingPeriodPrivacyEvent']
 | 
			
		||||
            ]
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function onAccompanyingPeriodPrivacyEvent(AccompanyingPeriodPrivacyEvent $event)
 | 
			
		||||
    {
 | 
			
		||||
        $involved = $this->getInvolved();
 | 
			
		||||
        $involved['period_id'] = $event->getPeriod()->getId();
 | 
			
		||||
        $involved['persons'] = $event->getPeriod()->getPersons()
 | 
			
		||||
            ->map(function(Person $p) { return $p->getId(); })
 | 
			
		||||
            ->toArray();
 | 
			
		||||
 | 
			
		||||
        $this->logger->notice(
 | 
			
		||||
            "[Privacy Event] An accompanying period has been viewed",
 | 
			
		||||
            array_merge($involved, $event->getArgs())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function onPrivacyEvent(PrivacyEvent $event)
 | 
			
		||||
    {
 | 
			
		||||
        $persons = array();
 | 
			
		||||
        
 | 
			
		||||
        $persons = [];
 | 
			
		||||
 | 
			
		||||
        if ($event->hasPersons() === true) {
 | 
			
		||||
            foreach ($event->getPersons() as $person) {
 | 
			
		||||
                $persons[] = $person->getId();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $involved = array(
 | 
			
		||||
            'by_user' => $this->token->getToken()->getUser()->getUsername(),
 | 
			
		||||
            'by_user_id' => $this->token->getToken()->getUser()->getId(),
 | 
			
		||||
            'person_id' => $event->getPerson()->getId(),
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $involved = $this->getInvolved();
 | 
			
		||||
        $involved['person_id'] = $event->getPerson()->getId();
 | 
			
		||||
 | 
			
		||||
        if ($event->hasPersons()) {
 | 
			
		||||
            $involved['persons'] = \array_map(
 | 
			
		||||
                function(Person $p) { return $p->getId(); }, 
 | 
			
		||||
                function(Person $p) { return $p->getId(); },
 | 
			
		||||
                $event->getPersons()
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $this->logger->notice(
 | 
			
		||||
            "[Privacy Event] A Person Folder has been viewed",
 | 
			
		||||
            array_merge($involved, $event->getArgs())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    protected function getInvolved(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'by_user' => $this->token->getToken()->getUser()->getUsername(),
 | 
			
		||||
            'by_user_id' => $this->token->getToken()->getUser()->getId(),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repository\Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repository\Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@
 | 
			
		||||
namespace Chill\PersonBundle\Repository;
 | 
			
		||||
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
@@ -38,5 +39,4 @@ class AccompanyingPeriodRepository extends ServiceEntityRepository
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($registry, AccompanyingPeriod::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repository\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Repository\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\Evaluation;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repository\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Repository\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\Goal;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\Goal;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repository\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Repository\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\Result;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\Result;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repository\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Repository\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\SocialAction;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repository\Chill\PersonBundle\Entity\SocialWork;
 | 
			
		||||
namespace Chill\PersonBundle\Repository\SocialWork;
 | 
			
		||||
 | 
			
		||||
use App\Entity\Chill\PersonBundle\Entity\SocialWork\SocialIssue;
 | 
			
		||||
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
<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>
 | 
			
		||||
@@ -1,11 +1,24 @@
 | 
			
		||||
<h3 class="single-line">
 | 
			
		||||
<div>
 | 
			
		||||
  <h3 class="single-line">
 | 
			
		||||
    {{ period.closingDate|format_date('long')  }}
 | 
			
		||||
    <span class="person"> /
 | 
			
		||||
        <a href="{{ path('chill_person_accompanying_period_list', { 'person_id': person.id } ) }}">
 | 
			
		||||
            {{ 'Closing the accompanying period' | trans }}
 | 
			
		||||
        </a>
 | 
			
		||||
    <span>
 | 
			
		||||
    <span class="chill-red">
 | 
			
		||||
        <i class="fa fa-folder"></i>
 | 
			
		||||
    </span>
 | 
			
		||||
</h3> 
 | 
			
		||||
    <span class="person"> / </span>
 | 
			
		||||
    {{ 'An accompanying period ends'|trans }}
 | 
			
		||||
    {% if 'person' != context %}
 | 
			
		||||
      {% for p in period.persons %}
 | 
			
		||||
        / {{ p|chill_entity_render_box({'addLink': true}) }}
 | 
			
		||||
      {% endfor %}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
  </h3>
 | 
			
		||||
 | 
			
		||||
  <div class="statement">
 | 
			
		||||
    <dl class="chill_view_data">
 | 
			
		||||
      <dd>{{ 'Participants'|trans }} :</dd>
 | 
			
		||||
      <dt>
 | 
			
		||||
      <ul>
 | 
			
		||||
        {% for p in period.participations %}
 | 
			
		||||
        <li>{{ p.person|chill_entity_render_box({ 'addLink': true }) }}: {{ 'since %date%'|trans({'%date%': p.startDate|format_date("long") } ) }}, {{ 'until %date%'|trans({'%date%': (p.endDate is not null ? p.endDate : period.closingDate)|format_date("long") }) }}</li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
      </ul>
 | 
			
		||||
      </dt>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,24 @@
 | 
			
		||||
<h3 class="single-line">
 | 
			
		||||
<div>
 | 
			
		||||
  <h3 class="single-line">
 | 
			
		||||
    {{ period.openingDate|format_date('long')  }}
 | 
			
		||||
    <span class="person"> /
 | 
			
		||||
        <a href="{{ path('chill_person_accompanying_period_list', { 'person_id': person.id } ) }}">
 | 
			
		||||
            {{ 'Opening the accompanying period' | trans }}
 | 
			
		||||
        </a>
 | 
			
		||||
    </span>
 | 
			
		||||
    <span class="chill-green">
 | 
			
		||||
        <i class="fa fa-folder-open"></i>
 | 
			
		||||
    </span>
 | 
			
		||||
</h3> 
 | 
			
		||||
    <span class="person"> / </span>
 | 
			
		||||
    {{ 'An accompanying period starts'|trans }}
 | 
			
		||||
    {% if 'person' != context %}
 | 
			
		||||
      {% for p in period.persons %}
 | 
			
		||||
        / {{ p|chill_entity_render_box({'addLink': true}) }}
 | 
			
		||||
      {% endfor %}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
  </h3>
 | 
			
		||||
 | 
			
		||||
  <div class="statement">
 | 
			
		||||
    <dl class="chill_view_data">
 | 
			
		||||
      <dd>{{ 'Participants'|trans }} :</dd>
 | 
			
		||||
      <dt>
 | 
			
		||||
      <ul>
 | 
			
		||||
        {% for p in period.participations %}
 | 
			
		||||
        <li>{{ 'Since %date%'|trans( {'%date%': p.startDate|format_date("long") } ) }} : {{ p.person|chill_entity_render_box({ 'addLink': true }) }}</li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
      </ul>
 | 
			
		||||
      </dt>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS, <http://www.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\PersonBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccompanyingPeriodNormalizer implements NormalizerInterface, NormalizerAwareInterface {
 | 
			
		||||
 | 
			
		||||
    protected ?NormalizerInterface $normalizer = null;
 | 
			
		||||
 | 
			
		||||
    public function normalize($period, string $format = null, array $context = array())
 | 
			
		||||
    {
 | 
			
		||||
        /** @var AccompanyingPeriod $period */
 | 
			
		||||
        return [
 | 
			
		||||
            'id' => $period->getId(),
 | 
			
		||||
            'openingDate' => $this->normalizer->normalize($period->getOpeningDate(), $format),
 | 
			
		||||
            'closingDate' => $this->normalizer->normalize($period->getClosingDate(), $format),
 | 
			
		||||
            'remark' => $period->getRemark(),
 | 
			
		||||
            'participations' => $this->normalizer->normalize($period->getParticipations(), $format),
 | 
			
		||||
            'closingMotive' => $this->normalizer->normalize($period->getClosingMotive(), $format),
 | 
			
		||||
            'user' => $this->normalizer->normalize($period->getUser(), $format),
 | 
			
		||||
            'step' => $period->getStep(),
 | 
			
		||||
            'origin' => $this->normalizer->normalize($period->getOrigin(), $format),
 | 
			
		||||
            'intensity' => $period->getIntensity(),
 | 
			
		||||
            'emergency' => $period->isEmergency(),
 | 
			
		||||
            'confidential' => $period->isConfidential()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsNormalization($data, string $format = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof AccompanyingPeriod;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setNormalizer(NormalizerInterface $normalizer)
 | 
			
		||||
    {
 | 
			
		||||
        $this->normalizer = $normalizer;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS, <http://www.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\PersonBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccompanyingPeriodParticipationNormalizer implements NormalizerInterface, NormalizerAwareInterface {
 | 
			
		||||
 | 
			
		||||
    protected ?NormalizerInterface $normalizer = null;
 | 
			
		||||
 | 
			
		||||
    public function normalize($participation, string $format = null, array $context = array())
 | 
			
		||||
    {
 | 
			
		||||
        /** @var AccompanyingPeriodParticipation $participation */
 | 
			
		||||
        return [
 | 
			
		||||
            'id' => $participation->getId(),
 | 
			
		||||
            'startDate' => $this->normalizer->normalize($participation->getStartDate(), $format),
 | 
			
		||||
            'endDate' => $this->normalizer->normalize($participation->getEndDate(), $format),
 | 
			
		||||
            'person' => $this->normalizer->normalize($participation->getPerson(), $format)
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsNormalization($data, string $format = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof AccompanyingPeriodParticipation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setNormalizer(NormalizerInterface $normalizer)
 | 
			
		||||
    {
 | 
			
		||||
        $this->normalizer = $normalizer;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS, <http://www.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\PersonBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
 | 
			
		||||
use Chill\PersonBundle\Repository\PersonRepository;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\RuntimeException;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Serialize a Person entity
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class PersonNormalizer implements
 | 
			
		||||
    NormalizerInterface,
 | 
			
		||||
    NormalizerAwareInterface,
 | 
			
		||||
    DenormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    protected NormalizerInterface $normalizer;
 | 
			
		||||
 | 
			
		||||
    protected PersonRepository $repository;
 | 
			
		||||
 | 
			
		||||
    public const GET_PERSON = 'get_person';
 | 
			
		||||
 | 
			
		||||
    public function __construct(PersonRepository $repository)
 | 
			
		||||
    {
 | 
			
		||||
        $this->repository = $repository;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function normalize($person, string $format = null, array $context = array())
 | 
			
		||||
    {
 | 
			
		||||
        /** @var Person $person */
 | 
			
		||||
        return [
 | 
			
		||||
            'id' => $person->getId(),
 | 
			
		||||
            'firstName' => $person->getFirstName(),
 | 
			
		||||
            'lastName' => $person->getLastName(),
 | 
			
		||||
            'birthdate' => $person->getBirthdate() ? $this->normalizer->normalize($person->getBirthdate()) : null,
 | 
			
		||||
            'center' => $this->normalizer->normalize($person->getCenter())
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function denormalize($data, string $type, string $format = null, array $context = []): Person
 | 
			
		||||
    {
 | 
			
		||||
        if ($context[self::GET_PERSON] ?? true) {
 | 
			
		||||
            $id = $data['id'] ?? null;
 | 
			
		||||
            if (NULL === $id) {
 | 
			
		||||
                throw new RuntimeException("missing id into person object");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        /** var Person $person */
 | 
			
		||||
        $person = $this->repository->findOneById($id);
 | 
			
		||||
 | 
			
		||||
        if (NULL === $person) {
 | 
			
		||||
            return UnexpectedValueException("person id not found");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $person;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsNormalization($data, string $format = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof Person;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsDenormalization($data, string $type, ?string $format = NULL): bool
 | 
			
		||||
    {
 | 
			
		||||
        return Person::class === $type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setNormalizer(NormalizerInterface $normalizer)
 | 
			
		||||
    {
 | 
			
		||||
        $this->normalizer = $normalizer;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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 = '';
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,174 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 * 
 | 
			
		||||
 * Copyright (C) 2014-2015, 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\PersonBundle\Tests\Controller;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Entity\Center;
 | 
			
		||||
use Doctrine\Common\Collections\Criteria;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test api for AccompanyingCourseControllerTest
 | 
			
		||||
 */
 | 
			
		||||
class AccompanyingCourseControllerTest extends WebTestCase
 | 
			
		||||
{
 | 
			
		||||
    protected static EntityManagerInterface $em;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Setup before the first test of this class (see phpunit doc)
 | 
			
		||||
     */
 | 
			
		||||
    public static function setUpBeforeClass()
 | 
			
		||||
    {
 | 
			
		||||
        static::bootKernel();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Setup before each test method (see phpunit doc)
 | 
			
		||||
     */
 | 
			
		||||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        $this->client = static::createClient(array(), array(
 | 
			
		||||
           'PHP_AUTH_USER' => 'center a_social',
 | 
			
		||||
           'PHP_AUTH_PW'   => 'password',
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @dataProvider dataGenerateRandomAccompanyingCourse
 | 
			
		||||
     */
 | 
			
		||||
    public function testAccompanyingCourseShow(int $personId, AccompanyingPeriod $period)
 | 
			
		||||
    {
 | 
			
		||||
        $this->client->request(Request::METHOD_GET, sprintf('/fr/person/api/1.0/accompanying-course/%d/show.json', $period->getId()));
 | 
			
		||||
        $response = $this->client->getResponse();
 | 
			
		||||
 | 
			
		||||
        $this->assertEquals(200, $response->getStatusCode(), "Test that the response of rest api has a status code ok (200)");
 | 
			
		||||
        
 | 
			
		||||
        $data = \json_decode($response->getContent());
 | 
			
		||||
        $this->assertEquals($data->id, $period->getId(),
 | 
			
		||||
            "test that the response's data contains the id of the period"
 | 
			
		||||
        ); 
 | 
			
		||||
        $this->assertGreaterThan(0, $data->participations);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @dataProvider dataGenerateRandomAccompanyingCourse
 | 
			
		||||
     */
 | 
			
		||||
    public function testAccompanyingCourseAddParticipation(int $personId, AccompanyingPeriod $period)
 | 
			
		||||
    {
 | 
			
		||||
        $this->client->request(
 | 
			
		||||
            Request::METHOD_POST,
 | 
			
		||||
            sprintf('/fr/person/api/1.0/accompanying-course/%d/participation.json', $period->getId()),
 | 
			
		||||
            [], // parameters
 | 
			
		||||
            [], // files
 | 
			
		||||
            [], // server parameters
 | 
			
		||||
            \json_encode([ 'id' => $personId ])
 | 
			
		||||
        );
 | 
			
		||||
        $response = $this->client->getResponse();
 | 
			
		||||
 | 
			
		||||
        $this->assertEquals(200, $response->getStatusCode(), "Test that the response of rest api has a status code ok (200)");
 | 
			
		||||
        $this->client->request(Request::METHOD_GET, sprintf('/fr/person/api/1.0/accompanying-course/%d/show.json', $period->getId()));
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->client->getResponse();
 | 
			
		||||
        $data = \json_decode($response->getContent());
 | 
			
		||||
 | 
			
		||||
        $participationsPersonsIds = \array_map(
 | 
			
		||||
            function($participation) { return $participation->person->id; },
 | 
			
		||||
            $data->participations);
 | 
			
		||||
 | 
			
		||||
        $this->assertContains($personId, $participationsPersonsIds);
 | 
			
		||||
 | 
			
		||||
        $this->personId = $personId;
 | 
			
		||||
        $this->period = $period;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function tearDown()
 | 
			
		||||
    {
 | 
			
		||||
        // remove participation created during test 'testAccompanyingCourseAddParticipation'
 | 
			
		||||
 | 
			
		||||
        $testAddParticipationName = 'testAccompanyingCourseAddParticipation';
 | 
			
		||||
 | 
			
		||||
        if ($testAddParticipationName !== \substr($this->getName(), 0, \strlen($testAddParticipationName))) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $em = static::$container->get(EntityManagerInterface::class);
 | 
			
		||||
 | 
			
		||||
        $participation = $em
 | 
			
		||||
            ->getRepository(AccompanyingPeriodParticipation::class)
 | 
			
		||||
            ->findOneBy(['person' => $this->personId, 'accompanyingPeriod' => $this->period])
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
        $em->remove($participation);
 | 
			
		||||
        $em->flush();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function dataGenerateRandomAccompanyingCourse()
 | 
			
		||||
    {
 | 
			
		||||
        // note about max result for person query, and maxGenerated:
 | 
			
		||||
        // 
 | 
			
		||||
        // in the final loop, an id is popped out of the personIds array twice:
 | 
			
		||||
        // 
 | 
			
		||||
        // * one for getting the person, which will in turn provide his accompanying period;
 | 
			
		||||
        // * one for getting the personId to populate to the data manager
 | 
			
		||||
        //
 | 
			
		||||
        // Ensure to keep always $maxGenerated to the double of $maxResults
 | 
			
		||||
        $maxGenerated = 1;
 | 
			
		||||
        $maxResults = 15 * 8;
 | 
			
		||||
 | 
			
		||||
        static::bootKernel();
 | 
			
		||||
        $em = static::$container->get(EntityManagerInterface::class);
 | 
			
		||||
        $center = $em->getRepository(Center::class)
 | 
			
		||||
            ->findOneBy(array('name' => 'Center A'));
 | 
			
		||||
 | 
			
		||||
        $personIds = $em->createQuery("SELECT p.id FROM ".
 | 
			
		||||
            Person::class." p ".
 | 
			
		||||
            " WHERE p.center = :center")
 | 
			
		||||
            ->setParameter('center', $center)
 | 
			
		||||
            ->setMaxResults($maxResults)
 | 
			
		||||
            ->getScalarResult();
 | 
			
		||||
        
 | 
			
		||||
        // create a random order
 | 
			
		||||
        shuffle($personIds);
 | 
			
		||||
 | 
			
		||||
        $nbGenerated = 0;
 | 
			
		||||
        while ($nbGenerated < $maxGenerated) {
 | 
			
		||||
            $id = \array_pop($personIds)["id"];
 | 
			
		||||
 | 
			
		||||
            $person = $em->getRepository(Person::class)
 | 
			
		||||
                ->find($id);
 | 
			
		||||
            $periods = $person->getAccompanyingPeriods();
 | 
			
		||||
 | 
			
		||||
            yield [\array_pop($personIds)["id"], $periods[\array_rand($periods)] ];
 | 
			
		||||
 | 
			
		||||
            $nbGenerated++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -24,6 +24,10 @@ namespace Chill\PersonBundle\Tests\Entity;
 | 
			
		||||
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
use Chill\MainBundle\Entity\Address;
 | 
			
		||||
use DateInterval;
 | 
			
		||||
use DateTime;
 | 
			
		||||
use Generator;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit tests for the person Entity
 | 
			
		||||
@@ -151,4 +155,54 @@ class PersonTest extends \PHPUnit\Framework\TestCase
 | 
			
		||||
        
 | 
			
		||||
        $this->assertEquals($r['result'], Person::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function dateProvider(): Generator
 | 
			
		||||
    {
 | 
			
		||||
        yield [(DateTime::createFromFormat('Y-m-d', '2021-01-05'))->settime(0, 0)];
 | 
			
		||||
        yield [(DateTime::createFromFormat('Y-m-d', '2021-02-05'))->settime(0, 0)];
 | 
			
		||||
        yield [(DateTime::createFromFormat('Y-m-d', '2021-03-05'))->settime(0, 0)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @dataProvider dateProvider
 | 
			
		||||
     */
 | 
			
		||||
    public function testGetLastAddress(DateTime $date)
 | 
			
		||||
    {
 | 
			
		||||
        $p = new Person($date);
 | 
			
		||||
 | 
			
		||||
        // Make sure that there is no last address.
 | 
			
		||||
        $this::assertNull($p->getLastAddress());
 | 
			
		||||
 | 
			
		||||
        // Take an arbitrary date before the $date in parameter.
 | 
			
		||||
        $addressDate = clone $date;
 | 
			
		||||
 | 
			
		||||
        // 1. Smoke test: Test that the first address added is the last one.
 | 
			
		||||
        $address1 = (new Address())->setValidFrom($addressDate->sub(new DateInterval('PT180M')));
 | 
			
		||||
        $p->addAddress($address1);
 | 
			
		||||
 | 
			
		||||
        $this::assertCount(1, $p->getAddresses());
 | 
			
		||||
        $this::assertSame($address1, $p->getLastAddress());
 | 
			
		||||
 | 
			
		||||
        // 2. Add an older address, which should not be the last address.
 | 
			
		||||
        $addressDate2 = clone $addressDate;
 | 
			
		||||
        $address2 = (new Address())->setValidFrom($addressDate2->sub(new DateInterval('PT30M')));
 | 
			
		||||
        $p->addAddress($address2);
 | 
			
		||||
 | 
			
		||||
        $this::assertCount(2, $p->getAddresses());
 | 
			
		||||
        $this::assertSame($address1, $p->getLastAddress());
 | 
			
		||||
 | 
			
		||||
        // 3. Add a newer address, which should be the last address.
 | 
			
		||||
        $addressDate3 = clone $addressDate;
 | 
			
		||||
        $address3 = (new Address())->setValidFrom($addressDate3->add(new DateInterval('PT30M')));
 | 
			
		||||
        $p->addAddress($address3);
 | 
			
		||||
 | 
			
		||||
        $this::assertCount(3, $p->getAddresses());
 | 
			
		||||
        $this::assertSame($address3, $p->getLastAddress());
 | 
			
		||||
 | 
			
		||||
        // 4. Get the last address from a specific date.
 | 
			
		||||
        $this::assertEquals($address1, $p->getLastAddress($addressDate));
 | 
			
		||||
        $this::assertEquals($address2, $p->getLastAddress($addressDate2));
 | 
			
		||||
        $this::assertEquals($address3, $p->getLastAddress($addressDate3));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,14 @@ namespace Chill\PersonBundle\Timeline;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
use Doctrine\ORM\EntityManager;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
 | 
			
		||||
use Chill\MainBundle\Entity\Center;
 | 
			
		||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provide method to build timeline for accompanying periods
 | 
			
		||||
@@ -28,19 +36,22 @@ use Doctrine\ORM\EntityManager;
 | 
			
		||||
 * This class is resued by TimelineAccompanyingPeriodOpening (for opening)
 | 
			
		||||
 * and TimelineAccompanyingPeriodClosing (for closing)
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
abstract class AbstractTimelineAccompanyingPeriod implements TimelineProviderInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var EntityManager
 | 
			
		||||
     */
 | 
			
		||||
    protected $em;
 | 
			
		||||
    protected EntityManager $em;
 | 
			
		||||
 | 
			
		||||
    private Security $security;
 | 
			
		||||
 | 
			
		||||
    private AuthorizationHelper $authorizationHelper;
 | 
			
		||||
 | 
			
		||||
    private const SUPPORTED_CONTEXTS = [ 'person', 'center' ];
 | 
			
		||||
    
 | 
			
		||||
    public function __construct(EntityManager $em)
 | 
			
		||||
    public function __construct(EntityManager $em, Security $security, AuthorizationHelper $authorizationHelper)
 | 
			
		||||
    {
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->security = $security;
 | 
			
		||||
        $this->authorizationHelper = $authorizationHelper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -72,23 +83,74 @@ abstract class AbstractTimelineAccompanyingPeriod implements TimelineProviderInt
 | 
			
		||||
     */
 | 
			
		||||
    protected function basicFetchQuery($context, array $args)
 | 
			
		||||
    {
 | 
			
		||||
        if ($context !== 'person') {
 | 
			
		||||
        if (FALSE === \in_array($context, self::SUPPORTED_CONTEXTS)) {
 | 
			
		||||
            throw new \LogicException('TimelineAccompanyingPeriod is not able '
 | 
			
		||||
                    . 'to render context '.$context);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $metadata = $this->em
 | 
			
		||||
                ->getClassMetadata('ChillPersonBundle:AccompanyingPeriod')
 | 
			
		||||
                ->getClassMetadata(AccompanyingPeriod::class)
 | 
			
		||||
                ;
 | 
			
		||||
        
 | 
			
		||||
        return array(
 | 
			
		||||
            'id' => $metadata->getColumnName('id'),
 | 
			
		||||
            'FROM' => $metadata->getTableName(),
 | 
			
		||||
            'WHERE' => sprintf('%s = %d', 
 | 
			
		||||
                $metadata
 | 
			
		||||
                    ->getAssociationMapping('person')['joinColumns'][0]['name'],
 | 
			
		||||
                $args['person']->getId())
 | 
			
		||||
        );
 | 
			
		||||
        [$where, $parameters] = $this->buildWhereClause($context, $args);
 | 
			
		||||
 | 
			
		||||
        return TimelineSingleQuery::fromArray([
 | 
			
		||||
            'id' => "{$metadata->getTableName()}.{$metadata->getColumnName('id')}",
 | 
			
		||||
            'FROM' => $this->buildFromClause($context),
 | 
			
		||||
            'WHERE' => $where,
 | 
			
		||||
            'parameters' => $parameters
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function buildFromClause($context)
 | 
			
		||||
    {
 | 
			
		||||
        $period = $this->em->getClassMetadata(AccompanyingPeriod::class);
 | 
			
		||||
        $participation = $this->em->getClassMetadata(AccompanyingPeriodParticipation::class);
 | 
			
		||||
        $person = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $join = $participation->getAssociationMapping('accompanyingPeriod')['joinColumns'][0];
 | 
			
		||||
        $joinPerson = $participation->getAssociationMapping('person')['joinColumns'][0];
 | 
			
		||||
 | 
			
		||||
        if ($context === 'person') {
 | 
			
		||||
            return "{$period->getTableName()} ".
 | 
			
		||||
                "JOIN {$participation->getTableName()} ".
 | 
			
		||||
                "ON {$participation->getTableName()}.{$join['name']} = ".
 | 
			
		||||
                "{$period->getTableName()}.{$join['referencedColumnName']}";
 | 
			
		||||
        } else {
 | 
			
		||||
            return "{$period->getTableName()} ".
 | 
			
		||||
                "JOIN {$participation->getTableName()} ".
 | 
			
		||||
                "ON {$participation->getTableName()}.{$join['name']} = ".
 | 
			
		||||
                "{$period->getTableName()}.{$join['referencedColumnName']} ".
 | 
			
		||||
                "JOIN {$person->getTableName()} ".
 | 
			
		||||
                "ON {$participation->getTableName()}.{$joinPerson['name']} = ".
 | 
			
		||||
                "{$person->getTableName()}.{$joinPerson['referencedColumnName']}"
 | 
			
		||||
                ;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function buildWhereClause($context, array $args): array
 | 
			
		||||
    {
 | 
			
		||||
        $participation = $this->em->getClassMetadata(AccompanyingPeriodParticipation::class);
 | 
			
		||||
        $join = $participation->getAssociationMapping('person')['joinColumns'][0];
 | 
			
		||||
        $person = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $joinCenter = $person->getAssociationMapping('center')['joinColumns'][0];
 | 
			
		||||
 | 
			
		||||
        if ($context === 'center') {
 | 
			
		||||
            $allowedCenters = $this->authorizationHelper->filterReachableCenters($this->security->getUser(), $args['centers'], PersonVoter::SEE);
 | 
			
		||||
            $params = [];
 | 
			
		||||
            $questionMarks = [];
 | 
			
		||||
            $query = "{$person->getTableName()}.{$joinCenter['name']} IN (";
 | 
			
		||||
            foreach ($allowedCenters as $c) {
 | 
			
		||||
                $questionMarks[] = '?';
 | 
			
		||||
                $params[] = $c->getId();
 | 
			
		||||
            }
 | 
			
		||||
            $query .= \implode(", ", $questionMarks).")";
 | 
			
		||||
 | 
			
		||||
            return [$query, $params];
 | 
			
		||||
        } elseif ($context === 'person') {
 | 
			
		||||
            return [ "{$participation->getTableName()}.{$join['name']} = ?", [ $args['person']->getId() ]];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new \LogicException("this context is not supported: $context");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -104,7 +166,7 @@ abstract class AbstractTimelineAccompanyingPeriod implements TimelineProviderInt
 | 
			
		||||
    {
 | 
			
		||||
        return array(
 | 
			
		||||
            'template' => $template,
 | 
			
		||||
            'template_data' => ['person' => $args['person'], 'period' => $entity]
 | 
			
		||||
            'template_data' => ['period' => $entity, 'context' => $context]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,10 @@ namespace Chill\PersonBundle\Timeline;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
use Doctrine\ORM\EntityManager;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provide information for opening periods to timeline
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class TimelineAccompanyingPeriodClosing extends AbstractTimelineAccompanyingPeriod
 | 
			
		||||
{
 | 
			
		||||
@@ -46,20 +45,27 @@ class TimelineAccompanyingPeriodClosing extends AbstractTimelineAccompanyingPeri
 | 
			
		||||
    public function fetchQuery($context, array $args)
 | 
			
		||||
    {
 | 
			
		||||
        $metadata = $this->em
 | 
			
		||||
                    ->getClassMetadata('ChillPersonBundle:AccompanyingPeriod');
 | 
			
		||||
                    ->getClassMetadata(AccompanyingPeriod::class);
 | 
			
		||||
        
 | 
			
		||||
        $data = $this->basicFetchQuery($context, $args);
 | 
			
		||||
        
 | 
			
		||||
        $data['type'] = 'accompanying_period_closing';
 | 
			
		||||
        $data['date'] = $metadata->getColumnName('closingDate');
 | 
			
		||||
        $data['WHERE'] = sprintf('%s = %d AND %s IS NOT NULL', 
 | 
			
		||||
                $metadata
 | 
			
		||||
                    ->getAssociationMapping('person')['joinColumns'][0]['name'],
 | 
			
		||||
                $args['person']->getId(),
 | 
			
		||||
                $metadata->getColumnName('closingDate'))
 | 
			
		||||
        $query = $this->basicFetchQuery($context, $args);
 | 
			
		||||
        [$where, $parameters] = $this->buildWhereClause($context, $args);
 | 
			
		||||
        $query->setKey('accompanying_period_closing')
 | 
			
		||||
            ->setDate($metadata->getColumnName('closingDate'))
 | 
			
		||||
            ->setWhere($where)
 | 
			
		||||
            ->setParameters($parameters)
 | 
			
		||||
            ;
 | 
			
		||||
            
 | 
			
		||||
        return $data;
 | 
			
		||||
        return $query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function buildWhereClause($context, array $args): array
 | 
			
		||||
    {
 | 
			
		||||
        list($query, $params) = parent::buildWhereClause($context, $args);
 | 
			
		||||
        $period = $this->em->getClassMetadata(AccompanyingPeriod::class);
 | 
			
		||||
 | 
			
		||||
        $query .= " AND {$period->getColumnName('closingDate')} IS NOT NULL ";
 | 
			
		||||
 | 
			
		||||
        return [ $query, $params ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,10 @@ namespace Chill\PersonBundle\Timeline;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
use Doctrine\ORM\EntityManager;
 | 
			
		||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provide information for opening periods to timeline
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class TimelineAccompanyingPeriodOpening extends AbstractTimelineAccompanyingPeriod
 | 
			
		||||
{
 | 
			
		||||
@@ -46,14 +45,14 @@ class TimelineAccompanyingPeriodOpening extends AbstractTimelineAccompanyingPeri
 | 
			
		||||
    public function fetchQuery($context, array $args)
 | 
			
		||||
    {
 | 
			
		||||
        $metadata = $this->em
 | 
			
		||||
                    ->getClassMetadata('ChillPersonBundle:AccompanyingPeriod');
 | 
			
		||||
                    ->getClassMetadata(AccompanyingPeriod::class);
 | 
			
		||||
        
 | 
			
		||||
        $data = $this->basicFetchQuery($context, $args);
 | 
			
		||||
        $query = $this->basicFetchQuery($context, $args);
 | 
			
		||||
        
 | 
			
		||||
        $data['type'] = 'accompanying_period_opening';
 | 
			
		||||
        $data['date'] = $metadata->getColumnName('openingDate');
 | 
			
		||||
        $query->setKey('accompanying_period_opening')
 | 
			
		||||
            ->setDate($metadata->getColumnName('openingDate'));
 | 
			
		||||
        
 | 
			
		||||
        return $data;
 | 
			
		||||
        return $query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -16,17 +16,23 @@ services:
 | 
			
		||||
        class: Chill\PersonBundle\Timeline\TimelineAccompanyingPeriodOpening
 | 
			
		||||
        arguments:
 | 
			
		||||
            - "@doctrine.orm.entity_manager"
 | 
			
		||||
            - '@Symfony\Component\Security\Core\Security'
 | 
			
		||||
            - '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
 | 
			
		||||
        public: true
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: chill.timeline, context: 'person' }
 | 
			
		||||
            - { name: chill.timeline, context: 'center' }
 | 
			
		||||
 | 
			
		||||
    chill.person.timeline.accompanying_period_closing:
 | 
			
		||||
        class: Chill\PersonBundle\Timeline\TimelineAccompanyingPeriodClosing
 | 
			
		||||
        arguments:
 | 
			
		||||
            - "@doctrine.orm.entity_manager"
 | 
			
		||||
            - '@Symfony\Component\Security\Core\Security'
 | 
			
		||||
            - '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
 | 
			
		||||
        public: true
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: chill.timeline, context: 'person' }
 | 
			
		||||
            - { name: chill.timeline, context: 'center' }
 | 
			
		||||
 | 
			
		||||
    chill.person.security.authorization.person:
 | 
			
		||||
        class: Chill\PersonBundle\Security\Authorization\PersonVoter
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
@@ -41,4 +42,8 @@ services:
 | 
			
		||||
        tags: ['controller.service_arguments']
 | 
			
		||||
 | 
			
		||||
    Chill\PersonBundle\Controller\AccompanyingCourseController:
 | 
			
		||||
        arguments:
 | 
			
		||||
            $serializer: '@Symfony\Component\Serializer\SerializerInterface'
 | 
			
		||||
            $dispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface'
 | 
			
		||||
            $validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
 | 
			
		||||
        tags: ['controller.service_arguments']
 | 
			
		||||
 
 | 
			
		||||
@@ -18,3 +18,8 @@ services:
 | 
			
		||||
        tags: [ doctrine.repository_service ]
 | 
			
		||||
        arguments:
 | 
			
		||||
            - '@Doctrine\Persistence\ManagerRegistry'
 | 
			
		||||
 | 
			
		||||
    Chill\PersonBundle\Repository\AccompanyingPeriodParticipationRepository:
 | 
			
		||||
        arguments:
 | 
			
		||||
            - '@Doctrine\Persistence\ManagerRegistry'
 | 
			
		||||
        tags: [ doctrine.repository_service ]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								src/Bundle/ChillPersonBundle/config/services/serializer.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Bundle/ChillPersonBundle/config/services/serializer.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
---
 | 
			
		||||
services:
 | 
			
		||||
    Chill\PersonBundle\Serializer\Normalizer\PersonNormalizer:
 | 
			
		||||
        arguments:
 | 
			
		||||
            $repository: '@Chill\PersonBundle\Repository\PersonRepository'
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: 'serializer.normalizer', priority: 64 }
 | 
			
		||||
 | 
			
		||||
    Chill\PersonBundle\Serializer\Normalizer\AccompanyingPeriodNormalizer:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: 'serializer.normalizer', priority: 64 }
 | 
			
		||||
 | 
			
		||||
    Chill\PersonBundle\Serializer\Normalizer\AccompanyingPeriodParticipationNormalizer:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: 'serializer.normalizer', priority: 64 }
 | 
			
		||||
@@ -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'
 | 
			
		||||
            
 | 
			
		||||
 
 | 
			
		||||
@@ -150,6 +150,8 @@ Update accompanying period: Mettre à jour une période d'accompagnement
 | 
			
		||||
'Closing motive': 'Motif de clôture'
 | 
			
		||||
'Person details': 'Détails de la personne'
 | 
			
		||||
'Update details for %name%': 'Modifier détails de %name%'
 | 
			
		||||
An accompanying period ends: Une periode d'accompagnement se clôture
 | 
			
		||||
An accompanying period starts: Une période d'accompagnement est ouverte
 | 
			
		||||
Any accompanying periods are open: Aucune période d'accompagnement ouverte
 | 
			
		||||
An accompanying period is open: Une période d'accompagnement est ouverte
 | 
			
		||||
Accompanying period list: Périodes d'accompagnement
 | 
			
		||||
@@ -162,11 +164,10 @@ Pediod closing form is not valid: Le formulaire n'est pas valide
 | 
			
		||||
Accompanying user: Accompagnant
 | 
			
		||||
No accompanying user: Aucun accompagnant
 | 
			
		||||
No data given: Pas d'information
 | 
			
		||||
Participants: Personnes impliquées
 | 
			
		||||
# pickAPersonType
 | 
			
		||||
Pick a person: Choisir une personne
 | 
			
		||||
 | 
			
		||||
#address
 | 
			
		||||
Since %date%: Depuis le %date%
 | 
			
		||||
No address given: Pas d'adresse renseignée
 | 
			
		||||
The address has been successfully updated: L'adresse a été mise à jour avec succès
 | 
			
		||||
Update address for %name%: Mettre à jour une adresse pour %name%
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<div class="report_entry">
 | 
			
		||||
    <h3>{{ report.date|format_date('long')  }}<span class="report"> / {{ 'Report'|trans }}</span></h3>
 | 
			
		||||
  <h3>{{ report.date|format_date('long')  }}<span class="report"> / {{ 'Report'|trans }}</span>{% if 'person' != context %} / {{ report.person|chill_entity_render_box }}{% endif %}</h3>
 | 
			
		||||
    <div class="statement">
 | 
			
		||||
        <span class="statement">{{ '%user% has filled a %report_label% report'|trans(
 | 
			
		||||
           { 
 | 
			
		||||
             '%user%' : user, 
 | 
			
		||||
             '%user%' : report.user, 
 | 
			
		||||
             '%report_label%': report.CFGroup.name|localize_translatable_string, 
 | 
			
		||||
             '%date%' : report.date|format_date('long') }
 | 
			
		||||
        ) }}</span>
 | 
			
		||||
@@ -25,13 +25,13 @@
 | 
			
		||||
     
 | 
			
		||||
     <ul class="record_actions">
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="{{ path('report_view', { 'person_id': person.id, 'report_id': report.id} ) }}" class="sc-button bt-view">
 | 
			
		||||
            <a href="{{ path('report_view', { 'person_id': report.person.id, 'report_id': report.id} ) }}" class="sc-button bt-view">
 | 
			
		||||
                {{ 'View the report'|trans }}
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {% if is_granted('CHILL_REPORT_UPDATE', report) %}
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="{{ path('report_edit', { 'person_id': person.id, 'report_id': report.id} ) }}" class="sc-button bt-edit">
 | 
			
		||||
            <a href="{{ path('report_edit', { 'person_id': report.person.id, 'report_id': report.id} ) }}" class="sc-button bt-edit">
 | 
			
		||||
                {{ 'Update the report'|trans }}
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
@@ -30,71 +30,34 @@ use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
use Chill\CustomFieldsBundle\Service\CustomFieldsHelper;
 | 
			
		||||
use Chill\ReportBundle\Entity\Report;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provide report for inclusion in timeline
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 * @author Champs Libres <info@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class TimelineReportProvider implements TimelineProviderInterface
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var EntityManager
 | 
			
		||||
     */
 | 
			
		||||
    protected $em;
 | 
			
		||||
    protected EntityManager $em;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var AuthorizationHelper
 | 
			
		||||
     */
 | 
			
		||||
    protected $helper;
 | 
			
		||||
    protected AuthorizationHelper $helper;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var \Chill\MainBundle\Entity\User 
 | 
			
		||||
     */
 | 
			
		||||
    protected $user;
 | 
			
		||||
    protected CustomFieldsHelper $customFieldsHelper;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var CustomFieldsHelper
 | 
			
		||||
     */
 | 
			
		||||
    protected $customFieldsHelper;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @var
 | 
			
		||||
     */
 | 
			
		||||
    protected $showEmptyValues;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * TimelineReportProvider constructor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param EntityManager $em
 | 
			
		||||
     * @param AuthorizationHelper $helper
 | 
			
		||||
     * @param TokenStorageInterface $storage
 | 
			
		||||
     * @param CustomFieldsHelper $customFieldsHelper
 | 
			
		||||
     * @param $showEmptyValues
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        EntityManager $em,
 | 
			
		||||
        AuthorizationHelper $helper,
 | 
			
		||||
        TokenStorageInterface $storage,
 | 
			
		||||
        Security $security,
 | 
			
		||||
        CustomFieldsHelper $customFieldsHelper,
 | 
			
		||||
        $showEmptyValues
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->helper = $helper;
 | 
			
		||||
        
 | 
			
		||||
        if (!$storage->getToken()->getUser() instanceof \Chill\MainBundle\Entity\User)
 | 
			
		||||
        {
 | 
			
		||||
            throw new \RuntimeException('A user should be authenticated !');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $this->user = $storage->getToken()->getUser();
 | 
			
		||||
        $this->security = $security;
 | 
			
		||||
        $this->customFieldsHelper = $customFieldsHelper;
 | 
			
		||||
        $this->showEmptyValues = $showEmptyValues;
 | 
			
		||||
    }
 | 
			
		||||
@@ -107,70 +70,162 @@ class TimelineReportProvider implements TimelineProviderInterface
 | 
			
		||||
    {
 | 
			
		||||
        $this->checkContext($context);
 | 
			
		||||
        
 | 
			
		||||
        $metadataReport = $this->em->getClassMetadata('ChillReportBundle:Report');
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person');
 | 
			
		||||
        
 | 
			
		||||
        return array(
 | 
			
		||||
           'id' => $metadataReport->getTableName()
 | 
			
		||||
                .'.'.$metadataReport->getColumnName('id'),
 | 
			
		||||
        $report = $this->em->getClassMetadata(Report::class);
 | 
			
		||||
        [$where, $parameters] = $this->getWhereClause($context, $args);
 | 
			
		||||
 | 
			
		||||
        return TimelineSingleQuery::fromArray([
 | 
			
		||||
           'id' => $report->getTableName()
 | 
			
		||||
                .'.'.$report->getColumnName('id'),
 | 
			
		||||
           'type' => 'report',
 | 
			
		||||
           'date' => $metadataReport->getTableName()
 | 
			
		||||
                .'.'.$metadataReport->getColumnName('date'),
 | 
			
		||||
           'FROM' => $this->getFromClause($metadataReport, $metadataPerson),
 | 
			
		||||
           'WHERE' => $this->getWhereClause($metadataReport, $metadataPerson,
 | 
			
		||||
                   $args['person'])
 | 
			
		||||
        );
 | 
			
		||||
           'date' => $report->getTableName()
 | 
			
		||||
                .'.'.$report->getColumnName('date'),
 | 
			
		||||
           'FROM' => $this->getFromClause($context),
 | 
			
		||||
           'WHERE' => $where,
 | 
			
		||||
           'parameters' => $parameters
 | 
			
		||||
       ]);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function getWhereClause(ClassMetadata $metadataReport, 
 | 
			
		||||
            ClassMetadata $metadataPerson, Person $person)
 | 
			
		||||
    private function getWhereClause(string $context, array $args): array
 | 
			
		||||
    {
 | 
			
		||||
        $role = new Role('CHILL_REPORT_SEE');
 | 
			
		||||
        $reachableCenters = $this->helper->getReachableCenters($this->user, 
 | 
			
		||||
                $role);
 | 
			
		||||
        $associationMapping = $metadataReport->getAssociationMapping('person');
 | 
			
		||||
        
 | 
			
		||||
        // we start with reports having the person_id linked to person 
 | 
			
		||||
        // (currently only context "person" is supported)
 | 
			
		||||
        $whereClause = sprintf('%s = %d',
 | 
			
		||||
                 $associationMapping['joinColumns'][0]['name'],
 | 
			
		||||
                 $person->getId());
 | 
			
		||||
        
 | 
			
		||||
        // we add acl (reachable center and scopes)
 | 
			
		||||
        $centerAndScopeLines = array();
 | 
			
		||||
        foreach ($reachableCenters as $center) {
 | 
			
		||||
            $reachablesScopesId = array_map(
 | 
			
		||||
                    function(Scope $scope) { return $scope->getId(); },
 | 
			
		||||
                    $this->helper->getReachableScopes($this->user, $role, 
 | 
			
		||||
                        $person->getCenter())
 | 
			
		||||
                );
 | 
			
		||||
                    
 | 
			
		||||
            $centerAndScopeLines[] = sprintf('(%s = %d AND %s IN (%s))',
 | 
			
		||||
                    $metadataPerson->getTableName().'.'.
 | 
			
		||||
                        $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'],
 | 
			
		||||
                    $center->getId(),
 | 
			
		||||
                    $metadataReport->getTableName().'.'.
 | 
			
		||||
                        $metadataReport->getAssociationMapping('scope')['joinColumns'][0]['name'],
 | 
			
		||||
                    implode(',', $reachablesScopesId));
 | 
			
		||||
            
 | 
			
		||||
        switch ($context) {
 | 
			
		||||
            case 'person':
 | 
			
		||||
                return $this->getWhereClauseForPerson($context, $args);
 | 
			
		||||
            case 'center':
 | 
			
		||||
                return $this->getWhereClauseForCenter($context, $args);
 | 
			
		||||
            default:
 | 
			
		||||
                throw new \UnexpectedValueException("This context $context is not implemented");
 | 
			
		||||
        }
 | 
			
		||||
        $whereClause .= ' AND ('.implode(' OR ', $centerAndScopeLines).')';
 | 
			
		||||
        
 | 
			
		||||
        return $whereClause;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getWhereClauseForCenter(string $context, array $args): array
 | 
			
		||||
    {
 | 
			
		||||
        $report = $this->em->getClassMetadata(Report::class);
 | 
			
		||||
        $person = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $role = new Role('CHILL_REPORT_SEE');
 | 
			
		||||
        $reachableCenters = $this->helper->getReachableCenters($this->security->getUser(), 
 | 
			
		||||
                $role);
 | 
			
		||||
        $reportPersonId = $report->getAssociationMapping('person')['joinColumns'][0]['name'];
 | 
			
		||||
        $reportScopeId = $report->getAssociationMapping('scope')['joinColumns'][0]['name'];
 | 
			
		||||
        $personCenterId = $person->getAssociationMapping('center')['joinColumns'][0]['name'];
 | 
			
		||||
        // parameters for the query, will be filled later
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
 | 
			
		||||
        // the clause, that will be joined with an "OR"
 | 
			
		||||
        $centerScopesClause = "({person}.{center_id} = ? ".
 | 
			
		||||
            "AND {report}.{scopes_id} IN ({scopes_ids}))";
 | 
			
		||||
        // container for formatted clauses
 | 
			
		||||
        $formattedClauses = [];
 | 
			
		||||
 | 
			
		||||
        $askedCenters = $args['centers'];
 | 
			
		||||
        foreach ($reachableCenters as $center) {
 | 
			
		||||
            if (FALSE === \in_array($center, $askedCenters)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // add the center id to the parameters
 | 
			
		||||
            $parameters[] = $center->getId(); 
 | 
			
		||||
            // loop over scopes
 | 
			
		||||
            $scopeIds = [];
 | 
			
		||||
            foreach ($this->helper->getReachableScopes($this->security->getUser(), 
 | 
			
		||||
                $role, $center) as $scope) {
 | 
			
		||||
                if (\in_array($scope->getId(), $scopeIds)) {
 | 
			
		||||
                    continue; 
 | 
			
		||||
                }
 | 
			
		||||
                $scopeIds[] = $scope->getId();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $formattedClauses[] = \strtr($centerScopesClause, [
 | 
			
		||||
                '{scopes_ids}' => \implode(', ', \array_fill(0, \count($scopeIds), '?'))
 | 
			
		||||
            ]);
 | 
			
		||||
            // append $scopeIds to parameters
 | 
			
		||||
            $parameters = \array_merge($parameters, $scopeIds);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (0 === count($formattedClauses)) {
 | 
			
		||||
            return [ 'FALSE = TRUE', [] ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            \strtr(
 | 
			
		||||
                \implode(' OR ', $formattedClauses),
 | 
			
		||||
                [
 | 
			
		||||
                    '{person}' => $person->getTableName(),
 | 
			
		||||
                    '{center_id}' => $personCenterId,
 | 
			
		||||
                    '{report}' => $report->getTableName(),
 | 
			
		||||
                    '{scopes_id}' => $reportScopeId,
 | 
			
		||||
                ]
 | 
			
		||||
            ),
 | 
			
		||||
            $parameters
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getWhereClauseForPerson(string $context, array $args): array
 | 
			
		||||
    {
 | 
			
		||||
        $report = $this->em->getClassMetadata(Report::class);
 | 
			
		||||
        $person = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $role = new Role('CHILL_REPORT_SEE');
 | 
			
		||||
        $reportPersonId = $report->getAssociationMapping('person')['joinColumns'][0]['name'];
 | 
			
		||||
        $reportScopeId = $report->getAssociationMapping('scope')['joinColumns'][0]['name'];
 | 
			
		||||
        $personCenterId = $person->getAssociationMapping('center')['joinColumns'][0]['name'];
 | 
			
		||||
        // parameters for the query, will be filled later
 | 
			
		||||
        $parameters = [ $args['person']->getId() ];
 | 
			
		||||
 | 
			
		||||
        // this is the final clause  that we are going to fill
 | 
			
		||||
        $clause = "{report}.{person_id} = ? AND {report}.{scopes_id} IN ({scopes_ids})";
 | 
			
		||||
        // iterate over reachable scopes
 | 
			
		||||
        $scopes = $this->helper->getReachableScopes($this->security->getUser(), $role, 
 | 
			
		||||
            $args['person']->getCenter());
 | 
			
		||||
 | 
			
		||||
        foreach ($scopes as $scope) {
 | 
			
		||||
            if (\in_array($scope->getId(), $parameters)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $parameters[] = $scope->getId();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (1 === count($parameters)) {
 | 
			
		||||
            // nothing change, we simplify the clause
 | 
			
		||||
            $clause = "{report}.{person_id} = ? AND FALSE = TRUE";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            \strtr(
 | 
			
		||||
                $clause,
 | 
			
		||||
                [
 | 
			
		||||
                    '{report}' => $report->getTableName(),
 | 
			
		||||
                    '{person_id}' => $reportPersonId, 
 | 
			
		||||
                    '{scopes_id}' => $reportScopeId,
 | 
			
		||||
                    '{scopes_ids}' => \implode(', ', 
 | 
			
		||||
                        \array_fill(0, \count($parameters)-1, '?'))
 | 
			
		||||
                ]
 | 
			
		||||
            ),
 | 
			
		||||
            $parameters
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function getFromClause(ClassMetadata $metadataReport,
 | 
			
		||||
            ClassMetadata $metadataPerson)
 | 
			
		||||
    private function getFromClause(string $context): string
 | 
			
		||||
    {
 | 
			
		||||
        $associationMapping = $metadataReport->getAssociationMapping('person');
 | 
			
		||||
        
 | 
			
		||||
        return $metadataReport->getTableName().' JOIN '
 | 
			
		||||
            .$metadataPerson->getTableName().' ON '
 | 
			
		||||
            .$metadataPerson->getTableName().'.'.
 | 
			
		||||
                $associationMapping['joinColumns'][0]['referencedColumnName']
 | 
			
		||||
            .' = '
 | 
			
		||||
            .$associationMapping['joinColumns'][0]['name']
 | 
			
		||||
        $report = $this->em->getClassMetadata(Report::class);
 | 
			
		||||
        $person = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $reportPersonId = $report
 | 
			
		||||
            ->getAssociationMapping('person')['joinColumns'][0]['name']
 | 
			
		||||
            ;
 | 
			
		||||
        $personId = $report
 | 
			
		||||
            ->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName']
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
        $clause = "{report} ".
 | 
			
		||||
            "JOIN {person} ON {report}.{person_id} = {person}.{id_person} ";
 | 
			
		||||
 | 
			
		||||
        return \strtr($clause,
 | 
			
		||||
            [
 | 
			
		||||
                '{report}' => $report->getTableName(),
 | 
			
		||||
                '{person}' => $person->getTableName(),
 | 
			
		||||
                '{person_id}' => $reportPersonId,
 | 
			
		||||
                '{id_person}' => $personId
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -199,12 +254,11 @@ class TimelineReportProvider implements TimelineProviderInterface
 | 
			
		||||
        $this->checkContext($context);
 | 
			
		||||
        
 | 
			
		||||
        return array(
 | 
			
		||||
           'template' => 'ChillReportBundle:Timeline:report_person_context.html.twig',
 | 
			
		||||
           'template' => 'ChillReportBundle:Timeline:report.html.twig',
 | 
			
		||||
           'template_data' => array(
 | 
			
		||||
              'report' => $entity,
 | 
			
		||||
              'custom_fields_in_summary' => $this->getFieldsToRender($entity, $context),
 | 
			
		||||
              'person' => $args['person'],
 | 
			
		||||
              'user' => $entity->getUser()
 | 
			
		||||
               'report' => $entity,
 | 
			
		||||
               'context' => $context,
 | 
			
		||||
               'custom_fields_in_summary' => $this->getFieldsToRender($entity, $context),
 | 
			
		||||
           )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -272,9 +326,9 @@ class TimelineReportProvider implements TimelineProviderInterface
 | 
			
		||||
     */
 | 
			
		||||
    private function checkContext($context)
 | 
			
		||||
    {
 | 
			
		||||
        if ($context !== 'person') {
 | 
			
		||||
        if ($context !== 'person' && $context !== 'center') {
 | 
			
		||||
            throw new \LogicException("The context '$context' is not "
 | 
			
		||||
                  . "supported. Currently only 'person' is supported");
 | 
			
		||||
                  . "supported. Currently only 'person' and 'center' is supported");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,11 +20,12 @@ services:
 | 
			
		||||
        arguments:
 | 
			
		||||
            - '@doctrine.orm.entity_manager'
 | 
			
		||||
            - '@chill.main.security.authorization.helper'
 | 
			
		||||
            - '@security.token_storage'
 | 
			
		||||
            - '@Symfony\Component\Security\Core\Security'
 | 
			
		||||
            - '@chill.custom_field.helper'
 | 
			
		||||
            - '%chill_custom_fields.show_empty_values%'
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: chill.timeline, context: 'person' }
 | 
			
		||||
            - { name: chill.timeline, context: 'center' }
 | 
			
		||||
            
 | 
			
		||||
    chill.report.security.authorization.report_voter:
 | 
			
		||||
        class: Chill\ReportBundle\Security\Authorization\ReportVoter
 | 
			
		||||
@@ -43,4 +44,4 @@ services:
 | 
			
		||||
            - "@doctrine.orm.entity_manager"
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type, alias: chill_reportbundle_report }
 | 
			
		||||
            
 | 
			
		||||
            
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,9 @@
 | 
			
		||||
        {% else %}
 | 
			
		||||
            <span class="statement">{{ '%user% has created the task'|trans({ '%user%': event.author.username }) }}</span> 
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% if 'person' != context %}
 | 
			
		||||
          / {{ task.person|chill_entity_render_box({'addLink': true}) }}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </h3>
 | 
			
		||||
    
 | 
			
		||||
    <div class="statement">
 | 
			
		||||
@@ -29,5 +32,17 @@
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </dl>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ul class="record_actions">
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="{{ chill_path_add_return_path('chill_task_single_task_show', { 'id' : task.id }, false, 'Back to the timeline'|trans ) }}" class="sc-button bt-show">
 | 
			
		||||
          {{ "View the task"|trans }}
 | 
			
		||||
        </a>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <a href="{{ chill_path_add_return_path('chill_task_single_task_edit', { 'id' : task.id }, false, 'Back to the timeline'|trans) }}" class="sc-button bt-update">
 | 
			
		||||
          {{ "Edit task"|trans }}
 | 
			
		||||
        </a>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
        
 | 
			
		||||
</div>
 | 
			
		||||
@@ -18,6 +18,7 @@
 | 
			
		||||
namespace Chill\TaskBundle\Timeline;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent;
 | 
			
		||||
use Chill\TaskBundle\Entity\SingleTask;
 | 
			
		||||
@@ -25,9 +26,8 @@ use Symfony\Component\Workflow\Registry;
 | 
			
		||||
use Symfony\Component\Workflow\Workflow;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provide timeline elements related to tasks, in tasks context
 | 
			
		||||
 *
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class SingleTaskTaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
 | 
			
		||||
{
 | 
			
		||||
@@ -63,7 +63,7 @@ class SingleTaskTaskLifeCycleEventTimelineProvider implements TimelineProviderIn
 | 
			
		||||
        $singleTaskMetadata = $this->em
 | 
			
		||||
            ->getClassMetadata(SingleTask::class);
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
        return TimelineSingleQuery::fromArray([
 | 
			
		||||
            'id' => sprintf('%s.%s.%s', $metadata->getSchemaName(), $metadata->getTableName(), $metadata->getColumnName('id')),
 | 
			
		||||
            'type' => self::TYPE,
 | 
			
		||||
            'date' => $metadata->getColumnName('datetime'),
 | 
			
		||||
@@ -77,8 +77,9 @@ class SingleTaskTaskLifeCycleEventTimelineProvider implements TimelineProviderIn
 | 
			
		||||
                sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()),
 | 
			
		||||
                $singleTaskMetadata->getColumnName('id'),
 | 
			
		||||
                $args['task']->getId()
 | 
			
		||||
                )
 | 
			
		||||
        ];
 | 
			
		||||
                ),
 | 
			
		||||
            'parameters' => [],
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,43 +21,30 @@ use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent;
 | 
			
		||||
use Chill\TaskBundle\Entity\SingleTask;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
use Symfony\Component\Workflow\Registry;
 | 
			
		||||
use Symfony\Component\Workflow\Workflow;
 | 
			
		||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | 
			
		||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
 | 
			
		||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
 | 
			
		||||
use Symfony\Component\Security\Core\Role\Role;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * Provide element for timeline for 'person' and 'center' context
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var EntityManagerInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $em;
 | 
			
		||||
    protected EntityManagerInterface $em;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var Registry
 | 
			
		||||
     */
 | 
			
		||||
    protected $registry;
 | 
			
		||||
    protected Registry $registry;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var AuthorizationHelper
 | 
			
		||||
     */
 | 
			
		||||
    protected $authorizationHelper;
 | 
			
		||||
    protected AuthorizationHelper $authorizationHelper;
 | 
			
		||||
 | 
			
		||||
    protected Security $security;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var TokenStorageInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $tokenStorage;
 | 
			
		||||
    
 | 
			
		||||
    const TYPE = 'chill_task.transition';
 | 
			
		||||
    
 | 
			
		||||
@@ -65,60 +52,172 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
 | 
			
		||||
        EntityManagerInterface $em, 
 | 
			
		||||
        Registry $registry,
 | 
			
		||||
        AuthorizationHelper $authorizationHelper,
 | 
			
		||||
        TokenStorageInterface $tokenStorage
 | 
			
		||||
        Security $security
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->registry = $registry;
 | 
			
		||||
        $this->authorizationHelper = $authorizationHelper;
 | 
			
		||||
        $this->tokenStorage = $tokenStorage;
 | 
			
		||||
        $this->security = $security;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function fetchQuery($context, $args)
 | 
			
		||||
    {
 | 
			
		||||
        if ($context !== 'person') {
 | 
			
		||||
            throw new \LogicException(sprintf('%s is not able '
 | 
			
		||||
                    . 'to render context %s', self::class, $context));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $metadata = $this->em
 | 
			
		||||
            ->getClassMetadata(SingleTaskPlaceEvent::class);
 | 
			
		||||
        $singleTaskMetadata = $this->em
 | 
			
		||||
            ->getClassMetadata(SingleTask::class);
 | 
			
		||||
        $user = $this->tokenStorage->getToken()->getUser();
 | 
			
		||||
        $circles = $this->authorizationHelper->getReachableCircles(
 | 
			
		||||
            $user, new Role(ActivityVoter::SEE_DETAILS), $args['person']->getCenter());
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        if (count($circles) > 0) {
 | 
			
		||||
            $circlesId = \array_map(function($c) { return $c->getId(); }, $circles);
 | 
			
		||||
            $circleRestriction = sprintf('%s.%s.%s IN (%s)', 
 | 
			
		||||
                $singleTaskMetadata->getSchemaName(), // chill_task schema
 | 
			
		||||
                $singleTaskMetadata->getTableName(), // single_task table name
 | 
			
		||||
                $singleTaskMetadata->getAssociationMapping('circle')['joinColumns'][0]['name'],
 | 
			
		||||
                \implode(', ', $circlesId)
 | 
			
		||||
                );
 | 
			
		||||
        } else {
 | 
			
		||||
            $circleRestriction = 'FALSE = TRUE';
 | 
			
		||||
        switch ($context) {
 | 
			
		||||
            case 'person':
 | 
			
		||||
                [ $where, $parameters ] = $this->getWhereClauseForPerson($args['person']);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'center':
 | 
			
		||||
                [ $where, $parameters ] = $this->getWhereClauseForCenter($args['centers']);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new \UnexpectedValueException("context {$context} is not supported");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        return [
 | 
			
		||||
 | 
			
		||||
        return TimelineSingleQuery::fromArray([
 | 
			
		||||
            'id' => sprintf('%s.%s.%s', $metadata->getSchemaName(), $metadata->getTableName(), $metadata->getColumnName('id')),
 | 
			
		||||
            'type' => self::TYPE,
 | 
			
		||||
            'date' => $metadata->getColumnName('datetime'),
 | 
			
		||||
            'FROM' => sprintf('%s JOIN %s ON %s = %s', 
 | 
			
		||||
                sprintf('%s.%s', $metadata->getSchemaName(), $metadata->getTableName()),
 | 
			
		||||
                sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()),
 | 
			
		||||
                $metadata->getAssociationMapping('task')['joinColumns'][0]['name'],
 | 
			
		||||
                sprintf('%s.%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName(), $singleTaskMetadata->getColumnName('id'))
 | 
			
		||||
                ),
 | 
			
		||||
            'WHERE' => sprintf('%s.%s = %d and %s',
 | 
			
		||||
                sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()),
 | 
			
		||||
                $singleTaskMetadata->getAssociationMapping('person')['joinColumns'][0]['name'],
 | 
			
		||||
                $args['person']->getId(),
 | 
			
		||||
                $circleRestriction
 | 
			
		||||
                )
 | 
			
		||||
            'FROM' => $this->getFromClause($context),
 | 
			
		||||
            'WHERE' => $where,
 | 
			
		||||
            'parameters' => $parameters
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getWhereClauseForCenter(array $centers): array
 | 
			
		||||
    {
 | 
			
		||||
        $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class);
 | 
			
		||||
        $singleTask = $this->em->getClassMetadata(SingleTask::class);
 | 
			
		||||
        $person = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $personFkCenter = $person->getAssociationMapping('center')['joinColumns'][0]['name'];
 | 
			
		||||
        $taskFkCircle = $singleTask->getAssociationMapping('circle')['joinColumns'][0]['name'];
 | 
			
		||||
 | 
			
		||||
        // the parameters
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
 | 
			
		||||
        // the clause that we will repeat for each center, joined by 'OR'
 | 
			
		||||
        $clause = "{person}.{center_id} = ? AND {task}.{circle} IN ({circle_ids})";
 | 
			
		||||
 | 
			
		||||
        // array to gather clauses
 | 
			
		||||
        $clauses = [];
 | 
			
		||||
 | 
			
		||||
        // loop over centers
 | 
			
		||||
        foreach ($this->authorizationHelper->getReachableCenters(
 | 
			
		||||
            $this->security->getUser(), new Role(ActivityVoter::SEE_DETAILS)) as $center) {
 | 
			
		||||
 | 
			
		||||
            if (FALSE === \in_array($center, $centers)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // fill center parameter
 | 
			
		||||
            $parameters[] = $center->getId();
 | 
			
		||||
 | 
			
		||||
            // we loop over circles
 | 
			
		||||
            $circles = $this->authorizationHelper->getReachableCircles(
 | 
			
		||||
                $this->security->getUser(), new Role(ActivityVoter::SEE_DETAILS), $center);
 | 
			
		||||
            $circleIds = [];
 | 
			
		||||
 | 
			
		||||
            foreach ($circles as $circle) {
 | 
			
		||||
                $parameters[] = $circleIds[] = $circle->getId();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $clauses[] = \strtr(
 | 
			
		||||
                $clause,
 | 
			
		||||
                [
 | 
			
		||||
                    '{person}' => $person->getTableName(),
 | 
			
		||||
                    '{center_id}' => $personFkCenter,
 | 
			
		||||
                    '{task}' => $singleTask->getSchemaName().".".$singleTask->getTableName(),
 | 
			
		||||
                    '{circle}' => $taskFkCircle,
 | 
			
		||||
                    '{circle_ids}' => \implode(', ', \array_fill(0, count($circleIds), '?'))
 | 
			
		||||
                ]
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (0 === \count($clauses)) {
 | 
			
		||||
            return [ 'FALSE = TRUE' , [] ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            \implode(' OR ', $clauses),
 | 
			
		||||
            $parameters
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getWhereClauseForPerson(Person $personArg): array
 | 
			
		||||
    {
 | 
			
		||||
        $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class);
 | 
			
		||||
        $singleTask = $this->em->getClassMetadata(SingleTask::class);
 | 
			
		||||
        $person = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $eventFkTask = $taskEvent->getAssociationMapping('task')['joinColumns'][0]['name'];
 | 
			
		||||
        $taskFkPerson = $singleTask->getAssociationMapping('person')['joinColumns'][0]['name'];
 | 
			
		||||
        $personPk = $singleTask->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName'];
 | 
			
		||||
        $taskFkCircle = $singleTask->getAssociationMapping('circle')['joinColumns'][0]['name'];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // the parameters
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
 | 
			
		||||
        // the clause that we will fill
 | 
			
		||||
        $clause = "{person}.{person_id} = ? AND {task}.{circle} IN ({circle_ids})";
 | 
			
		||||
 | 
			
		||||
        // person is the first parameter
 | 
			
		||||
        $parameters[] = $personArg->getId();
 | 
			
		||||
 | 
			
		||||
        // we loop over circles
 | 
			
		||||
        $circles = $this->authorizationHelper->getReachableCircles(
 | 
			
		||||
            $this->security->getUser(), new Role(ActivityVoter::SEE_DETAILS), $personArg->getCenter());
 | 
			
		||||
 | 
			
		||||
        if (0 === count($circles)) {
 | 
			
		||||
            // go fast to block access to every tasks
 | 
			
		||||
            return [ "FALSE = TRUE", [] ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($circles as $circle) {
 | 
			
		||||
            $parameters[] = $circleIds[] = $circle->getId();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            \strtr(
 | 
			
		||||
                $clause,
 | 
			
		||||
                [
 | 
			
		||||
                    '{person}' => $person->getTableName(),
 | 
			
		||||
                    '{person_id}' => $person->getColumnName('id'),
 | 
			
		||||
                    '{task}' => $singleTask->getSchemaName().".".$singleTask->getTableName(),
 | 
			
		||||
                    '{circle}' => $taskFkCircle,
 | 
			
		||||
                    '{circle_ids}' => \implode(', ', \array_fill(0, count($circleIds), '?'))
 | 
			
		||||
                ]
 | 
			
		||||
            ),
 | 
			
		||||
            $parameters
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getFromClause(string $context)
 | 
			
		||||
    {
 | 
			
		||||
        $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class);
 | 
			
		||||
        $singleTask = $this->em->getClassMetadata(SingleTask::class);
 | 
			
		||||
        $person = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $eventFkTask = $taskEvent->getAssociationMapping('task')['joinColumns'][0]['name'];
 | 
			
		||||
        $taskFkPerson = $singleTask->getAssociationMapping('person')['joinColumns'][0]['name'];
 | 
			
		||||
        $personPk = $singleTask->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName'];
 | 
			
		||||
 | 
			
		||||
        $from = "{single_task_event} ".
 | 
			
		||||
            "JOIN {single_task} ON {single_task}.{task_pk} = {single_task_event}.{event_fk_task} ".
 | 
			
		||||
            "JOIN {person} ON {single_task}.{task_person_fk} = {person}.{person_pk}";
 | 
			
		||||
 | 
			
		||||
        return \strtr(
 | 
			
		||||
            $from,
 | 
			
		||||
            [
 | 
			
		||||
                '{single_task}' => sprintf('%s.%s', $singleTask->getSchemaName(), $singleTask->getTableName()),
 | 
			
		||||
                '{single_task_event}' => sprintf('%s.%s', $taskEvent->getSchemaName(), $taskEvent->getTableName()),
 | 
			
		||||
                '{task_pk}' => $singleTask->getColumnName('id'),
 | 
			
		||||
                '{event_fk_task}' => $eventFkTask, 
 | 
			
		||||
                '{person}' => $person->getTableName(),
 | 
			
		||||
                '{task_person_fk}' => $taskFkPerson,
 | 
			
		||||
                '{person_pk}' => $personPk 
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    } 
 | 
			
		||||
    
 | 
			
		||||
    public function getEntities(array $ids) 
 | 
			
		||||
@@ -147,10 +246,11 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
 | 
			
		||||
        $transition = $this->getTransitionByName($entity->getTransition(), $workflow);
 | 
			
		||||
        
 | 
			
		||||
        return [
 | 
			
		||||
            'template' => 'ChillTaskBundle:Timeline:single_task_transition_person_context.html.twig',
 | 
			
		||||
            'template' => 'ChillTaskBundle:Timeline:single_task_transition.html.twig',
 | 
			
		||||
            'template_data' => [ 
 | 
			
		||||
                'person' => $args['person'],
 | 
			
		||||
                'context' => $context,
 | 
			
		||||
                'event' => $entity,
 | 
			
		||||
                'task' => $entity->getTask(),
 | 
			
		||||
                'transition' => $transition
 | 
			
		||||
            ]
 | 
			
		||||
        ];
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,11 @@ services:
 | 
			
		||||
            $em: '@Doctrine\ORM\EntityManagerInterface'
 | 
			
		||||
            $registry: '@Symfony\Component\Workflow\Registry'
 | 
			
		||||
            $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
 | 
			
		||||
            $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
 | 
			
		||||
            $security: '@Symfony\Component\Security\Core\Security'
 | 
			
		||||
        public: true
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: 'chill.timeline', context: 'person' }
 | 
			
		||||
            - { name: 'chill.timeline', context: 'center' }
 | 
			
		||||
            
 | 
			
		||||
    Chill\TaskBundle\Timeline\SingleTaskTaskLifeCycleEventTimelineProvider:
 | 
			
		||||
        arguments:
 | 
			
		||||
 
 | 
			
		||||
 Submodule tests/app updated: f15fff5f2a...8e74ea90b1
									
								
							
		Reference in New Issue
	
	Block a user