Compare commits

...

67 Commits

Author SHA1 Message Date
915a2e7284 Merge remote-tracking branch 'origin/dune-risky' into dune-risky 2021-07-13 16:59:38 +02:00
cb99074d1f fix custom field wich could not be saved 2021-07-13 16:58:17 +02:00
cfd9f1bab1 replace call to entity by class FQDN 2021-06-02 00:46:55 +02:00
358410cde1 Remove dead code 2021-05-26 19:44:58 +00:00
06c74ed5ed add menu into section 2021-05-26 21:14:42 +02:00
25c986cc61 fix rendering for timeline entries in center context 2021-05-26 18:10:51 +02:00
cad8174333 apply timelineSingleQuery on events 2021-05-26 18:05:02 +02:00
75c586fbf8 fix documentation for timeline 2021-05-26 17:58:58 +02:00
2cb9dfc250 add tasks to global timeline 2021-05-26 17:50:54 +02:00
5350a09951 apply timeline for report 2021-05-25 19:07:09 +02:00
9b1a66c992 fix query in timeline in activity and person bundle 2021-05-25 09:48:19 +02:00
74541f360b fix activity timeline using TimelineSingleQuery 2021-05-24 20:26:33 +02:00
ea477a9842 add accompanying period opening/closing to global timeline 2021-05-17 13:24:50 +02:00
73653744d7 Prepare for deprecation of class Role, and add method to filter centers 2021-05-17 13:23:58 +02:00
c3ef8d112c first impl for global timeline: apply on activities 2021-05-14 16:25:56 +02:00
Pol Dellaiera
8c98f2cf6e Update .gitignore file. 2021-05-07 09:04:29 +02:00
e1c8278f71 Merge branch 'fix-return-type' into 'master'
Fix return type of Person::getAddresses().

See merge request Chill-Projet/chill-bundles!39
2021-05-06 15:05:35 +00:00
Pol Dellaiera
ed3f46ce7f Fix return type. 2021-05-06 17:01:11 +02:00
7503c845df Merge branch 'dev-undefined-index' into 'master'
fix undefined index in person edit form

See merge request Chill-Projet/chill-bundles!26
2021-05-06 13:47:13 +00:00
3686a294d3 fix return type for person::getAddresses 2021-05-06 13:57:44 +02:00
2db847ada2 Merge branch 'drupol-fix-person-addresses' into 'master'
Fix person addresses

See merge request Chill-Projet/chill-bundles!33
2021-05-06 07:14:54 +00:00
Pol Dellaiera
7819c1204c Merge branch '4-remove-unused-main-test-route' into 'master'
Issue #4: Remove obsolete route.

Closes #4

See merge request Chill-Projet/chill-bundles!31
2021-05-05 10:30:03 +00:00
Pol Dellaiera
e470a6a97e Merge remote-tracking branch 'tmp/fix-person-last-address' into drupol-fix-person-addresses 2021-05-05 11:24:18 +02:00
Pol Dellaiera
cd8c47449b Issue #4: Remove obsolete route. 2021-05-05 11:11:25 +02:00
d6e7fadb4f Merge branch 'issue_43_admin_prosoc' into 'master'
URGENT : Fix namespace pbm

See merge request Chill-Projet/chill-bundles!30
2021-05-05 07:00:05 +00:00
Marc Ducobu
2be1c08c44 Fix namespace pbm 2021-05-04 19:14:50 +02:00
Pol Dellaiera
d426d28ba0 Merge branch 'add-readme' into 'master'
docs: Add root README file.

See merge request Chill-Projet/chill-bundles!29
2021-05-04 13:14:14 +00:00
Pol Dellaiera
72e69fc0b3 docs: Add root README file. 2021-05-04 14:49:36 +02:00
52472b0ec3 Merge branch 'fix-tests-main' into 'master'
Fix tests for chill main bundle

See merge request Chill-Projet/chill-bundles!24
2021-04-29 22:35:47 +00:00
cab3b1059d ChillMainBundle: use localize function 2021-04-28 15:56:50 +02:00
4c82e65c1f Merge branch '14-correctif-creation-personne' into 'master'
Use injected EntityManager - fix property visibility.

See merge request Chill-Projet/chill-bundles!25
2021-04-28 08:36:34 +00:00
cf4d7df7ad Merge branch 'bootstrap-serializer' into 'master'
Bootstrap serializer

See merge request Chill-Projet/chill-bundles!17
2021-04-28 08:32:02 +00:00
86d13410c3 Merge branch 'bootstrap-serializer' into fix-tests-main 2021-04-28 10:29:53 +02:00
19fdf2a503 Add test for AccompanyingCourseController 2021-04-28 10:28:10 +02:00
Pol Dellaiera
5448238697 Set final keyword - remove redundant information in phpdoc. 2021-04-28 07:21:19 +02:00
Pol Dellaiera
c5250a1059 Use injected EntityManager - fix property visibility. 2021-04-27 23:07:59 +02:00
b2c1a7b8de Merge remote-tracking branch 'origin/master' into fix-tests-main 2021-04-27 22:50:32 +02:00
49c6915c0a Merge branch 'vendee_27' into 'master'
Vendee 27

See merge request Chill-Projet/chill-bundles!21
2021-04-27 20:49:59 +00:00
b4583fc6dc improve serializers 2021-04-27 22:48:48 +02:00
f7c508939c Add test for AccompanyingCourseController 2021-04-27 22:40:00 +02:00
a2160bef7d WIP: first implementation for test 2021-04-27 19:33:34 +02:00
8641d6bdce try to fix correct values for redis 2021-04-27 18:06:58 +02:00
5f4d513aa6 try to connect to redis in gitlab-ci 2021-04-27 17:57:09 +02:00
f47b15de39 try to fix .gitlab-ci with correct redis parameters 2021-04-27 17:52:12 +02:00
7426dc02cf currently restrict to chill main test suite 2021-04-27 17:18:46 +02:00
a570160aed update chill app to working commit 2021-04-27 17:18:26 +02:00
6e81ef0065 tests: update chill-app subprojet ref 2021-04-27 17:09:41 +02:00
e3cc76c689 tests/ChillPersonBundle: use type casting 2021-04-27 17:03:31 +02:00
b43f43fa0b ChillPersonBundle: persist new Persons 2021-04-27 16:53:57 +02:00
7596bd5a06 fix tests for chill main 2021-04-27 16:33:44 +02:00
f02d6df262 tests/ChillPersonBundle: in sf4 container is a static method of KernelTestCase 2021-04-27 15:15:41 +02:00
e4e2138765 tests/ChillPersonBundle: replace testGenderIsNull with crafted form as html input now prevents user form not selecting a gender 2021-04-27 15:15:41 +02:00
395735e6f7 tests/ChillPersonBundle: remove broken obsolete testFirstnameTooLong, html input and doctrine contrains are working 2021-04-27 15:15:41 +02:00
2c96c02261 tests/ChillPersonBundle: the gender input has three options: man, women and undefined 2021-04-27 15:15:41 +02:00
53813f8f29 tests/ChillPersonBundle: use mutualized getClientAuthenticated() 2021-04-27 15:15:41 +02:00
Marc Ducobu
b8ef0d28f5 Migration for AccompanyingPeriodWork, AccompanyongPeriodWokGoal & SocialWork/Evaluation 2021-04-27 15:15:02 +02:00
Marc Ducobu
e973c4013f Correct namespace 2021-04-27 15:11:31 +02:00
Marc Ducobu
0f9a395dfc Entites for AccomanyingPeriodWork, AccomanyingPeriodWorkGoal & Social/WorkEvaluation 2021-04-27 15:10:56 +02:00
Marc Ducobu
38ac3badef Integration rmq Julien 2021-04-27 11:24:26 +02:00
Marc Ducobu
cda32fb925 Pbm migration double 2021-04-26 17:19:10 +02:00
Marc Ducobu
58cb34f39b Creation of entities : SocialIssue / Social Action / Result / Goal 2021-04-26 17:09:54 +02:00
66426f5102 serializer on accompanying course
Two new routes:

* `GET /{_locale}/person/api/1.0/accompanying-course/{parcours_id}/show.json`: get a json representation for a course
* `POST /{_locale}/person/api/1.0/accompanying-course/{parcours_id}/participation.json`:
add a particitipation to course. Usage:

    `curl -v --cookie "PHPSESSID=fed98aa23e40cb36e630f84155aea3bb;" -X
POST --data '{ "id": 481 }'
http://localhost:8001/fr/person/api/1.0/accompanying-course/270/participation.json`

    Will add the person with id "481" to the course.
2021-04-26 17:01:22 +02:00
Pol Dellaiera
03243605da Remove unrelated code style change. 2021-04-02 11:18:52 +02:00
Pol Dellaiera
48e2d2ceab Do not use DateTimeImmutable. 2021-04-02 11:18:48 +02:00
Pol Dellaiera
777fb25860 tests: Add missing test based on review's feedback. 2021-04-02 11:12:50 +02:00
Pol Dellaiera
03601b9707 Update Person::getLastAddress() based on feedback. 2021-04-02 11:11:24 +02:00
c205bbddd3 writing test to reproduce bug 2021-03-08 10:26:12 +01:00
103 changed files with 3880 additions and 1071 deletions

3
.gitignore vendored
View File

@@ -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 ###

View File

@@ -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
View 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

View File

@@ -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() ]
]);
}
//....

View File

@@ -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>

View File

@@ -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(

View File

@@ -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];
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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");
}

View File

@@ -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' }

View File

@@ -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'

View File

@@ -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))

View File

@@ -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
);
}
}
}

View File

@@ -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
)
);
}
}

View File

@@ -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.'%']);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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'), [

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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(

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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(

View File

@@ -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() {

View File

@@ -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;
}
}
}

View File

@@ -50,7 +50,7 @@ class AuthorizationHelperTest extends KernelTestCase
*/
private function getAuthorizationHelper()
{
return static::$kernel->getContainer()
return static::$container
->get('chill.main.security.authorization.helper')
;
}

View File

@@ -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);

View File

@@ -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"
);
}
}

View File

@@ -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]);
}
}
}
}

View File

@@ -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
));

View 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;
}
}

View File

@@ -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

View File

@@ -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 }

View 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 }

View File

@@ -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'

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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)
{
}
}

View File

@@ -39,22 +39,16 @@ use Chill\MainBundle\Search\SearchProvider;
use Chill\PersonBundle\Repository\PersonRepository;
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;
@@ -65,35 +59,39 @@ class PersonController extends AbstractController
protected $eventDispatcher;
/**
*
* @var PersonRepository;
*/
protected $personRepository;
/**
*
* @var ConfigPersonAltNamesHelper
*/
protected $configPersonAltNameHelper;
/**
* @var EntityManagerInterface
*/
private $em;
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* @var ValidatorInterface
*/
private $validator;
public function __construct(
public function __construct(
SimilarPersonMatcher $similarPersonMatcher,
TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher,
PersonRepository $personRepository,
ConfigPersonAltNamesHelper $configPersonAltNameHelper,
LoggerInterface $logger,
ValidatorInterface $validator
ValidatorInterface $validator,
EntityManagerInterface $em
) {
$this->similarPersonMatcher = $similarPersonMatcher;
$this->translator = $translator;
@@ -102,14 +100,14 @@ class PersonController extends AbstractController
$this->personRepository = $personRepository;
$this->logger = $logger;
$this->validator = $validator;
$this->em = $em;
}
public function getCFGroup()
{
$cFGroup = null;
$em = $this->getDoctrine()->getManager();
$cFDefaultGroup = $em->getRepository("ChillCustomFieldsBundle:CustomFieldsDefaultGroup")
$cFDefaultGroup = $this->em->getRepository("ChillCustomFieldsBundle:CustomFieldsDefaultGroup")
->findOneByEntity("Chill\PersonBundle\Entity\Person");
if($cFDefaultGroup) {
@@ -198,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()
@@ -343,6 +340,8 @@ class PersonController extends AbstractController
$this->logger->info('Person created without errors');
}
$this->em->persist($person);
$alternatePersons = $this->similarPersonMatcher
->matchPerson($person);
@@ -391,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())));

View File

@@ -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;
}

View File

@@ -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
*/

View File

@@ -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();
}
);
}
}

View File

@@ -0,0 +1,309 @@
<?php
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
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;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=AccompanyingPeriodWorkRepository::class)
* @ORM\Table(name="chill_person_accompanying_period_work")
*/
class AccompanyingPeriodWork
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="text")
*/
private $note;
/**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
*/
private $accompanyingPeriod;
/**
* @ORM\ManyToOne(targetEntity=SocialAction::class)
*/
private $socialAction;
/**
* @ORM\Column(type="datetime")
*/
private $createdAt;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
*/
private $createdBy;
/**
* @ORM\Column(type="datetime")
*/
private $startDate;
/**
* @ORM\Column(type="datetime")
*/
private $endDate;
/**
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
*
* In schema : traitant
*/
private $handlingThierParty;
/**
* @ORM\Column(type="boolean")
*/
private $createdAutomatically;
/**
* @ORM\Column(type="text")
*/
private $createdAutomaticallyReason;
/**
* @ORM\OneToMany(targetEntity=AccompanyingPeriodWorkGoal::class, mappedBy="accompanyingPeriodWork")
*/
private $goals;
/**
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorks")
* @ORM\JoinTable(name="chill_person_accompanying_period_work_result")
*/
private $results;
/**
* @ORM\ManyToMany(targetEntity=ThirdParty::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_third_party")
*
* In schema : intervenants
*/
private $thirdParties;
public function __construct()
{
$this->goals = new ArrayCollection();
$this->results = new ArrayCollection();
$this->thirdParties = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNote(): ?string
{
return $this->note;
}
public function setNote(string $note): self
{
$this->note = $note;
return $this;
}
public function getAccompanyingPeriod(): ?AccompanyingPeriod
{
return $this->accompanyingPeriod;
}
public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self
{
$this->accompanyingPeriod = $accompanyingPeriod;
return $this;
}
public function getSocialAction(): ?SocialAction
{
return $this->socialAction;
}
public function setSocialAction(?SocialAction $socialAction): self
{
$this->socialAction = $socialAction;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getCreatedBy(): ?User
{
return $this->createdBy;
}
public function setCreatedBy(?User $createdBy): self
{
$this->createdBy = $createdBy;
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->startDate;
}
public function setStartDate(\DateTimeInterface $startDate): self
{
$this->startDate = $startDate;
return $this;
}
public function getEndDate(): ?\DateTimeInterface
{
return $this->endDate;
}
public function setEndDate(\DateTimeInterface $endDate): self
{
$this->endDate = $endDate;
return $this;
}
public function getHandlingThierParty(): ?ThirdParty
{
return $this->handlingThierParty;
}
public function setHandlingThierParty(?ThirdParty $handlingThierParty): self
{
$this->handlingThierParty = $handlingThierParty;
return $this;
}
public function getCreatedAutomatically(): ?bool
{
return $this->createdAutomatically;
}
public function setCreatedAutomatically(bool $createdAutomatically): self
{
$this->createdAutomatically = $createdAutomatically;
return $this;
}
public function getCreatedAutomaticallyReason(): ?string
{
return $this->createdAutomaticallyReason;
}
public function setCreatedAutomaticallyReason(string $createdAutomaticallyReason): self
{
$this->createdAutomaticallyReason = $createdAutomaticallyReason;
return $this;
}
/**
* @return Collection|AccompanyingPeriodWorkGoal[]
*/
public function getGoals(): Collection
{
return $this->goals;
}
public function addGoal(AccompanyingPeriodWorkGoal $goal): self
{
if (!$this->goals->contains($goal)) {
$this->goals[] = $goal;
$goal->setAccompanyingPeriodWork2($this);
}
return $this;
}
public function removeGoal(AccompanyingPeriodWorkGoal $goal): self
{
if ($this->goals->removeElement($goal)) {
// set the owning side to null (unless already changed)
if ($goal->getAccompanyingPeriodWork2() === $this) {
$goal->setAccompanyingPeriodWork2(null);
}
}
return $this;
}
/**
* @return Collection|Result[]
*/
public function getResults(): Collection
{
return $this->results;
}
public function addResult(Result $result): self
{
if (!$this->results->contains($result)) {
$this->results[] = $result;
}
return $this;
}
public function removeResult(Result $result): self
{
$this->results->removeElement($result);
return $this;
}
/**
* @return Collection|ThirdParty[]
*/
public function getThirdParties(): Collection
{
return $this->thirdParties;
}
public function addThirdParty(ThirdParty $thirdParty): self
{
if (!$this->thirdParties->contains($thirdParty)) {
$this->thirdParties[] = $thirdParty;
}
return $this;
}
public function removeThirdParty(ThirdParty $thirdParty): self
{
$this->thirdParties->removeElement($thirdParty);
return $this;
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
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;
/**
* @ORM\Entity(repositoryClass=AccompanyingPeriodWorkGoalRepository::class)
* @ORM\Table(name="chill_person_accompanying_period_work_goal")
*/
class AccompanyingPeriodWorkGoal
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="text")
*/
private $note;
/**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriodWork::class, inversedBy="goals")
*/
private $accompanyingPeriodWork;
/**
* @ORM\ManyToOne(targetEntity=Goal::class)
*/
private $goal;
/**
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorkGoals")
* @ORM\JoinTable(name="chill_person_accompanying_period_work_goal_result")
*/
private $results;
public function __construct()
{
$this->results = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNote(): ?string
{
return $this->note;
}
public function setNote(string $note): self
{
$this->note = $note;
return $this;
}
public function getAccompanyingPeriodWork(): ?AccompanyingPeriodWork
{
return $this->accompanyingPeriodWork;
}
public function setAccompanyingPeriodWork(?AccompanyingPeriodWork $accompanyingPeriodWork): self
{
$this->accompanyingPeriodWork = $accompanyingPeriodWork;
return $this;
}
public function getGoal(): ?Goal
{
return $this->goal;
}
public function setGoal(?Goal $goal): self
{
$this->goal = $goal;
return $this;
}
/**
* @return Collection|Result[]
*/
public function getResults(): Collection
{
return $this->results;
}
public function addResult(Result $result): self
{
if (!$this->results->contains($result)) {
$this->results[] = $result;
}
return $this;
}
public function removeResult(Result $result): self
{
$this->results->removeElement($result);
return $this;
}
}

View File

@@ -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);
}
/**

View File

@@ -0,0 +1,93 @@
<?php
namespace Chill\PersonBundle\Entity\SocialWork;
use Chill\PersonBundle\Repository\SocialWork\EvaluationRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=EvaluationRepository::class)
* @ORM\Table(name="chill_person_social_work_evaluation")
*/
class Evaluation
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="json")
*/
private $title = [];
/**
* @ORM\Column(type="dateinterval")
*/
private $delay;
/**
* @ORM\Column(type="dateinterval")
*/
private $notificationDelay;
/**
* @ORM\ManyToOne(targetEntity=SocialAction::class)
*/
private $socialAction;
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): array
{
return $this->title;
}
public function setTitle(array $title): self
{
$this->title = $title;
return $this;
}
public function getDelay(): ?\DateInterval
{
return $this->delay;
}
public function setDelay(\DateInterval $delay): self
{
$this->delay = $delay;
return $this;
}
public function getNotificationDelay(): ?\DateInterval
{
return $this->notificationDelay;
}
public function setNotificationDelay(\DateInterval $notificationDelay): self
{
$this->notificationDelay = $notificationDelay;
return $this;
}
public function getSocialAction(): ?SocialAction
{
return $this->socialAction;
}
public function setSocialAction(?SocialAction $socialAction): self
{
$this->socialAction = $socialAction;
return $this;
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Chill\PersonBundle\Entity\SocialWork;
use Chill\PersonBundle\Repository\SocialWork\GoalRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=GoalRepository::class)
* @ORM\Table(name="chill_person_social_work_goal")
*/
class Goal
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="json")
*/
private $title = [];
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $desactivationDate;
/**
* @ORM\ManyToMany(targetEntity=SocialAction::class, mappedBy="goals")
*/
private $socialActions;
/**
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="goals")
* @ORM\JoinTable(name="chill_person_social_work_goal_result")
*/
private $results;
public function __construct()
{
$this->socialActions = new ArrayCollection();
$this->results = new ArrayCollection();
}
public function getTitle(): array
{
return $this->title;
}
public function setTitle(array $title): self
{
$this->title = $title;
return $this;
}
public function getDesactivationDate(): ?\DateTimeInterface
{
return $this->desactivationDate;
}
public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self
{
$this->desactivationDate = $desactivationDate;
return $this;
}
/**
* @return Collection|SocialAction[]
*/
public function getSocialActions(): Collection
{
return $this->socialActions;
}
public function addSocialAction(SocialAction $socialAction): self
{
if (!$this->socialActions->contains($socialAction)) {
$this->socialActions[] = $socialAction;
}
return $this;
}
public function removeSocialAction(SocialAction $socialAction): self
{
$this->socialActions->removeElement($socialAction);
return $this;
}
/**
* @return Collection|Result[]
*/
public function getResults(): Collection
{
return $this->results;
}
public function addResult(Result $result): self
{
if (!$this->results->contains($result)) {
$this->results[] = $result;
}
return $this;
}
public function removeResult(Result $result): self
{
$this->results->removeElement($result);
return $this;
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace Chill\PersonBundle\Entity\SocialWork;
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;
/**
* @ORM\Entity(repositoryClass=ResultRepository::class)
* @ORM\Table(name="chill_person_social_work_result")
*/
class Result
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="json")
*/
private $title = [];
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $desactivationDate;
/**
* @ORM\ManyToMany(targetEntity=SocialAction::class, mappedBy="results")
*/
private $socialActions;
/**
* @ORM\ManyToMany(targetEntity=Goal::class, mappedBy="results")
*/
private $goals;
/**
* @ORM\ManyToMany(targetEntity=AccompanyingPeriodWork::class, mappedBy="results")
*/
private $accompanyingPeriodWorks;
/**
* @ORM\ManyToMany(targetEntity=AccompanyingPeriodWorkGoal::class, mappedBy="results")
*/
private $accompanyingPeriodWorkGoals;
public function __construct()
{
$this->socialActions = new ArrayCollection();
$this->goals = new ArrayCollection();
$this->accompanyingPeriodWorks = new ArrayCollection();
$this->accompanyingPeriodWorkGoals = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): array
{
return $this->title;
}
public function setTitle(array $title): self
{
$this->title = $title;
return $this;
}
public function getDesactivationDate(): ?\DateTimeInterface
{
return $this->desactivationDate;
}
public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self
{
$this->desactivationDate = $desactivationDate;
return $this;
}
/**
* @return Collection|SocialAction[]
*/
public function getSocialActions(): Collection
{
return $this->socialActions;
}
public function addSocialAction(SocialAction $socialAction): self
{
if (!$this->socialActions->contains($socialAction)) {
$this->socialActions[] = $socialAction;
}
return $this;
}
public function removeSocialAction(SocialAction $socialAction): self
{
$this->socialActions->removeElement($socialAction);
return $this;
}
/**
* @return Collection|Goal[]
*/
public function getGoals(): Collection
{
return $this->goals;
}
public function addGoal(Goal $goal): self
{
if (!$this->goals->contains($goal)) {
$this->goals[] = $goal;
}
return $this;
}
public function removeGoal(Goal $goal): self
{
$this->goals->removeElement($goal);
return $this;
}
/**
* @return Collection|AccompanyingPeriodWork[]
*/
public function getAccompanyingPeriodWorks(): Collection
{
return $this->accompanyingPeriodWorks;
}
public function addAccompanyingPeriodWork(AccompanyingPeriodWork $accompanyingPeriod): self
{
if (!$this->accompanyingPeriodWorks->contains($accompanyingPeriod)) {
$this->accompanyingPeriodWorks[] = $accompanyingPeriod;
}
return $this;
}
public function removeAccompanyingPeriodWork(AccompanyingPeriodWork $accompanyingPeriod): self
{
$this->accompanyingPeriodWorks->removeElement($accompanyingPeriod);
return $this;
}
/**
* @return Collection|AccompanyingPeriodWorkGoal[]
*/
public function getAccompanyingPeriodWorkGoals(): Collection
{
return $this->accompanyingPeriodWorkGoals;
}
public function addAccompanyingPeriodWorkGoal(AccompanyingPeriodWorkGoal $accompanyingPeriodWorkGoal): self
{
if (!$this->accompanyingPeriodWorkGoals->contains($accompanyingPeriodWorkGoal)) {
$this->accompanyingPeriodWorkGoals[] = $accompanyingPeriodWorkGoal;
}
return $this;
}
public function removeAccompanyingPeriodWorkGoal(AccompanyingPeriodWorkGoal $accompanyingPeriodWorkGoal): self
{
$this->accompanyingPeriodWorkGoals->removeElement($accompanyingPeriodWorkGoal);
return $this;
}
}

View File

@@ -0,0 +1,214 @@
<?php
namespace Chill\PersonBundle\Entity\SocialWork;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=SocialActionRepository::class)
* @ORM\Table(name="chill_person_social_action")
*/
class SocialAction
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $desactivationDate;
/**
* @ORM\ManyToOne(targetEntity=SocialIssue::class, inversedBy="socialActions")
*/
private $issue;
/**
* @ORM\ManyToOne(targetEntity=SocialAction::class, inversedBy="children")
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity=SocialAction::class, mappedBy="parent")
*/
private $children;
/**
* @ORM\Column(type="dateinterval")
*/
private $defaultNotificationDelay;
/**
* @ORM\Column(type="json")
*/
private $title = [];
/**
* @ORM\ManyToMany(targetEntity=Goal::class, inversedBy="socialActions")
* @ORM\JoinTable(name="chill_person_social_action_goal")
*/
private $goals;
/**
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="socialActions")
* @ORM\JoinTable(name="chill_person_social_action_result")
*/
private $results;
public function __construct()
{
$this->children = new ArrayCollection();
$this->goals = new ArrayCollection();
$this->results = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getDesactivationDate(): ?\DateTimeInterface
{
return $this->desactivationDate;
}
public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self
{
$this->desactivationDate = $desactivationDate;
return $this;
}
public function getIssue(): ?SocialIssue
{
return $this->issue;
}
public function setIssue(?SocialIssue $issue): self
{
$this->issue = $issue;
return $this;
}
public function getParent(): ?self
{
return $this->parent;
}
public function setParent(?self $parent): self
{
$this->parent = $parent;
return $this;
}
/**
* @return Collection|self[]
*/
public function getChildren(): Collection
{
return $this->children;
}
public function addChild(self $child): self
{
if (!$this->children->contains($child)) {
$this->children[] = $child;
$child->setParent($this);
}
return $this;
}
public function removeChild(self $child): self
{
if ($this->children->removeElement($child)) {
// set the owning side to null (unless already changed)
if ($child->getParent() === $this) {
$child->setParent(null);
}
}
return $this;
}
public function getDefaultNotificationDelay(): ?\DateInterval
{
return $this->defaultNotificationDelay;
}
public function setDefaultNotificationDelay(\DateInterval $defaultNotificationDelay): self
{
$this->defaultNotificationDelay = $defaultNotificationDelay;
return $this;
}
public function getTitle(): array
{
return $this->title;
}
public function setTitle(array $title): self
{
$this->title = $title;
return $this;
}
/**
* @return Collection|Goal[]
*/
public function getGoals(): Collection
{
return $this->goals;
}
public function addGoal(Goal $goal): self
{
if (!$this->goals->contains($goal)) {
$this->goals[] = $goal;
}
return $this;
}
public function removeGoal(Goal $goal): self
{
$this->goals->removeElement($goal);
return $this;
}
/**
* @return Collection|Result[]
*/
public function getResults(): Collection
{
return $this->results;
}
public function addResult(Result $result): self
{
if (!$this->results->contains($result)) {
$this->results[] = $result;
}
return $this;
}
public function removeResult(Result $result): self
{
$this->results->removeElement($result);
return $this;
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace Chill\PersonBundle\Entity\SocialWork;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=SocialIssueRepository::class)
* @ORM\Table(name="chill_person_social_issue")
*/
class SocialIssue
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=SocialIssue::class, inversedBy="children")
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity=SocialIssue::class, mappedBy="parent")
*/
private $children;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $desactivationDate;
/**
* @ORM\Column(type="json")
*/
private $title = [];
/**
* @ORM\OneToMany(targetEntity=SocialAction::class, mappedBy="issue")
*/
private $socialActions;
public function __construct()
{
$this->children = new ArrayCollection();
$this->socialActions = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getParent(): ?self
{
return $this->parent;
}
public function setParent(?self $parent): self
{
$this->parent = $parent;
return $this;
}
/**
* @return Collection|self[]
*/
public function getChildren(): Collection
{
return $this->children;
}
public function addChild(self $child): self
{
if (!$this->children->contains($child)) {
$this->children[] = $child;
$child->setParent($this);
}
return $this;
}
public function removeChild(self $child): self
{
if ($this->children->removeElement($child)) {
// set the owning side to null (unless already changed)
if ($child->getParent() === $this) {
$child->setParent(null);
}
}
return $this;
}
public function getDesactivationDate(): ?\DateTimeInterface
{
return $this->desactivationDate;
}
public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self
{
$this->desactivationDate = $desactivationDate;
return $this;
}
public function getTitle(): array
{
return $this->title;
}
public function setTitle(array $title): self
{
$this->title = $title;
return $this;
}
/**
* @return Collection|SocialAction[]
*/
public function getSocialActions(): Collection
{
return $this->socialActions;
}
public function addSocialAction(SocialAction $socialAction): self
{
if (!$this->socialActions->contains($socialAction)) {
$this->socialActions[] = $socialAction;
$socialAction->setSocialIssue($this);
}
return $this;
}
public function removeSocialAction(SocialAction $socialAction): self
{
if ($this->socialActions->removeElement($socialAction)) {
// set the owning side to null (unless already changed)
if ($socialAction->getSocialIssue() === $this) {
$socialAction->setSocialIssue(null);
}
}
return $this;
}
}

View File

@@ -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;
}
}

View File

@@ -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(),
];
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method AccompanyingPeriodWorkGoal|null find($id, $lockMode = null, $lockVersion = null)
* @method AccompanyingPeriodWorkGoal|null findOneBy(array $criteria, array $orderBy = null)
* @method AccompanyingPeriodWorkGoal[] findAll()
* @method AccompanyingPeriodWorkGoal[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class AccompanyingPeriodWorkGoalRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, AccompanyingPeriodWorkGoal::class);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method AccompanyingPeriodWork|null find($id, $lockMode = null, $lockVersion = null)
* @method AccompanyingPeriodWork|null findOneBy(array $criteria, array $orderBy = null)
* @method AccompanyingPeriodWork[] findAll()
* @method AccompanyingPeriodWork[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class AccompanyingPeriodWorkRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, AccompanyingPeriodWork::class);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Evaluation|null find($id, $lockMode = null, $lockVersion = null)
* @method Evaluation|null findOneBy(array $criteria, array $orderBy = null)
* @method Evaluation[] findAll()
* @method Evaluation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class EvaluationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Evaluation::class);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Goal|null find($id, $lockMode = null, $lockVersion = null)
* @method Goal|null findOneBy(array $criteria, array $orderBy = null)
* @method Goal[] findAll()
* @method Goal[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class GoalRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Goal::class);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\Result;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Result|null find($id, $lockMode = null, $lockVersion = null)
* @method Result|null findOneBy(array $criteria, array $orderBy = null)
* @method Result[] findAll()
* @method Result[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ResultRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Result::class);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method SocialAction|null find($id, $lockMode = null, $lockVersion = null)
* @method SocialAction|null findOneBy(array $criteria, array $orderBy = null)
* @method SocialAction[] findAll()
* @method SocialAction[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class SocialActionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, SocialAction::class);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method SocialIssue|null find($id, $lockMode = null, $lockVersion = null)
* @method SocialIssue|null findOneBy(array $criteria, array $orderBy = null)
* @method SocialIssue[] findAll()
* @method SocialIssue[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class SocialIssueRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, SocialIssue::class);
}
}

View File

@@ -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>

View File

@@ -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 }}&nbsp;:</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>

View File

@@ -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 }}&nbsp;:</dd>
<dt>
<ul>
{% for p in period.participations %}
<li>{{ 'Since %date%'|trans( {'%date%': p.startDate|format_date("long") } ) }}&nbsp;: {{ p.person|chill_entity_render_box({ 'addLink': true }) }}</li>
{% endfor %}
</ul>
</dt>
</div>
</div>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 = '';

View File

@@ -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++;
}
}
}

View File

@@ -24,12 +24,14 @@ namespace Chill\PersonBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\DomCrawler\Form;
use Chill\MainBundle\Test\PrepareClientTrait;
/**
* Test creation and deletion for persons
*/
class PersonControllerCreateTest extends WebTestCase
{
use PrepareClientTrait;
const FIRSTNAME_INPUT = 'chill_personbundle_person_creation[firstName]';
const LASTNAME_INPUT = "chill_personbundle_person_creation[lastName]";
@@ -39,20 +41,12 @@ class PersonControllerCreateTest extends WebTestCase
const CENTER_INPUT = "chill_personbundle_person_creation[center]";
const LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta.Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq.";
/**
* return an authenticated client, useful for submitting form
*
* @return \Symfony\Component\BrowserKit\Client
*/
private function getAuthenticatedClient($username = 'center a_social')
public function setUp()
{
return static::createClient(array(), array(
'PHP_AUTH_USER' => $username,
'PHP_AUTH_PW' => 'password',
));
$this->client = $this::getClientAuthenticated();
}
/**
*
* @param Form $creationForm
@@ -76,8 +70,8 @@ class PersonControllerCreateTest extends WebTestCase
*/
public function testAddAPersonPage()
{
$client = $this->getAuthenticatedClient();
$client = $this->client;
$crawler = $client->request('GET', '/fr/person/new');
$this->assertTrue($client->getResponse()->isSuccessful(),
@@ -99,9 +93,9 @@ class PersonControllerCreateTest extends WebTestCase
$genderType = $form->get(self::GENDER_INPUT);
$this->assertEquals('radio', $genderType->getType(),
'The gender input has two radio button: man and women');
$this->assertEquals(2, count($genderType->availableOptionValues()),
'The gender input has two radio button: man and women');
'The gender input has radio buttons');
$this->assertEquals(3, count($genderType->availableOptionValues()),
'The gender input has three options: man, women and undefined');
$this->assertTrue(in_array('man', $genderType->availableOptionValues()),
'gender has "homme" option');
$this->assertTrue(in_array('woman', $genderType->availableOptionValues()),
@@ -120,45 +114,14 @@ class PersonControllerCreateTest extends WebTestCase
* @param Form $form
* @depends testAddAPersonPage
*/
public function testFirstnameTooLong(Form $form)
public function testForgedNullGender(Form $form)
{
$client = $this->getAuthenticatedClient();
$this->fillAValidCreationForm($form);
$form->get(self::FIRSTNAME_INPUT)->setValue(mb_substr(self::LONG_TEXT, 0, 256));
$crawler = $client->submit($form);
$this->assertEquals(1, $crawler->filter('.error')->count(),
"An error message is shown if we fill more than 255 characters in firstname");
}
/**
*
* @param Form $form
* @depends testAddAPersonPage
*/
public function testLastnameTooLong(Form $form)
{
$this->fillAValidCreationForm($form);
$form->get(self::LASTNAME_INPUT)->setValue(mb_substr(self::LONG_TEXT, 0, 256));
$crawler = $this->getAuthenticatedClient()->submit($form);
$this->assertEquals(1, $crawler->filter('.error')->count(),
"An error message is shown if we fill more than 255 characters in lastname");
}
/**
*
* @param Form $form
* @depends testAddAPersonPage
*/
public function testGenderIsNull(Form $form)
{
$this->fillAValidCreationForm($form);
$form->get(self::GENDER_INPUT)->disableValidation()->setValue(NULL);
$crawler = $this->getAuthenticatedClient()->submit($form);
$this->assertEquals(1, $crawler->filter('.error')->count(),
'A message is shown if gender is not set');
$form->get(self::FIRSTNAME_INPUT)->setValue('john');
$form->get(self::LASTNAME_INPUT)->setValue('doe');
$date = new \DateTime('1947-02-01');
$form->get(self::BIRTHDATE_INPUT)->setValue($date->format('d-m-Y'));
$this->client->submit($form);
$this->assertResponseStatusCodeSame(500);
}
/**
@@ -171,10 +134,10 @@ class PersonControllerCreateTest extends WebTestCase
public function testValidForm(Form $form)
{
$this->fillAValidCreationForm($form);
$client = $this->getAuthenticatedClient();
$client = $this->client;
$client->submit($form);
$this->assertTrue($client->getResponse()->isRedirect(),
$this->assertTrue((bool)$client->getResponse()->isRedirect(),
"a valid form redirect to url /{_locale}/person/{personId}/general/edit");
$client->followRedirect();
@@ -198,7 +161,7 @@ class PersonControllerCreateTest extends WebTestCase
*/
public function testPersonViewAccessible($personId)
{
$client = $this->getAuthenticatedClient();
$client = $this->client;
$client->request('GET', '/fr/person/'.$personId.'/general');
$this->assertTrue($client->getResponse()->isSuccessful(),
@@ -212,7 +175,7 @@ class PersonControllerCreateTest extends WebTestCase
*/
public function testValidFormWithMultiCenterUser()
{
$client = $this->getAuthenticatedClient('multi_center');
$client = $this->getClientAuthenticated('multi_center');
$crawler = $client->request('GET', '/fr/person/new');
@@ -242,7 +205,7 @@ class PersonControllerCreateTest extends WebTestCase
public function testReviewExistingDetectionInversedLastNameWithFirstName()
{
$client = $this->getAuthenticatedClient();
$client = $this->client;
$crawler = $client->request('GET', '/fr/person/new');

View File

@@ -24,6 +24,7 @@ namespace Chill\PersonBundle\Tests\Controller;
use Chill\PersonBundle\Entity\Person;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Chill\MainBundle\Test\PrepareClientTrait;
/**
* Test the edition of persons
@@ -34,6 +35,8 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
*/
class PersonControllerUpdateTest extends WebTestCase
{
use PrepareClientTrait;
/** @var \Doctrine\ORM\EntityManagerInterface The entity manager */
private $em;
@@ -71,10 +74,7 @@ class PersonControllerUpdateTest extends WebTestCase
$this->editUrl = '/en/person/'.$this->person->getId().'/general/edit';
$this->viewUrl = '/en/person/'.$this->person->getId().'/general';
$this->client = static::createClient(array(), array(
'PHP_AUTH_USER' => 'center a_social',
'PHP_AUTH_PW' => 'password',
));
$this->client = $this->getClientAuthenticated();
}
/**

View File

@@ -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));
}
}

View File

@@ -36,12 +36,6 @@ class PickPersonTypeTest extends KernelTestCase
*/
protected $user;
/**
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
*
* @var \Symfony\Component\Form\FormFactoryInterface
@@ -52,16 +46,14 @@ class PickPersonTypeTest extends KernelTestCase
{
self::bootKernel();
$this->container = self::$kernel->getContainer();
$this->user = $this->container->get('doctrine.orm.entity_manager')
$this->user = self::$container->get('doctrine.orm.entity_manager')
->getRepository('ChillMainBundle:User')
->findOneBy(array('username' => 'multi_center'));
$this->formFactory = $this->container->get('form.factory');
$this->formFactory = self::$container->get('form.factory');
$token = (new UsernamePasswordToken($this->user, 'password', 'firewall'));
$this->container->get('security.token_storage')
self::$container->get('security.token_storage')
->setToken($token);
}

View File

@@ -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]
);
}
}

View File

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

View File

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

View File

@@ -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

View File

@@ -8,6 +8,7 @@ services:
$configPersonAltNameHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper'
$logger: '@Psr\Log\LoggerInterface'
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'
$em: '@Doctrine\ORM\EntityManagerInterface'
tags: ['controller.service_arguments']
Chill\PersonBundle\Controller\TimelinePersonController:
@@ -15,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:
@@ -40,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']

View File

@@ -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 ]

View 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 }

View File

@@ -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'

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Create Social action, Social Issue, Work Goal and Work Result
*/
final class Version20210426145930 extends AbstractMigration
{
public function getDescription() : string
{
return 'Create Social action, Social Issue, Work Goal and Work Result';
}
public function up(Schema $schema) : void
{
$this->addSql('CREATE SEQUENCE chill_person_social_action_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_person_social_issue_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_person_social_work_goal_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_person_social_work_result_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_person_social_action (id INT NOT NULL, issue_id INT DEFAULT NULL, parent_id INT DEFAULT NULL, desactivationDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, defaultNotificationDelay INTERVAL NOT NULL, title JSONB NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_B7ABFAB85E7AA58C ON chill_person_social_action (issue_id)');
$this->addSql('CREATE INDEX IDX_B7ABFAB8727ACA70 ON chill_person_social_action (parent_id)');
$this->addSql('COMMENT ON COLUMN chill_person_social_action.defaultNotificationDelay IS \'(DC2Type:dateinterval)\'');
$this->addSql('CREATE TABLE chill_person_social_action_goal (socialaction_id INT NOT NULL, goal_id INT NOT NULL, PRIMARY KEY(socialaction_id, goal_id))');
$this->addSql('CREATE INDEX IDX_163CA4DD3DC32179 ON chill_person_social_action_goal (socialaction_id)');
$this->addSql('CREATE INDEX IDX_163CA4DD667D1AFE ON chill_person_social_action_goal (goal_id)');
$this->addSql('CREATE TABLE chill_person_social_action_result (socialaction_id INT NOT NULL, result_id INT NOT NULL, PRIMARY KEY(socialaction_id, result_id))');
$this->addSql('CREATE INDEX IDX_CA98C58C3DC32179 ON chill_person_social_action_result (socialaction_id)');
$this->addSql('CREATE INDEX IDX_CA98C58C7A7B643 ON chill_person_social_action_result (result_id)');
$this->addSql('CREATE TABLE chill_person_social_issue (id INT NOT NULL, parent_id INT DEFAULT NULL, desactivationDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, title JSONB NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_7A484DAE727ACA70 ON chill_person_social_issue (parent_id)');
$this->addSql('CREATE TABLE chill_person_social_work_goal (id INT NOT NULL, title JSONB NOT NULL, desactivationDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE TABLE chill_person_social_work_goal_result (goal_id INT NOT NULL, result_id INT NOT NULL, PRIMARY KEY(goal_id, result_id))');
$this->addSql('CREATE INDEX IDX_F3BAEEA9667D1AFE ON chill_person_social_work_goal_result (goal_id)');
$this->addSql('CREATE INDEX IDX_F3BAEEA97A7B643 ON chill_person_social_work_goal_result (result_id)');
$this->addSql('CREATE TABLE chill_person_social_work_result (id INT NOT NULL, title JSONB NOT NULL, desactivationDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('ALTER TABLE chill_person_social_action ADD CONSTRAINT FK_B7ABFAB85E7AA58C FOREIGN KEY (issue_id) REFERENCES chill_person_social_issue (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_action ADD CONSTRAINT FK_B7ABFAB8727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_person_social_action (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_action_goal ADD CONSTRAINT FK_163CA4DD3DC32179 FOREIGN KEY (socialaction_id) REFERENCES chill_person_social_action (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_action_goal ADD CONSTRAINT FK_163CA4DD667D1AFE FOREIGN KEY (goal_id) REFERENCES chill_person_social_work_goal (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_action_result ADD CONSTRAINT FK_CA98C58C3DC32179 FOREIGN KEY (socialaction_id) REFERENCES chill_person_social_action (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_action_result ADD CONSTRAINT FK_CA98C58C7A7B643 FOREIGN KEY (result_id) REFERENCES chill_person_social_work_result (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_issue ADD CONSTRAINT FK_7A484DAE727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_person_social_issue (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_work_goal_result ADD CONSTRAINT FK_F3BAEEA9667D1AFE FOREIGN KEY (goal_id) REFERENCES chill_person_social_work_goal (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_work_goal_result ADD CONSTRAINT FK_F3BAEEA97A7B643 FOREIGN KEY (result_id) REFERENCES chill_person_social_work_result (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema) : void
{
$this->addSql('ALTER TABLE chill_person_social_action DROP CONSTRAINT FK_B7ABFAB8727ACA70');
$this->addSql('ALTER TABLE chill_person_social_action_goal DROP CONSTRAINT FK_163CA4DD3DC32179');
$this->addSql('ALTER TABLE chill_person_social_action_result DROP CONSTRAINT FK_CA98C58C3DC32179');
$this->addSql('ALTER TABLE chill_person_social_action DROP CONSTRAINT FK_B7ABFAB85E7AA58C');
$this->addSql('ALTER TABLE chill_person_social_issue DROP CONSTRAINT FK_7A484DAE727ACA70');
$this->addSql('ALTER TABLE chill_person_social_action_goal DROP CONSTRAINT FK_163CA4DD667D1AFE');
$this->addSql('ALTER TABLE chill_person_social_work_goal_result DROP CONSTRAINT FK_F3BAEEA9667D1AFE');
$this->addSql('ALTER TABLE chill_person_social_action_result DROP CONSTRAINT FK_CA98C58C7A7B643');
$this->addSql('ALTER TABLE chill_person_social_work_goal_result DROP CONSTRAINT FK_F3BAEEA97A7B643');
$this->addSql('DROP SEQUENCE chill_person_social_action_id_seq CASCADE');
$this->addSql('DROP SEQUENCE chill_person_social_issue_id_seq CASCADE');
$this->addSql('DROP SEQUENCE chill_person_social_work_goal_id_seq CASCADE');
$this->addSql('DROP SEQUENCE chill_person_social_work_result_id_seq CASCADE');
$this->addSql('DROP TABLE chill_person_social_action');
$this->addSql('DROP TABLE chill_person_social_action_goal');
$this->addSql('DROP TABLE chill_person_social_action_result');
$this->addSql('DROP TABLE chill_person_social_issue');
$this->addSql('DROP TABLE chill_person_social_work_goal');
$this->addSql('DROP TABLE chill_person_social_work_goal_result');
$this->addSql('DROP TABLE chill_person_social_work_result');
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Create tables for the entites AccomanyingPeriodWork, AccomanyingPeriodWorkGoal & Social/WorkEvaluation
*/
final class Version20210427125700 extends AbstractMigration
{
public function getDescription() : string
{
return 'Create tables for the entites AccomanyingPeriodWork, AccomanyingPeriodWorkGoal & Social/WorkEvaluation';
}
public function up(Schema $schema) : void
{
$this->addSql('CREATE SEQUENCE chill_person_accompanying_period_work_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_person_accompanying_period_work_goal_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_person_social_work_evaluation_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_person_accompanying_period_work (id INT NOT NULL, note TEXT NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, endDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, createdAutomatically BOOLEAN NOT NULL, createdAutomaticallyReason TEXT NOT NULL, accompanyingPeriod_id INT DEFAULT NULL, socialAction_id INT DEFAULT NULL, createdBy_id INT NOT NULL, handlingThierParty_id INT DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_B694FB3D7FA8EF0 ON chill_person_accompanying_period_work (accompanyingPeriod_id)');
$this->addSql('CREATE INDEX IDX_B694FB3BF32A3DA ON chill_person_accompanying_period_work (socialAction_id)');
$this->addSql('CREATE INDEX IDX_B694FB33174800F ON chill_person_accompanying_period_work (createdBy_id)');
$this->addSql('CREATE INDEX IDX_B694FB382B35EA7 ON chill_person_accompanying_period_work (handlingThierParty_id)');
$this->addSql('CREATE TABLE chill_person_accompanying_period_work_result (accompanyingperiodwork_id INT NOT NULL, result_id INT NOT NULL, PRIMARY KEY(accompanyingperiodwork_id, result_id))');
$this->addSql('CREATE INDEX IDX_46E95929B99F6060 ON chill_person_accompanying_period_work_result (accompanyingperiodwork_id)');
$this->addSql('CREATE INDEX IDX_46E959297A7B643 ON chill_person_accompanying_period_work_result (result_id)');
$this->addSql('CREATE TABLE chill_person_accompanying_period_work_third_party (accompanyingperiodwork_id INT NOT NULL, thirdparty_id INT NOT NULL, PRIMARY KEY(accompanyingperiodwork_id, thirdparty_id))');
$this->addSql('CREATE INDEX IDX_83B57B86B99F6060 ON chill_person_accompanying_period_work_third_party (accompanyingperiodwork_id)');
$this->addSql('CREATE INDEX IDX_83B57B86C7D3A8E6 ON chill_person_accompanying_period_work_third_party (thirdparty_id)');
$this->addSql('CREATE TABLE chill_person_accompanying_period_work_goal (id INT NOT NULL, goal_id INT DEFAULT NULL, note TEXT NOT NULL, accompanyingPeriodWork_id INT DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_911B88FEC55C1209 ON chill_person_accompanying_period_work_goal (accompanyingPeriodWork_id)');
$this->addSql('CREATE INDEX IDX_911B88FE667D1AFE ON chill_person_accompanying_period_work_goal (goal_id)');
$this->addSql('CREATE TABLE chill_person_accompanying_period_work_goal_result (accompanyingperiodworkgoal_id INT NOT NULL, result_id INT NOT NULL, PRIMARY KEY(accompanyingperiodworkgoal_id, result_id))');
$this->addSql('CREATE INDEX IDX_3E37D1F37B14AD03 ON chill_person_accompanying_period_work_goal_result (accompanyingperiodworkgoal_id)');
$this->addSql('CREATE INDEX IDX_3E37D1F37A7B643 ON chill_person_accompanying_period_work_goal_result (result_id)');
$this->addSql('CREATE TABLE chill_person_social_work_evaluation (id INT NOT NULL, title JSONB NOT NULL, delay INTERVAL NOT NULL, notificationDelay INTERVAL NOT NULL, socialAction_id INT DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_2E23F3FEBF32A3DA ON chill_person_social_work_evaluation (socialAction_id)');
$this->addSql('COMMENT ON COLUMN chill_person_social_work_evaluation.delay IS \'(DC2Type:dateinterval)\'');
$this->addSql('COMMENT ON COLUMN chill_person_social_work_evaluation.notificationDelay IS \'(DC2Type:dateinterval)\'');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work ADD CONSTRAINT FK_B694FB3D7FA8EF0 FOREIGN KEY (accompanyingPeriod_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work ADD CONSTRAINT FK_B694FB3BF32A3DA FOREIGN KEY (socialAction_id) REFERENCES chill_person_social_action (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work ADD CONSTRAINT FK_B694FB33174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work ADD CONSTRAINT FK_B694FB382B35EA7 FOREIGN KEY (handlingThierParty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_result ADD CONSTRAINT FK_46E95929B99F6060 FOREIGN KEY (accompanyingperiodwork_id) REFERENCES chill_person_accompanying_period_work (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_result ADD CONSTRAINT FK_46E959297A7B643 FOREIGN KEY (result_id) REFERENCES chill_person_social_work_result (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_third_party ADD CONSTRAINT FK_83B57B86B99F6060 FOREIGN KEY (accompanyingperiodwork_id) REFERENCES chill_person_accompanying_period_work (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_third_party ADD CONSTRAINT FK_83B57B86C7D3A8E6 FOREIGN KEY (thirdparty_id) REFERENCES chill_3party.third_party (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_goal ADD CONSTRAINT FK_911B88FEC55C1209 FOREIGN KEY (accompanyingPeriodWork_id) REFERENCES chill_person_accompanying_period_work (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_goal ADD CONSTRAINT FK_911B88FE667D1AFE FOREIGN KEY (goal_id) REFERENCES chill_person_social_work_goal (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_goal_result ADD CONSTRAINT FK_3E37D1F37B14AD03 FOREIGN KEY (accompanyingperiodworkgoal_id) REFERENCES chill_person_accompanying_period_work_goal (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_goal_result ADD CONSTRAINT FK_3E37D1F37A7B643 FOREIGN KEY (result_id) REFERENCES chill_person_social_work_result (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_social_work_evaluation ADD CONSTRAINT FK_2E23F3FEBF32A3DA FOREIGN KEY (socialAction_id) REFERENCES chill_person_social_action (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema) : void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_result DROP CONSTRAINT FK_46E95929B99F6060');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_third_party DROP CONSTRAINT FK_83B57B86B99F6060');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_goal DROP CONSTRAINT FK_911B88FEC55C1209');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_goal_result DROP CONSTRAINT FK_3E37D1F37B14AD03');
$this->addSql('DROP SEQUENCE chill_person_accompanying_period_work_id_seq CASCADE');
$this->addSql('DROP SEQUENCE chill_person_accompanying_period_work_goal_id_seq CASCADE');
$this->addSql('DROP SEQUENCE chill_person_social_work_evaluation_id_seq CASCADE');
$this->addSql('DROP TABLE chill_person_accompanying_period_work');
$this->addSql('DROP TABLE chill_person_accompanying_period_work_result');
$this->addSql('DROP TABLE chill_person_accompanying_period_work_third_party');
$this->addSql('DROP TABLE chill_person_accompanying_period_work_goal');
$this->addSql('DROP TABLE chill_person_accompanying_period_work_goal_result');
$this->addSql('DROP TABLE chill_person_social_work_evaluation');
}
}

View File

@@ -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%

View File

@@ -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>

View File

@@ -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");
}
}

View File

@@ -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 }

View File

@@ -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>

View File

@@ -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' => [],
]);
}

Some files were not shown because too many files have changed in this diff Show More