Compare commits

..

7 Commits

397 changed files with 3721 additions and 13333 deletions

View File

@@ -15,9 +15,7 @@ before_script:
- curl -sS https://getcomposer.org/installer | php - curl -sS https://getcomposer.org/installer | php
- php -d memory_limit=2G composer.phar install - php -d memory_limit=2G composer.phar install
- php tests/app/bin/console doctrine:migrations:migrate -n - php tests/app/bin/console doctrine:migrations:migrate -n
- php -d memory_limit=2G tests/app/bin/console cache:clear --env=dev - php -d memory_limit=2G tests/app/bin/console doctrine:fixtures:load -n
- php -d memory_limit=3G tests/app/bin/console doctrine:fixtures:load -n
- php -d memory_limit=2G tests/app/bin/console cache:clear --env=test
- echo "before_script finished" - echo "before_script finished"
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service # Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service

View File

@@ -1,24 +0,0 @@
# Description of changes
<!--
describe here the change of your MR. It can be either a text, or a bullet list
for changes
-->
# Issues related
<!--
list the issues related to this MR.
It may be client issues, or dev issues
-->
* ...
* ...
# Tests
<!-- Describe tests if any, or why no tests -->

View File

@@ -10,83 +10,15 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here -->
* [main] fix adding multiple AddresseDeRelais (combine PickAddressType with ChillCollection)
* [person]: do not suggest the current household of the person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/51)
* [person]: display other phone numbers in view + add message in case no others phone numbers (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/184)
* unnecessary whitespace removed from person banner after person-id + double parentheses removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/290)
* [person]: delete accompanying period work, including related objects (cascade) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/36)
* [address]: Display of incomplete address adjusted.
* [household]: improve relationship graph
* add form to create/edit/delete relationship link,
* improve graph refresh mechanism
* add feature to export canvas as image (png)
* [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern;
* [person] do not ask for center any more on person creation
* [3party] do not ask for center any more on 3party creation
## Test releases
### Test release 2021-11-08
* [person]: Display the name of a user when searching after a User (TMS)
* [person]: Add civility to the person
* [person]: Various improvements on the edit person form
* [person]: Set available_languages and available_countries as parameters for use in the edit person form
* [activity] Bugfix: documents can now be added to an activity.
* [tasks] improve tasks with filter order
* [tasks] refactor singleControllerTasks: limit the number of conditions from the context
* [validations] validation of accompanying period added: no duplicate participations or resources (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/60).
* [renderbox] If gender of person is not defined, no icon is displayed instead of neuter-icon (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/129).
* [confidential information] module added to blur confidential information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/248).
* refactor `AuthorizationHelper` and `UserACLAwareRepository` to fix constructor, and separate logic for parent role helper into `ParentRoleHelper`
* [main]: filter location and locationType in backend: exclude NULL names, only active and availableToUsers
* [activity]: perform client-side validation & show/hide fields in the "new location" modal
* [person]: normalize person with CenterResolverDispatcher and handle case where center is null or multiple in PersonRenderBox
* [docstore] voter for PersonDocument and AccompanyingCourseDocument on the 2.0 way (using VoterHelperFactory)
* [docstore] add authorization check inside controller and menu
* [activity]: fix inheritance for role `ACTIVITY FULL` and add missing acl in menu
* [person] show current address in search results
* [person] show alt names in search results
* [admin]: links to activity admin section added again.
* [household]: endDate field deleted from household edit form.
* [household]: View accompanying periods of current and old household members.
* [tasks]: different layout for task list / my tasks, and fix link to tasks in alert or in warning
* [admin]: links to activity admin section added again.
* [household]: household addresses ordered by ValidFrom date and by id to show the last created address on top.
* [socialWorkAction]: display of social issue and parent issues + banner context added.
* [DBAL dependencies] Upgrade to DBAL 3.1
### Test release 2021-10-27
* [person]: delete double actions buttons on search person page
* [person]: accompanying course work: remove creation date display the list of work + handle case when end date is null
* [main]: Add new pages with a menu for managing location and location type in the admin
* [main]: Add some fixtures for location type
* [calendar]: Pass the location when transforming a calendar item (rdv) into an activity
* [calendar]: Add a user menu for "my calendar"
### Test release 2021-10-18
* [3party]: french translation of contact and company * [3party]: french translation of contact and company
* [3party]: show parent in list * [3party]: show parent in list
* [3party]: change color for badge "child" * [3party]: change color for badge "child"
* [3party]: fix address creation
* [household members editor] finalisation of editor
* [AccompanyingCourse banner]: replace translation referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/70)
* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location.
* [household]: add relationship page with dynamic data visualisation graph
## Test releases ## Test releases
### Test release 2021-10-11 ### Test release 2021-10-11
* Address: zoom on postal code geometry + fix origin of manually entered postal code
* in the Address vue component, order the postal code and street address by alphabetic and numeric order
* add 3 new fields to PostalCode and adapt postal code command and fixtures * add 3 new fields to PostalCode and adapt postal code command and fixtures
* [Aside activity] Fixes for aside activity * [Aside activity] Fixes for aside activity
@@ -106,14 +38,10 @@ and this project adheres to
* [FilterOrder]: add development kit for generating filter and ordering in list * [FilterOrder]: add development kit for generating filter and ordering in list
* [Capitalization of names] person names are capitalized on creation, on prePersist event * [Capitalization of names] person names are capitalized on creation, on prePersist event
* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ;
* [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ;
### test release 2021-10-04 ### test release 2021-10-04
* [Household editor][UI] Update how household suggestion and addresses are picked; * [Household editor][UI] Update how household suggestion and addresses are picked;
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/80
* [AddAddress] Handle address suggestion; * [AddAddress] Handle address suggestion;
* [CenterType][Create a person] when overriding the ACL rules, allow to show a PickCenterType * [CenterType][Create a person] when overriding the ACL rules, allow to show a PickCenterType
when no centers are reachable by the default ACL. when no centers are reachable by the default ACL.
@@ -132,31 +60,8 @@ and this project adheres to
https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37 https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37
https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221 https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221
* [Household editor] suggest only temporarily addresses; * [On-The-Fly] modale works for showing, editing and creating person or thirdparty ;
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/82 * [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ;
* On-The-Fly modale works for showing, editing and creating person and thirdparty ;
* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ;
* [AddAddress] Handle address suggestion;
* [AddAddress][Entity address]: add a link between address and address reference;
* [Household editor] suggest household by comparing the temporary addresses from courses;
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/81 ## Test release yyyy-mm-dd
* On-The-Fly modale works for showing, editing and creating person and thirdparty
## Test released
<!--
Coming soon...
DO NOT ADD unreleased items here. Add them under "Unreleased" title
### Test release yyyy-mm-dd
-->
## Stable releases
No stable releases for v2+

View File

@@ -54,9 +54,6 @@
"doctrine/doctrine-fixtures-bundle": "^3.3", "doctrine/doctrine-fixtures-bundle": "^3.3",
"fakerphp/faker": "^1.13", "fakerphp/faker": "^1.13",
"nelmio/alice": "^3.8", "nelmio/alice": "^3.8",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^7.0", "phpunit/phpunit": "^7.0",
"symfony/debug-bundle": "^5.1", "symfony/debug-bundle": "^5.1",
"symfony/dotenv": "^5.1", "symfony/dotenv": "^5.1",

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +0,0 @@
parameters:
level: 1
paths:
- src/
excludePaths:
- src/Bundle/*/Tests/*
- src/Bundle/*/Test/*
- src/Bundle/*/config/*
- src/Bundle/*/migrations/*
- src/Bundle/*/translations/*
- src/Bundle/*/Resources/*
- src/Bundle/*/src/Tests/*
- src/Bundle/*/src/Test/*
- src/Bundle/*/src/config/*
- src/Bundle/*/src/migrations/*
- src/Bundle/*/src/translations/*
- src/Bundle/*/src/Resources/*
includes:
- phpstan-baseline.neon

View File

@@ -242,11 +242,6 @@ class ActivityController extends AbstractController
} }
} }
if (array_key_exists('location', $activityData)) {
$location = $em->getRepository(\Chill\MainBundle\Entity\Location::class)->find($activityData['location']);
$entity->setLocation($location);
}
if (array_key_exists('comment', $activityData)) { if (array_key_exists('comment', $activityData)) {
$comment = new CommentEmbeddable(); $comment = new CommentEmbeddable();
$comment->setComment($activityData['comment']); $comment->setComment($activityData['comment']);

View File

@@ -22,10 +22,8 @@
namespace Chill\ActivityBundle\DataFixtures\ORM; namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\PersonBundle\Entity\Person;
use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Faker\Factory as FakerFactory; use Faker\Factory as FakerFactory;
use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\Activity;
@@ -33,19 +31,25 @@ use Chill\MainBundle\DataFixtures\ORM\LoadUsers;
use Chill\ActivityBundle\DataFixtures\ORM\LoadActivityReason; use Chill\ActivityBundle\DataFixtures\ORM\LoadActivityReason;
use Chill\ActivityBundle\DataFixtures\ORM\LoadActivityType; use Chill\ActivityBundle\DataFixtures\ORM\LoadActivityType;
use Chill\MainBundle\DataFixtures\ORM\LoadScopes; use Chill\MainBundle\DataFixtures\ORM\LoadScopes;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
class LoadActivity extends AbstractFixture implements OrderedFixtureInterface /**
* Load reports into DB
*
* @author Champs-Libres Coop
*/
class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{ {
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
/** /**
* @var \Faker\Generator * @var \Faker\Generator
*/ */
private $faker; private $faker;
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em) public function __construct()
{ {
$this->faker = FakerFactory::create('fr_FR'); $this->faker = FakerFactory::create('fr_FR');
$this->em = $em;
} }
public function getOrder() public function getOrder()
@@ -84,7 +88,7 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
{ {
$reasonRef = LoadActivityReason::$references[array_rand(LoadActivityReason::$references)]; $reasonRef = LoadActivityReason::$references[array_rand(LoadActivityReason::$references)];
if (in_array($this->getReference($reasonRef)->getId(), $excludingIds, true)) { if (in_array($this->getReference($reasonRef)->getId(), $excludingIds)) {
// we have a reason which should be excluded. Find another... // we have a reason which should be excluded. Find another...
return $this->getRandomActivityReason($excludingIds); return $this->getRandomActivityReason($excludingIds);
} }
@@ -128,17 +132,20 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
public function load(ObjectManager $manager) public function load(ObjectManager $manager)
{ {
$persons = $this->em $persons = $this->container->get('doctrine.orm.entity_manager')
->getRepository(Person::class) ->getRepository('ChillPersonBundle:Person')
->findAll(); ->findAll();
foreach ($persons as $person) { foreach($persons as $person) {
$activityNbr = rand(0,3); $activityNbr = rand(0,3);
$ref = 'activity_'.$person->getFullnameCanonical();
for ($i = 0; $i < $activityNbr; $i ++) { for($i = 0; $i < $activityNbr; $i ++) {
$activity = $this->newRandomActivity($person); $activity = $this->newRandomActivity($person);
$manager->persist($activity); $manager->persist($activity);
} }
$this->setReference($ref, $activity);
} }
$manager->flush(); $manager->flush();
} }

View File

@@ -87,9 +87,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
ActivityVoter::UPDATE => array(ActivityVoter::SEE_DETAILS), ActivityVoter::UPDATE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::CREATE => array(ActivityVoter::SEE_DETAILS), ActivityVoter::CREATE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::DELETE => array(ActivityVoter::SEE_DETAILS), ActivityVoter::DELETE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::SEE_DETAILS => array(ActivityVoter::SEE), ActivityVoter::SEE_DETAILS => array(ActivityVoter::SEE)
ActivityVoter::FULL => [ActivityVoter::CREATE, ActivityVoter::DELETE,
ActivityVoter::UPDATE],
) )
)); ));
} }

View File

@@ -23,7 +23,6 @@ namespace Chill\ActivityBundle\Entity;
use Chill\DocStoreBundle\Entity\Document; use Chill\DocStoreBundle\Entity\Document;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Location;
use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface; use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
@@ -158,7 +157,7 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
private ?Collection $thirdParties = null; private ?Collection $thirdParties = null;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"}) * @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject")
*/ */
private Collection $documents; private Collection $documents;
@@ -178,13 +177,6 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
*/ */
private string $sentReceived = ''; private string $sentReceived = '';
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
* @groups({"read"})
*/
private ?Location $location = null;
public function __construct() public function __construct()
{ {
$this->reasons = new ArrayCollection(); $this->reasons = new ArrayCollection();
@@ -563,22 +555,4 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
return $this; return $this;
} }
/**
* @return Location|null
*/
public function getLocation(): ?Location
{
return $this->location;
}
/**
* @param Location|null $location
* @return Activity
*/
public function setLocation(?Location $location): Activity
{
$this->location = $location;
return $this;
}
} }

View File

@@ -1,19 +1,19 @@
<?php <?php
/* /*
* *
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop> * Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the * published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details. * GNU Affero General Public License for more details.
* *
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@@ -44,7 +44,7 @@ class ActivityReason
/** /**
* @var array * @var array
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name; private $name;
@@ -108,7 +108,7 @@ class ActivityReason
return $this->name; return $this->name;
} }
} }
/** /**
* Set category of the reason. If you set to the reason an inactive * Set category of the reason. If you set to the reason an inactive
* category, the reason will become inactive * category, the reason will become inactive
@@ -121,7 +121,7 @@ class ActivityReason
if($this->category !== $category && ! $category->getActive()) { if($this->category !== $category && ! $category->getActive()) {
$this->setActive(False); $this->setActive(False);
} }
$this->category = $category; $this->category = $category;
return $this; return $this;

View File

@@ -2,17 +2,17 @@
/* /*
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop> * Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the * published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details. * GNU Affero General Public License for more details.
* *
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@@ -43,7 +43,7 @@ class ActivityReasonCategory
/** /**
* @var string * @var string
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name; private $name;
@@ -52,7 +52,7 @@ class ActivityReasonCategory
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $active = true; private $active = true;
/** /**
* Array of ActivityReason * Array of ActivityReason
* @var ArrayCollection * @var ArrayCollection
@@ -61,7 +61,7 @@ class ActivityReasonCategory
* mappedBy="category") * mappedBy="category")
*/ */
private $reasons; private $reasons;
/** /**
* ActivityReasonCategory constructor. * ActivityReasonCategory constructor.
*/ */
@@ -69,7 +69,7 @@ class ActivityReasonCategory
{ {
$this->reasons = new ArrayCollection(); $this->reasons = new ArrayCollection();
} }
/** /**
* @return string * @return string
*/ */
@@ -139,9 +139,9 @@ class ActivityReasonCategory
$reason->setActive($active); $reason->setActive($active);
} }
} }
$this->active = $active; $this->active = $active;
return $this; return $this;
} }

View File

@@ -44,7 +44,7 @@ class ActivityType
private ?int $id; private ?int $id;
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private array $name = []; private array $name = [];
@@ -248,16 +248,6 @@ class ActivityType
*/ */
private string $socialActionsLabel = ''; private string $socialActionsLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default"=1})
*/
private int $locationVisible = self::FIELD_INVISIBLE;
/**
* @ORM\Column(type="string", nullable=false, options={"default"=""})
*/
private string $locationLabel = '';
/** /**
* @ORM\Column(type="float", options={"default"="0.0"}) * @ORM\Column(type="float", options={"default"="0.0"})
*/ */
@@ -830,29 +820,4 @@ class ActivityType
return $this; return $this;
} }
public function getLocationVisible(): ?int
{
return $this->locationVisible;
}
public function setLocationVisible(int $locationVisible): self
{
$this->locationVisible = $locationVisible;
return $this;
}
public function getLocationLabel(): ?string
{
return $this->locationLabel;
}
public function setLocationLabel(string $locationLabel): self
{
$this->locationLabel = $locationLabel;
return $this;
}
} }

View File

@@ -40,7 +40,7 @@ class ActivityTypeCategory
private ?int $id; private ?int $id;
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private array $name = []; private array $name = [];

View File

@@ -29,27 +29,27 @@ use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\Query\Expr\Join;
/** /**
* *
* *
* @author Julien Fastré <julien.fastre@champs-libres.coop> * @author Julien Fastré <julien.fastre@champs-libres.coop>
*/ */
class ActivityTypeAggregator implements AggregatorInterface class ActivityTypeAggregator implements AggregatorInterface
{ {
/** /**
* *
* @var EntityRepository * @var EntityRepository
*/ */
protected $typeRepository; protected $typeRepository;
/** /**
* *
* @var TranslatableStringHelper * @var TranslatableStringHelper
*/ */
protected $stringHelper; protected $stringHelper;
const KEY = 'activity_type_aggregator'; const KEY = 'activity_type_aggregator';
public function __construct( public function __construct(
EntityRepository $typeRepository, EntityRepository $typeRepository,
TranslatableStringHelper $stringHelper TranslatableStringHelper $stringHelper
@@ -57,19 +57,19 @@ class ActivityTypeAggregator implements AggregatorInterface
$this->typeRepository = $typeRepository; $this->typeRepository = $typeRepository;
$this->stringHelper = $stringHelper; $this->stringHelper = $stringHelper;
} }
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
// add select element // add select element
$qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY)); $qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY));
// add the "group by" part // add the "group by" part
$groupBy = $qb->addGroupBy(self::KEY); $groupBy = $qb->addGroupBy(self::KEY);
} }
/** /**
* Check if a join between Activity and another alias * Check if a join between Activity and another alias
* *
* @param Join[] $joins * @param Join[] $joins
* @param string $alias the alias to search for * @param string $alias the alias to search for
* @return boolean * @return boolean
@@ -81,7 +81,7 @@ class ActivityTypeAggregator implements AggregatorInterface
return true; return true;
} }
} }
return false; return false;
} }
@@ -99,18 +99,18 @@ class ActivityTypeAggregator implements AggregatorInterface
{ {
return "Aggregate by activity type"; return "Aggregate by activity type";
} }
public function addRole() public function addRole()
{ {
return new Role(ActivityStatsVoter::STATS); return new Role(ActivityStatsVoter::STATS);
} }
public function getLabels($key, array $values, $data): \Closure public function getLabels($key, array $values, $data)
{ {
// for performance reason, we load data from db only once // for performance reason, we load data from db only once
$this->typeRepository->findBy(array('id' => $values)); $this->typeRepository->findBy(array('id' => $values));
return function($value): string { return function($value) use ($data) {
if ($value === '_header') { if ($value === '_header') {
return 'Activity type'; return 'Activity type';
} }
@@ -120,11 +120,12 @@ class ActivityTypeAggregator implements AggregatorInterface
return $this->stringHelper->localize($t->getName()); return $this->stringHelper->localize($t->getName());
}; };
} }
public function getQueryKeys($data): array public function getQueryKeys($data)
{ {
return [self::KEY]; return array(self::KEY);
} }
} }

View File

@@ -6,7 +6,6 @@ use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityPresence; use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\DocStoreBundle\Form\StoredObjectType; use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\CommentType;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
@@ -223,7 +222,7 @@ class ActivityType extends AbstractType
if ($activityType->isVisible('comment')) { if ($activityType->isVisible('comment')) {
$builder->add('comment', CommentType::class, [ $builder->add('comment', CommentType::class, [
'label' => empty($activityType->getLabel('comment')) 'label' => empty($activityType->getLabel('comment'))
? 'activity.comment' : $activityType->getLabel('comment'), ? 'activity.comment' : $activityType->getLabel('comment'),
'required' => $activityType->isRequired('comment'), 'required' => $activityType->isRequired('comment'),
]); ]);
@@ -303,23 +302,6 @@ class ActivityType extends AbstractType
; ;
} }
if ($activityType->isVisible('location')) {
$builder->add('location', HiddenType::class)
->get('location')
->addModelTransformer(new CallbackTransformer(
function (?Location $location): string {
if (null === $location) {
return '';
}
return $location->getId();
},
function (?string $id): ?Location {
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
}
))
;
}
if ($activityType->isVisible('emergency')) { if ($activityType->isVisible('emergency')) {
$builder->add('emergency', CheckboxType::class, [ $builder->add('emergency', CheckboxType::class, [
'label' => $activityType->getLabel('emergency'), 'label' => $activityType->getLabel('emergency'),

View File

@@ -71,7 +71,7 @@ class TranslatableActivityType extends AbstractType
if ($options['active_only'] === true) { if ($options['active_only'] === true) {
$qb->where($qb->expr()->eq('at.active', ':active')); $qb->where($qb->expr()->eq('at.active', ':active'));
$qb->setParameter('active', true, \Doctrine\DBAL\Types\Types::BOOLEAN); $qb->setParameter('active', true, \Doctrine\DBAL\Types\Type::BOOLEAN);
} }
} }

View File

@@ -2,27 +2,29 @@
namespace Chill\ActivityBundle\Menu; namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Knp\Menu\MenuItem; use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{ {
protected TokenStorageInterface $tokenStorage;
protected AuthorizationHelper $authorizationHelper;
protected TranslatorInterface $translator; protected TranslatorInterface $translator;
protected Security $security;
public function __construct( public function __construct(
Security $security, TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper,
TranslatorInterface $translator TranslatorInterface $translator
) { ) {
$this->security = $security;
$this->translator = $translator; $this->translator = $translator;
$this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage;
} }
public static function getMenuIds(): array public static function getMenuIds(): array
{ {
@@ -33,8 +35,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{ {
$period = $parameters['accompanyingCourse']; $period = $parameters['accompanyingCourse'];
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep() if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) {
&& $this->security->isGranted(ActivityVoter::SEE, $period)) {
$menu->addChild($this->translator->trans('Activity'), [ $menu->addChild($this->translator->trans('Activity'), [
'route' => 'chill_activity_activity_list', 'route' => 'chill_activity_activity_list',
'routeParameters' => [ 'routeParameters' => [

View File

@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
final class AdminMenuBuilder implements LocalMenuBuilderInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public static function getMenuIds(): array
{
return ['admin_index', 'admin_section', 'admin_activity'];
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
return;
}
if (in_array($menuId, ['admin_index', 'admin_section'])) {
$menu->addChild('Activities', [
'route' => 'chill_admin_activity_index'
])
->setExtras([
'order' => 2000,
'explain' => "Activity configuration"
]);
} else {
$menu
->addChild('Activities', [
'route' => 'chill_admin_activity_index'
])
->setExtras([
'order' => '60'
]);
}
}
}

View File

@@ -1,20 +1,17 @@
<template> <template>
<concerned-groups></concerned-groups> <concerned-groups></concerned-groups>
<social-issues-acc></social-issues-acc> <social-issues-acc></social-issues-acc>
<location></location>
</template> </template>
<script> <script>
import ConcernedGroups from './components/ConcernedGroups.vue'; import ConcernedGroups from './components/ConcernedGroups.vue';
import SocialIssuesAcc from './components/SocialIssuesAcc.vue'; import SocialIssuesAcc from './components/SocialIssuesAcc.vue';
import Location from './components/Location.vue';
export default { export default {
name: "App", name: "App",
components: { components: {
ConcernedGroups, ConcernedGroups,
SocialIssuesAcc, SocialIssuesAcc
Location
} }
} }
</script> </script>

View File

@@ -3,61 +3,16 @@ import { getSocialIssues } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api.
/* /*
* Load socialActions by socialIssue (id) * Load socialActions by socialIssue (id)
*/ */
const getSocialActionByIssue = (id) => { const getSocialActionByIssue = (id) => {
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`; const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
return fetch(url) return fetch(url)
.then(response => { .then(response => {
if (response.ok) { return response.json(); } if (response.ok) { return response.json(); }
throw Error('Error with request resource response'); throw Error('Error with request resource response');
}); });
}; };
/*
* Load Locations
*/
const getLocations = () => {
const url = `/api/1.0/main/location.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
/*
* Load Location Types
*/
const getLocationTypes = () => {
const url = `/api/1.0/main/location-type.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
/*
* Post a Location
*/
const postLocation = (body) => {
const url = `/api/1.0/main/location.json`;
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
export { export {
getSocialIssues, getSocialIssues,
getSocialActionByIssue, getSocialActionByIssue
getLocations,
getLocationTypes,
postLocation
}; };

View File

@@ -73,12 +73,9 @@ export default {
addPersons: { addPersons: {
key: 'activity', key: 'activity',
options: { options: {
type: ['person', 'thirdparty', 'user'], type: ['person', 'thirdparty', 'user'], // TODO add 'user'
priority: null, priority: null,
uniq: false, uniq: false,
button: {
size: 'btn-sm'
}
} }
} }
} }

View File

@@ -1,74 +0,0 @@
<template>
<teleport to="#location">
<div class="mb-3 row">
<label class="col-form-label col-sm-4">
{{ $t('activity.location') }}
</label>
<div class="col-sm-8">
<VueMultiselect
name="selectLocation"
id="selectLocation"
label="name"
track-by="id"
open-direction="top"
:multiple="false"
:searchable="true"
:placeholder="$t('activity.choose_location')"
:custom-label="customLabel"
:options="locations"
v-model="location">
</VueMultiselect>
<new-location v-bind:locations="locations"></new-location>
</div>
</div>
</teleport>
</template>
<script>
import { mapState } from "vuex";
import VueMultiselect from 'vue-multiselect';
import NewLocation from './Location/NewLocation.vue';
import { getLocations } from '../api.js';
export default {
name: "Location",
components: {
NewLocation,
VueMultiselect
},
data() {
return {
locations: []
}
},
computed: {
...mapState(['activity']),
location: {
get() {
return this.activity.location;
},
set(value) {
this.$store.dispatch('updateLocation', value);
}
}
},
mounted() {
this.getLocationsList();
},
methods: {
getLocationsList() {
getLocations().then(response => new Promise(resolve => {
console.log('getLocations', response);
this.locations = response.results;
resolve();
}))
},
customLabel(value) {
return `${value.locationType.title.fr} ${value.name ? value.name : ''}`;
}
}
}
</script>

View File

@@ -1,271 +0,0 @@
<template>
<div>
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-create" @click="openModal">
{{ $t('activity.create_new_location') }}
</a>
</li>
</ul>
<teleport to="body">
<modal v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false">
<template v-slot:header>
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
</template>
<template v-slot:body>
<form>
<div class="form-floating mb-3">
<p v-if="errors.length">
<b>{{ $t('activity.errors') }}</b>
<ul>
<li v-for="error in errors" :key="error">{{ error }}</li>
</ul>
</p>
</div>
<div class="form-floating mb-3">
<select class="form-select form-select-lg" id="type" required v-model="selectType">
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
<option v-for="t in locationTypes" :value="t" :key="t.id">
{{ t.title.fr }}
</option>
</select>
<label>{{ $t('activity.location_fields.type') }}</label>
</div>
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
<label for="name">{{ $t('activity.location_fields.name') }}</label>
</div>
<add-address
:context="addAddress.context"
:options="addAddress.options"
:addressChangedCallback="submitNewAddress"
v-if="showAddAddress"
ref="addAddress">
</add-address>
<div class="form-floating mb-3" v-if="showContactData">
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label>
</div>
<div class="form-floating mb-3" v-if="hasPhonenumber1">
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
<label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label>
</div>
<div class="form-floating mb-3" v-if="showContactData">
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
<label for="email">{{ $t('activity.location_fields.email') }}</label>
</div>
</form>
</template>
<template v-slot:footer>
<button class="btn btn-save"
@click.prevent="saveNewLocation"
>
{{ $t('action.save') }}
</button>
</template>
</modal>
</teleport>
</div>
</template>
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import { mapState } from "vuex";
import { getLocationTypes, postLocation } from "../../api";
export default {
name: "NewLocation",
components: {
Modal,
AddAddress,
},
props: ['locations'],
data() {
return {
errors: [],
selected: {
type: null,
name: null,
addressId: null,
phonenumber1: null,
phonenumber2: null,
email: null,
},
locationTypes: [],
modal: {
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl"
},
addAddress: {
options: {
button: {
text: { create: 'activity.create_address', edit: 'activity.edit_address' },
size: 'btn-sm'
},
title: { create: 'activity.create_address', edit: 'activity.edit_address' },
},
context: {
target: { //name, id
},
edit: false,
addressId: null
}
}
}
},
computed: {
...mapState(['activity']),
selectType: {
get() {
return this.selected.type;
},
set(value) {
this.selected.type = value;
}
},
inputName: {
get() {
return this.selected.name;
},
set(value) {
this.selected.name = value;
}
},
inputEmail: {
get() {
return this.selected.email;
},
set(value) {
this.selected.email = value;
}
},
inputPhonenumber1: {
get() {
return this.selected.phonenumber1;
},
set(value) {
this.selected.phonenumber1 = value;
}
},
inputPhonenumber2: {
get() {
return this.selected.phonenumber2;
},
set(value) {
this.selected.phonenumber2 = value;
}
},
hasPhonenumber1() {
return this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== "";
},
showAddAddress() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.addressRequired !== 'never') {
cond = true;
}
}
return cond;
},
showContactData() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.contactData !== 'never') {
cond = true;
}
}
return cond;
},
},
mounted() {
this.getLocationTypesList();
},
methods: {
checkForm() {
let cond = true;
this.errors = [];
if (!this.selected.type) {
this.errors.push('Type de localisation requis');
cond = false;
} else {
if (this.selected.type.addressRequired === 'required' && !this.selected.addressId) {
this.errors.push('Adresse requise');
cond = false;
}
if (this.selected.type.contactData === 'required' && !this.selected.phonenumber1) {
this.errors.push('Numéro de téléphone requis');
cond = false;
}
if (this.selected.type.contactData === 'required' && !this.selected.email) {
this.errors.push('Adresse email requise');
cond = false;
}
}
return cond;
},
getLocationTypesList() {
getLocationTypes().then(response => new Promise(resolve => {
console.log('getLocationTypes', response);
this.locationTypes = response.results.filter(t => t.availableForUsers === true);
resolve();
}))
},
openModal() {
this.modal.showModal = true;
},
saveNewLocation() {
if (this.checkForm()) {
console.log('saveNewLocation', this.selected);
let body = {
type: 'location',
name: this.selected.name,
locationType: {
id: this.selected.type.id,
type: 'location-type'
},
phonenumber1: this.selected.phonenumber1,
phonenumber2: this.selected.phonenumber2,
email: this.selected.email,
};
if (this.selected.addressId) {
body = Object.assign(body, {
address: {
id: this.selected.addressId
}
});
}
postLocation(body)
.then(
location => new Promise(resolve => {
console.log('postLocation', location);
this.locations.push(location);
this.$store.dispatch('updateLocation', location);
resolve();
this.modal.showModal = false;
})
).catch(
err => {
this.errors.push(err.message);
}
);
};
},
submitNewAddress(payload) {
console.log('submitNewAddress', payload);
this.selected.addressId = payload.addressId;
this.addAddress.context.addressId = payload.addressId;
this.addAddress.context.edit = true;
}
}
}
</script>

View File

@@ -1,12 +1,12 @@
<template> <template>
<teleport to="#social-issues-acc"> <teleport to="#social-issues-acc">
<div class="mb-3 row"> <div class="mb-3 row">
<div class="col-4"> <div class="col-4">
<label class="col-form-label">{{ $t('activity.social_issues') }}</label> <label class="col-form-label">{{ $t('activity.social_issues') }}</label>
</div> </div>
<div class="col-8"> <div class="col-8">
<check-social-issue <check-social-issue
v-for="issue in socialIssuesList" v-for="issue in socialIssuesList"
v-bind:key="issue.id" v-bind:key="issue.id"
@@ -14,7 +14,7 @@
v-bind:selection="socialIssuesSelected" v-bind:selection="socialIssuesSelected"
@updateSelected="updateIssuesSelected"> @updateSelected="updateIssuesSelected">
</check-social-issue> </check-social-issue>
<div class="my-3"> <div class="my-3">
<VueMultiselect <VueMultiselect
name="otherIssues" name="otherIssues"
@@ -28,15 +28,16 @@
v-bind:taggable="false" v-bind:taggable="false"
v-bind:multiple="false" v-bind:multiple="false"
v-bind:searchable="true" v-bind:searchable="true"
v-bind:allow-empty="true" v-bind:allow-empty="true"
v-bind:show-labels="false" v-bind:show-labels="false"
v-bind:loading="issueIsLoading" v-bind:loading="issueIsLoading"
v-bind:placeholder="$t('activity.choose_other_social_issue')" v-bind:placeholder="$t('activity.choose_other_social_issue')"
v-bind:options="socialIssuesOther" v-bind:options="socialIssuesOther"
v-model="value"
@select="addIssueInList"> @select="addIssueInList">
</VueMultiselect> </VueMultiselect>
</div> </div>
</div> </div>
</div> </div>
@@ -45,11 +46,11 @@
<label class="col-form-label">{{ $t('activity.social_actions') }}</label> <label class="col-form-label">{{ $t('activity.social_actions') }}</label>
</div> </div>
<div class="col-8"> <div class="col-8">
<div v-if="actionIsLoading === true"> <div v-if="actionIsLoading === true">
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i> <i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
</div> </div>
<check-social-action <check-social-action
v-if="socialIssuesSelected.length || socialActionsSelected.length" v-if="socialIssuesSelected.length || socialActionsSelected.length"
v-for="action in socialActionsList" v-for="action in socialActionsList"
@@ -58,18 +59,19 @@
v-bind:selection="socialActionsSelected" v-bind:selection="socialActionsSelected"
@updateSelected="updateActionsSelected"> @updateSelected="updateActionsSelected">
</check-social-action> </check-social-action>
<span v-else class="inline-choice chill-no-data-statement mt-3"> <span v-else class="inline-choice chill-no-data-statement mt-3">
{{ $t('activity.select_first_a_social_issue') }} {{ $t('activity.select_first_a_social_issue') }}
</span> </span>
</div> </div>
</div> </div>
</teleport> </teleport>
</template> </template>
<script> <script>
import { readonly } from 'vue';
import VueMultiselect from 'vue-multiselect'; import VueMultiselect from 'vue-multiselect';
import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue'; import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue';
import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue'; import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue';
@@ -111,7 +113,7 @@ export default {
this.issueIsLoading = true; this.issueIsLoading = true;
getSocialIssues().then(response => new Promise((resolve, reject) => { getSocialIssues().then(response => new Promise((resolve, reject) => {
this.$store.commit('updateIssuesOther', response.results); this.$store.commit('updateIssuesOther', response.results);
/* Add in list the issues already associated (if not yet listed) /* Add in list the issues already associated (if not yet listed)
*/ */
this.socialIssuesSelected.forEach(issue => { this.socialIssuesSelected.forEach(issue => {
@@ -119,34 +121,34 @@ export default {
this.$store.commit('addIssueInList', issue); this.$store.commit('addIssueInList', issue);
} }
}, this); }, this);
/* Remove from multiselect the issues that are not yet in checkbox list /* Remove from multiselect the issues that are not yet in checkbox list
*/ */
this.socialIssuesList.forEach(issue => { this.socialIssuesList.forEach(issue => {
this.$store.commit('removeIssueInOther', issue); this.$store.commit('removeIssueInOther', issue);
}, this); }, this);
/* Filter issues /* Filter issues
*/ */
this.$store.commit('filterList', 'issues'); this.$store.commit('filterList', 'issues');
/* Add in list the actions already associated (if not yet listed) /* Add in list the actions already associated (if not yet listed)
*/ */
this.socialActionsSelected.forEach(action => { this.socialActionsSelected.forEach(action => {
this.$store.commit('addActionInList', action); this.$store.commit('addActionInList', action);
}, this); }, this);
/* Filter issues /* Filter issues
*/ */
this.$store.commit('filterList', 'actions'); this.$store.commit('filterList', 'actions');
this.issueIsLoading = false; this.issueIsLoading = false;
resolve(); resolve();
})); }));
}, },
methods: { methods: {
/* When choosing an issue in multiselect, add it in checkboxes (as selected), /* When choosing an issue in multiselect, add it in checkboxes (as selected),
remove it from multiselect, and add socialActions concerned remove it from multiselect, and add socialActions concerned
*/ */
addIssueInList(value) { addIssueInList(value) {
@@ -169,30 +171,30 @@ export default {
//console.log('updateActionsSelected', actions); //console.log('updateActionsSelected', actions);
this.$store.dispatch('updateActionsSelected', actions); this.$store.dispatch('updateActionsSelected', actions);
}, },
/* Add socialActions concerned: after reset, loop on each issue selected /* Add socialActions concerned: after reset, loop on each issue selected
to get social actions concerned to get social actions concerned
*/ */
updateActionsList() { updateActionsList() {
//console.log('updateActionsList'); //console.log('updateActionsList');
this.resetActionsList(); this.resetActionsList();
this.socialIssuesSelected.forEach(item => { this.socialIssuesSelected.forEach(item => {
this.actionIsLoading = true; this.actionIsLoading = true;
getSocialActionByIssue(item.id) getSocialActionByIssue(item.id)
.then(actions => new Promise((resolve, reject) => { .then(actions => new Promise((resolve, reject) => {
actions.results.forEach(action => { actions.results.forEach(action => {
this.$store.commit('addActionInList', action); this.$store.commit('addActionInList', action);
}, this); }, this);
this.$store.commit('filterList', 'actions'); this.$store.commit('filterList', 'actions');
this.actionIsLoading = false; this.actionIsLoading = false;
resolve(); resolve();
})); }));
}, this); }, this);
}, },
/* Reset socialActions List: flush list and restore selected actions /* Reset socialActions List: flush list and restore selected actions
*/ */
resetActionsList() { resetActionsList() {
this.$store.commit('resetActionsList'); this.$store.commit('resetActionsList');
@@ -205,7 +207,7 @@ export default {
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style> <style src="vue-multiselect/dist/vue-multiselect.css"></style>
<style lang="scss" scoped> <style lang="scss">
span.multiselect__single { span.multiselect__single {
display: none !important; display: none !important;
} }

View File

@@ -1,15 +1,14 @@
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n' import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
const activityMessages = { const appMessages = {
fr: { fr: {
activity: { activity: {
// //
errors: "Le formulaire contient des erreurs",
social_issues: "Problématiques sociales", social_issues: "Problématiques sociales",
choose_other_social_issue: "Ajouter une autre problématique sociale...", choose_other_social_issue: "Ajouter une autre problématique sociale...",
social_actions: "Actions d'accompagnement", social_actions: "Actions d'accompagnement",
select_first_a_social_issue: "Sélectionnez d'abord une problématique sociale", select_first_a_social_issue: "Sélectionnez d'abord une problématique sociale",
// //
add_persons: "Ajouter des personnes concernées", add_persons: "Ajouter des personnes concernées",
bloc_persons: "Usagers", bloc_persons: "Usagers",
@@ -17,28 +16,12 @@ const activityMessages = {
bloc_persons_not_associated: "Tiers non-pro.", bloc_persons_not_associated: "Tiers non-pro.",
bloc_thirdparty: "Tiers professionnels", bloc_thirdparty: "Tiers professionnels",
bloc_users: "T(M)S", bloc_users: "T(M)S",
//
location: "Localisation",
choose_location: "Choisissez une localisation",
choose_location_type: "Choisissez un type de localisation",
create_new_location: "Créer une nouvelle localisation",
location_fields: {
name: "Nom",
type: "Type",
phonenumber1: "Téléphone",
phonenumber2: "Autre téléphone",
email: "Adresse courriel",
},
create_address: 'Créer une adresse',
edit_address: "Modifier l'adresse"
} }
} }
} }
Object.assign(activityMessages.fr, personMessages.fr); Object.assign(appMessages.fr, personMessages.fr);
export { export {
activityMessages appMessages
}; };

View File

@@ -1,11 +1,11 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import { activityMessages } from './i18n' import { appMessages } from './i18n'
import store from './store' import store from './store'
import App from './App.vue'; import App from './App.vue';
const i18n = _createI18n(activityMessages); const i18n = _createI18n(appMessages);
const app = createApp({ const app = createApp({
template: `<app></app>`, template: `<app></app>`,

View File

@@ -114,10 +114,6 @@ const store = createStore({
state.activity.users = state.activity.users.filter(user => user !== payload); state.activity.users = state.activity.users.filter(user => user !== payload);
break; break;
}; };
},
updateLocation(state, value) {
console.log('### mutation: updateLocation', value);
state.activity.location = value;
} }
}, },
actions: { actions: {
@@ -177,12 +173,6 @@ const store = createStore({
break; break;
}; };
commit('removePersonInvolved', payload); commit('removePersonInvolved', payload);
},
updateLocation({ commit }, value) {
console.log('### action: updateLocation', value);
let hiddenLocation = document.getElementById("chill_activitybundle_activity_location");
hiddenLocation.value = value.id;
commit('updateLocation', value);
} }
} }
}); });

View File

@@ -40,6 +40,7 @@
}, },
{ 'title': 'Users concerned'|trans, { 'title': 'Users concerned'|trans,
'items': entity.users, 'items': entity.users,
'path' : 'admin_user_show',
'key' : 'id' 'key' : 'id'
}, },
] %} ] %}
@@ -57,7 +58,6 @@
<ul class="list-content"> <ul class="list-content">
{% for item in bloc.items %} {% for item in bloc.items %}
<li> <li>
{% if bloc.path is defined %}
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}"> <a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}"> <span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
{{ item|chill_entity_render_box({ {{ item|chill_entity_render_box({
@@ -66,14 +66,6 @@
}) }} }) }}
</span> </span>
</a> </a>
{% else %}
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
</span>
{% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -93,7 +85,6 @@
<ul class="list-content"> <ul class="list-content">
{% for item in bloc.items %} {% for item in bloc.items %}
<li> <li>
{% if bloc.path is defined %}
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}"> <a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}"> <span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
{{ item|chill_entity_render_box({ {{ item|chill_entity_render_box({
@@ -102,12 +93,6 @@
}) }} }) }}
</span> </span>
</a> </a>
{% else %}
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
{% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -129,19 +114,12 @@
{% for item in bloc.items %} {% for item in bloc.items %}
<span class="wl-item {% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}"> <span class="wl-item {% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
{% if bloc.path is defined %}
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}"> <a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
{{ item|chill_entity_render_box({ {{ item|chill_entity_render_box({
'render': 'raw', 'render': 'raw',
'addAltNames': false 'addAltNames': false
}) }} }) }}
</a> </a>
{% else %}
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
{% endif %}
</span> </span>
{% endfor %} {% endfor %}

View File

@@ -10,7 +10,7 @@
'title' : 'Remove activity'|trans, 'title' : 'Remove activity'|trans,
'confirm_question' : 'Are you sure you want to remove the activity about "%name%" ?'|trans({ '%name%' : accompanyingCourse.id } ), 'confirm_question' : 'Are you sure you want to remove the activity about "%name%" ?'|trans({ '%name%' : accompanyingCourse.id } ),
'cancel_route' : 'chill_activity_activity_list', 'cancel_route' : 'chill_activity_activity_list',
'cancel_parameters' : { 'accompanying_period_id' : accompanyingCourse.id, 'id' : activity.id }, 'cancel_parameters' : { 'accompanying_course_id' : accompanyingCourse.id, 'id' : activity.id },
'form' : delete_form 'form' : delete_form
} ) }} } ) }}
{% endblock %} {% endblock %}

View File

@@ -54,10 +54,7 @@
{{ form_row(edit_form.date) }} {{ form_row(edit_form.date) }}
{% endif %} {% endif %}
{%- if edit_form.location is defined -%} {# TODO .. location #}
{{ form_row(edit_form.location) }}
<div id="location"></div>
{% endif %}
{%- if edit_form.durationTime is defined -%} {%- if edit_form.durationTime is defined -%}
{{ form_row(edit_form.durationTime) }} {{ form_row(edit_form.durationTime) }}

View File

@@ -6,10 +6,10 @@
{% block content %} {% block content %}
<div class="activity-edit"> <div class="activity-edit">
<div id="activity"></div> {# <=== vue component #} <div id="activity"></div> {# <=== vue component #}
{% include 'ChillActivityBundle:Activity:edit.html.twig' %} {% include 'ChillActivityBundle:Activity:edit.html.twig' %}
</div> </div>
{% endblock %} {% endblock %}
@@ -18,10 +18,10 @@
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}'); '{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
}); });
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};
</script> </script>
{{ encore_entry_script_tags('vue_activity') }} {{ encore_entry_script_tags('vue_activity') }}
{% endblock %} {% endblock %}

View File

@@ -22,10 +22,10 @@
{% block personcontent %} {% block personcontent %}
<div class="activity-edit"> <div class="activity-edit">
<div id="activity"></div> {# <=== vue component #} <div id="activity"></div> {# <=== vue component #}
{% include 'ChillActivityBundle:Activity:edit.html.twig' %} {% include 'ChillActivityBundle:Activity:edit.html.twig' %}
</div> </div>
{% endblock %} {% endblock %}
@@ -33,7 +33,7 @@
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}'); '{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
}); });
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};

View File

@@ -55,10 +55,7 @@
{{ form_row(form.date) }} {{ form_row(form.date) }}
{% endif %} {% endif %}
{%- if form.location is defined -%} {# TODO .. location #}
{{ form_row(form.location) }}
<div id="location"></div>
{% endif %}
{%- if form.durationTime is defined -%} {%- if form.durationTime is defined -%}
{{ form_row(form.durationTime) }} {{ form_row(form.durationTime) }}

View File

@@ -6,10 +6,10 @@
{% block personcontent %} {% block personcontent %}
<div class="activity-new"> <div class="activity-new">
<div id="activity"></div> {# <=== vue component #} <div id="activity"></div> {# <=== vue component #}
{% include 'ChillActivityBundle:Activity:new.html.twig' with {'context': 'person'} %} {% include 'ChillActivityBundle:Activity:new.html.twig' with {'context': 'person'} %}
</div> </div>
{% endblock %} {% endblock %}
@@ -17,7 +17,7 @@
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}'); '{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
}); });
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};

View File

@@ -62,21 +62,6 @@
<dt class="inline">{{ 'Date'|trans }}</dt> <dt class="inline">{{ 'Date'|trans }}</dt>
<dd>{{ entity.date|format_date('long') }}</dd> <dd>{{ entity.date|format_date('long') }}</dd>
{% if t.locationVisible %}
<dt class="inline">{{ 'Activity location'|trans }}</dt>
<dd>
{% if entity.location is not null %}
<p>
<span>{{ entity.location.locationType.title|localize_translatable_string }}</span>
{{ entity.location.name }}
</p>
{{ entity.location.address|chill_entity_render_box }}
{% else %}
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
{% endif %}
</dd>
{% endif %}
{% if t.durationTimeVisible %} {% if t.durationTimeVisible %}
<dt class="inline">{{ 'Duration Time'|trans }}</dt> <dt class="inline">{{ 'Duration Time'|trans }}</dt>
<dd>{% if entity.durationTime is not null %} <dd>{% if entity.durationTime is not null %}

View File

@@ -1,6 +1,4 @@
services: services:
Chill\ActivityBundle\DataFixtures\ORM\: Chill\ActivityBundle\DataFixtures\ORM\:
autowire: true
autoconfigure: true
resource: ../../DataFixtures/ORM resource: ../../DataFixtures/ORM
tags: [ 'doctrine.fixture.orm' ] tags: [ 'doctrine.fixture.orm' ]

View File

@@ -4,7 +4,7 @@ namespace Chill\Migrations\Activity;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Type;
/** /**
* Add an "active" column on activitytype table * Add an "active" column on activitytype table

View File

@@ -22,6 +22,7 @@ final class Version20210401090853 extends AbstractMigration
// this up() migration is auto-generated, please modify it to your needs // this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SEQUENCE activitytypecategory_id_seq INCREMENT BY 1 MINVALUE 1 START 1000'); $this->addSql('CREATE SEQUENCE activitytypecategory_id_seq INCREMENT BY 1 MINVALUE 1 START 1000');
$this->addSql('CREATE TABLE activitytypecategory (id INT NOT NULL, name JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE activitytypecategory (id INT NOT NULL, name JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN activitytypecategory.name IS \'(DC2Type:json_array)\'');
$this->addSql('INSERT INTO activitytypecategory VALUES(1, \'{"fr": "Défaut", "en": "Default"}\', true)'); $this->addSql('INSERT INTO activitytypecategory VALUES(1, \'{"fr": "Défaut", "en": "Default"}\', true)');
} }

View File

@@ -51,6 +51,7 @@ final class Version20210408122329 extends AbstractMigration
$this->addSql('ALTER TABLE activitytype ADD socialDataLabel VARCHAR(255) DEFAULT \'\' NOT NULL'); $this->addSql('ALTER TABLE activitytype ADD socialDataLabel VARCHAR(255) DEFAULT \'\' NOT NULL');
$this->addSql('ALTER TABLE activitytype ALTER name SET NOT NULL'); $this->addSql('ALTER TABLE activitytype ALTER name SET NOT NULL');
$this->addSql('ALTER TABLE activitytype ALTER active DROP DEFAULT'); $this->addSql('ALTER TABLE activitytype ALTER active DROP DEFAULT');
$this->addSql('COMMENT ON COLUMN activitytype.name IS \'(DC2Type:json_array)\'');
} }
public function down(Schema $schema) : void public function down(Schema $schema) : void

View File

@@ -6,6 +6,7 @@ Duration time: Durée
Duration Time: Durée Duration Time: Durée
durationTime: durée durationTime: durée
Travel time: Durée de déplacement Travel time: Durée de déplacement
Reasons: Sujets
Attendee: Présence de la personne Attendee: Présence de la personne
attendee: présence de la personne attendee: présence de la personne
list_reasons: liste des sujets list_reasons: liste des sujets
@@ -22,7 +23,6 @@ Update: Mettre à jour
Update activity: Modifier l'activité Update activity: Modifier l'activité
Scope: Cercle Scope: Cercle
Activity data: Données de l'activité Activity data: Données de l'activité
Activity location: Localisation de l'activité
No reason associated: Aucun sujet No reason associated: Aucun sujet
No social issues associated: Aucune problématique sociale No social issues associated: Aucune problématique sociale
No social actions associated: Aucune action d'accompagnement No social actions associated: Aucune action d'accompagnement
@@ -99,13 +99,10 @@ CHILL_ACTIVITY_LIST: Liste des activités
Activities: Activités Activities: Activités
Activity configuration: Configuration des activités Activity configuration: Configuration des activités
Activity configuration menu: Configuration des activités Activity configuration menu: Configuration des activités
Activity types: Types d'activité Activity Types: Types d'activité
Activity type configuration: Configuration des categories d'activités
Activity Reasons: Sujets d'une activité Activity Reasons: Sujets d'une activité
Activity Reasons Category: Catégories de sujet d'activités Activity Reasons Category: Catégories de sujet d'activités
Activity Types Categories: Catégories des types d'activité Activity Types Categories: Catégories des types d'activité
Activity Presences: Presences des activités
# Crud # Crud
crud: crud:

View File

@@ -19,6 +19,8 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @phpstan-ignore-next-line
*/ */
public function load(array $configs, ContainerBuilder $container): void public function load(array $configs, ContainerBuilder $container): void
{ {
@@ -109,4 +111,4 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
] ]
]); ]);
} }
} }

View File

@@ -58,7 +58,7 @@ final class AsideActivityFormType extends AbstractType
$builder $builder
->add('agent', EntityType::class, ->add('agent', EntityType::class,
[ [
'label' => 'For agent', 'label' => 'Agent',
'required' => true, 'required' => true,
'class' => User::class, 'class' => User::class,
'data' => $this->storage->getToken()->getUser(), 'data' => $this->storage->getToken()->getUser(),

View File

@@ -32,7 +32,7 @@ final class AdminMenuBuilder implements \Chill\MainBundle\Routing\LocalMenuBuild
]) ])
->setExtras([ ->setExtras([
'order' => 900, 'order' => 900,
'explain' => "Aside activity type configuration" 'explain' => "Configure aside activities categories"
]); ]);
} else { } else {
$menu $menu

View File

@@ -1,7 +1,7 @@
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %} {% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
{% block admin_content %} {% block admin_content %}
<h1>{{ 'Aside Activity Type List'|trans }}</h1> <h1>{{ 'Aside Activity Type list'|trans }}</h1>
<table class="records_list table table-bordered border-dark"> <table class="records_list table table-bordered border-dark">
<thead> <thead>

View File

@@ -30,9 +30,7 @@ final class CategoryRender implements ChillEntityRenderInterface
{ {
$options = array_merge(self::DEFAULT_ARGS, $options); $options = array_merge(self::DEFAULT_ARGS, $options);
$titles = [ $titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
$this->translatableStringHelper->localize($asideActivityCategory->getTitle()),
];
while ($asideActivityCategory->hasParent()) { while ($asideActivityCategory->hasParent()) {
$asideActivityCategory = $asideActivityCategory->getParent(); $asideActivityCategory = $asideActivityCategory->getParent();

View File

@@ -3,7 +3,6 @@ Show the aside activity: Voir l'activité annexe
Edit the aside activity: Modifier l'activité annexe Edit the aside activity: Modifier l'activité annexe
Remove aside activity: Supprimer l'activité annexe Remove aside activity: Supprimer l'activité annexe
Aside activity: Activité annexe Aside activity: Activité annexe
Aside Activity Type List: Liste des catégories d'activités annexes
Duration time: Durée Duration time: Durée
durationTime: durée durationTime: durée
user_username: nom de l'utilisateur user_username: nom de l'utilisateur
@@ -46,7 +45,7 @@ Back to the list: Retour à la liste
Choose the duration: Choisir la durée Choose the duration: Choisir la durée
Choose a category: Choisir une catégorie Choose a category: Choisir une catégorie
Is active: Actif Is active: Actif
For agent: Pour l'utilisateur Agent: Utilisateur
date: Date date: Date
Duration: Durée Duration: Durée
Note: Note Note: Note
@@ -157,11 +156,6 @@ The activity has been successfully removed.: L'activité a été supprimée.
#Menu #Menu
Create an aside activity: "Créer une activité annexe" Create an aside activity: "Créer une activité annexe"
Aside activity categories: Catégories des activités annexes
Aside activity configuration menu: "Menu de configuration des activités annexes" Aside activity configuration menu: "Menu de configuration des activités annexes"
Aside activity configuration: "Configuration des activités annexes"
Phonecall: "Appel téléphonique" Phonecall: "Appel téléphonique"
# admin
Aside activities: Activités annexes
Aside activity types: Types d'activités annexes
Aside activity type configuration: Configuration des categories d'activités annexes

View File

@@ -97,9 +97,9 @@ class CalendarController extends AbstractController
'calendarItems' => $calendarItems, 'calendarItems' => $calendarItems,
'user' => $user 'user' => $user
]); ]);
}
if ($accompanyingPeriod instanceof AccompanyingPeriod) { } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
$total = $this->calendarRepository->countByAccompanyingPeriod($accompanyingPeriod); $total = $this->calendarRepository->countByAccompanyingPeriod($accompanyingPeriod);
$paginator = $this->paginator->create($total); $paginator = $this->paginator->create($total);
$calendarItems = $this->calendarRepository->findBy( $calendarItems = $this->calendarRepository->findBy(
@@ -117,8 +117,6 @@ class CalendarController extends AbstractController
'paginator' => $paginator 'paginator' => $paginator
]); ]);
} }
throw new \Exception('Unable to list actions.');
} }
/** /**
@@ -236,7 +234,6 @@ class CalendarController extends AbstractController
'professionalsId' => $professionalsId, 'professionalsId' => $professionalsId,
'date' => $entity->getStartDate()->format('Y-m-d'), 'date' => $entity->getStartDate()->format('Y-m-d'),
'durationTime' => $durationTimeInMinutes, 'durationTime' => $durationTimeInMinutes,
'location' => $entity->getLocation()->getId(),
'comment' => $entity->getComment()->getComment(), 'comment' => $entity->getComment()->getComment(),
]; ];

View File

@@ -5,20 +5,21 @@ namespace Chill\CalendarBundle\DataFixtures\ORM;
use Chill\CalendarBundle\Entity\CalendarRange; use Chill\CalendarBundle\Entity\CalendarRange;
use Chill\MainBundle\DataFixtures\ORM\LoadUsers; use Chill\MainBundle\DataFixtures\ORM\LoadUsers;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\UserRepository;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
class LoadCalendarRange extends Fixture implements FixtureGroupInterface, OrderedFixtureInterface class LoadCalendarRange extends Fixture implements FixtureGroupInterface, OrderedFixtureInterface
{ {
public function __construct( public function __construct(
UserRepository $userRepository EntityManagerInterface $em
) { ) {
$this->userRepository = $userRepository; $this->userRepository = $em->getRepository(User::class);
} }
public function getOrder(): int public function getOrder(): int
@@ -36,7 +37,7 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere
public function load(ObjectManager $manager): void public function load(ObjectManager $manager): void
{ {
$arr = range(-50, 50); $arr = range(-50, 50);
print "Creating calendar range ('plage de disponibilités')\n"; print "Creating calendar range ('plage de disponibilités')\n";
$users = $this->userRepository->findAll(); $users = $this->userRepository->findAll();
@@ -69,7 +70,7 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere
->setUser($u) ->setUser($u)
->setStartDate($startEvent) ->setStartDate($startEvent)
->setEndDate($endEvent); ->setEndDate($endEvent);
$manager->persist($calendarRange); $manager->persist($calendarRange);
} }
@@ -78,4 +79,4 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere
} }
$manager->flush(); $manager->flush();
} }
} }

View File

@@ -2,7 +2,6 @@
namespace Chill\CalendarBundle\Entity; namespace Chill\CalendarBundle\Entity;
use Chill\MainBundle\Entity\Location;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
@@ -137,13 +136,6 @@ class Calendar
*/ */
private ?bool $sendSMS; private ?bool $sendSMS;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
* @groups({"read"})
*/
private ?Location $location = null;
public function __construct() public function __construct()
{ {
$this->comment = new CommentEmbeddable(); $this->comment = new CommentEmbeddable();
@@ -362,7 +354,7 @@ class Calendar
return $this; return $this;
} }
public function getPersonsAssociated(): array public function getPersonsAssociated(): array
{ {
if (null !== $this->accompanyingPeriod) { if (null !== $this->accompanyingPeriod) {
$personsAssociated = []; $personsAssociated = [];
@@ -371,11 +363,11 @@ class Calendar
$personsAssociated[] = $participation->getPerson(); $personsAssociated[] = $participation->getPerson();
} }
} }
return $personsAssociated; return $personsAssociated;
} }
return []; return [];
} }
public function getPersonsNotAssociated(): array public function getPersonsNotAssociated(): array
{ {
if (null !== $this->accompanyingPeriod) { if (null !== $this->accompanyingPeriod) {
@@ -415,22 +407,4 @@ class Calendar
])); ]));
} }
/**
* @return Location|null
*/
public function getLocation(): ?Location
{
return $this->location;
}
/**
* @param Location|null $location
* @return Calendar
*/
public function setLocation(?Location $location): Calendar
{
$this->location = $location;
return $this;
}
} }

View File

@@ -34,7 +34,7 @@ class CancelReason
private $canceledBy; private $canceledBy;
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name = []; private $name = [];

View File

@@ -25,7 +25,7 @@ class Invite
private User $user; private User $user;
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private array $status = []; private array $status = [];

View File

@@ -2,7 +2,6 @@
namespace Chill\CalendarBundle\Form; namespace Chill\CalendarBundle\Form;
use Chill\MainBundle\Entity\Location;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -156,7 +155,7 @@ class CalendarType extends AbstractType
} }
)) ))
; ;
$builder->add('calendarRange', HiddenType::class); $builder->add('calendarRange', HiddenType::class);
$builder->get('calendarRange') $builder->get('calendarRange')
->addModelTransformer(new CallbackTransformer( ->addModelTransformer(new CallbackTransformer(
@@ -179,20 +178,6 @@ class CalendarType extends AbstractType
)) ))
; ;
$builder->add('location', HiddenType::class)
->get('location')
->addModelTransformer(new CallbackTransformer(
function (?Location $location): string {
if (null === $location) {
return '';
}
return $location->getId();
},
function (?string $id): ?Location {
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
}
))
;
// $builder->add('invites', HiddenType::class); // $builder->add('invites', HiddenType::class);
// $builder->get('invites') // $builder->get('invites')

View File

@@ -1,92 +0,0 @@
<?php
/*
* Copyright (C) 2018 Champs Libres Cooperative <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\CalendarBundle\Menu;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Chill\TaskBundle\Templating\UI\CountNotificationTask;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
/**
*
* @author Champs-Libres
*/
class UserMenuBuilder implements LocalMenuBuilderInterface
{
/**
*
* @var CountNotificationTask
*/
public $counter;
/*
* @var TokenStorageInterface
*/
public $tokenStorage;
/**
*
* @var TranslatorInterface
*/
public $translator;
/**
*
* @var AuthorizationCheckerInterface
*/
public $authorizationChecker;
public function __construct(
CountNotificationTask $counter,
TokenStorageInterface $tokenStorage,
TranslatorInterface $translator,
AuthorizationCheckerInterface $authorizationChecker
) {
$this->counter = $counter;
$this->tokenStorage = $tokenStorage;
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$user = $this->tokenStorage->getToken()->getUser();
if ($this->authorizationChecker->isGranted('ROLE_USER')){
$menu->addChild("My calendar list", [
'route' => 'chill_calendar_calendar_list'
])
->setExtras([
'order' => 9,
'icon' => 'tasks'
]);
}
}
public static function getMenuIds(): array
{
return [ 'user' ];
}
}

View File

@@ -1,10 +1,9 @@
<template> <template>
<concerned-groups></concerned-groups> <concerned-groups></concerned-groups>
<location></location>
<teleport to="#calendarControls"> <teleport to="#calendarControls">
<calendar-user-selector <calendar-user-selector
v-bind:users="users" v-bind:users="users"
v-bind:calendarEvents="calendarEvents" v-bind:calendarEvents="calendarEvents"
v-bind:updateEventsSource="updateEventsSource" v-bind:updateEventsSource="updateEventsSource"
v-bind:showMyCalendar="showMyCalendar" v-bind:showMyCalendar="showMyCalendar"
v-bind:toggleMyCalendar="toggleMyCalendar" v-bind:toggleMyCalendar="toggleMyCalendar"
@@ -23,7 +22,6 @@
<script> <script>
import ConcernedGroups from 'ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue'; import ConcernedGroups from 'ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue';
import Location from 'ChillActivityAssets/vuejs/Activity/components/Location.vue';
import CalendarUserSelector from '../_components/CalendarUserSelector/CalendarUserSelector.vue'; import CalendarUserSelector from '../_components/CalendarUserSelector/CalendarUserSelector.vue';
import '@fullcalendar/core/vdom'; // solves problem with Vite import '@fullcalendar/core/vdom'; // solves problem with Vite
import frLocale from '@fullcalendar/core/locales/fr'; import frLocale from '@fullcalendar/core/locales/fr';
@@ -37,7 +35,6 @@ export default {
name: "App", name: "App",
components: { components: {
ConcernedGroups, ConcernedGroups,
Location,
CalendarUserSelector, CalendarUserSelector,
FullCalendar FullCalendar
}, },

View File

@@ -1,6 +1,5 @@
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n' import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
import { calendarUserSelectorMessages } from '../_components/CalendarUserSelector/js/i18n'; import { calendarUserSelectorMessages } from '../_components/CalendarUserSelector/js/i18n';
import { activityMessages } from 'ChillActivityAssets/vuejs/Activity/i18n';
const appMessages = { const appMessages = {
fr: { fr: {
@@ -18,7 +17,6 @@ const appMessages = {
Object.assign(appMessages.fr, personMessages.fr); Object.assign(appMessages.fr, personMessages.fr);
Object.assign(appMessages.fr, calendarUserSelectorMessages.fr); Object.assign(appMessages.fr, calendarUserSelectorMessages.fr);
Object.assign(appMessages.fr, activityMessages.fr);
export { export {
appMessages appMessages

View File

@@ -66,11 +66,6 @@ const store = createStore({
setEvents(state, payload) { setEvents(state, payload) {
console.log(payload) console.log(payload)
state.currentEvent = {start: payload.start, end: payload.end} state.currentEvent = {start: payload.start, end: payload.end}
},
// Location
updateLocation(state, value) {
console.log('### mutation: updateLocation', value);
state.activity.location = value;
} }
}, },
actions: { actions: {
@@ -135,14 +130,6 @@ const store = createStore({
commit('setEvents', payload); commit('setEvents', payload);
}, },
// Location
updateLocation({ commit }, value) {
console.log('### action: updateLocation', value);
let hiddenLocation = document.getElementById("chill_calendarbundle_calendar_location");
hiddenLocation.value = value.id;
commit('updateLocation', value);
}
} }
}); });

View File

@@ -38,10 +38,7 @@
{{ form_row(form.calendarRange) }} {{ form_row(form.calendarRange) }}
{% endif %} {% endif %}
{%- if form.location is defined -%} .. location
{{ form_row(form.location) }}
<div id="location"></div>
{% endif %}
{%- if form.comment is defined -%} {%- if form.comment is defined -%}
{{ form_row(form.comment) }} {{ form_row(form.comment) }}
@@ -59,7 +56,7 @@
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a <a
class="btn btn-cancel" class="btn btn-cancel"
{%- if context == 'user' -%} {%- if context == 'user' -%}
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'user_id': user.id } )}}" href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'user_id': user.id } )}}"

View File

@@ -6,10 +6,14 @@
{% block content %} {% block content %}
<div class="calendar-edit"> <div class="calendar-edit">
<div class="row justify-content-center">
<div id="calendar"></div> {# <=== vue component #} <div class="col-md-10 col-xxl">
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'accompanyingCourse'} %}
<div id="calendar"></div> {# <=== vue component #}
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'accompanyingCourse'} %}
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}
@@ -17,7 +21,7 @@
{{ parent() }} {{ parent() }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}'); '{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
}); });
window.entity = {{ entity_json|json_encode|raw }}; window.entity = {{ entity_json|json_encode|raw }};
@@ -36,4 +40,4 @@
{% block block_post_menu %} {% block block_post_menu %}
<div id="calendarControls"></div> <div id="calendarControls"></div>
{% endblock %} {% endblock %}

View File

@@ -4,10 +4,14 @@
{% block content %} {% block content %}
<div class="calendar-edit"> <div class="calendar-edit">
<div class="row justify-content-center">
<div id="calendar"></div> {# <=== vue component #} <div class="col-md-10 col-xxl">
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'user'} %}
<div id="calendar"></div> {# <=== vue component #}
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'user'} %}
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}
@@ -15,7 +19,7 @@
{{ parent() }} {{ parent() }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}'); '{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
}); });
window.entity = {{ entity_json|json_encode|raw }}; window.entity = {{ entity_json|json_encode|raw }};

View File

@@ -34,10 +34,7 @@
{{ form_row(form.endDate) }} {{ form_row(form.endDate) }}
{% endif %} {% endif %}
{%- if form.location is defined -%} .. location
{{ form_row(form.location) }}
<div id="location"></div>
{% endif %}
{%- if form.comment is defined -%} {%- if form.comment is defined -%}
{{ form_row(form.comment) }} {{ form_row(form.comment) }}
@@ -51,9 +48,9 @@
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a <a
class="btn btn-cancel" class="btn btn-cancel"
{%- if context == 'person' -%} {%- if context == 'person' -%}
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'person_id': person.id } )}}" href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'person_id': person.id } )}}"
{%- else -%} {%- else -%}
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'accompanying_period_id': accompanyingCourse.id } )}}" href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'accompanying_period_id': accompanyingCourse.id } )}}"

View File

@@ -6,21 +6,25 @@
{% block content %} {% block content %}
<div class="calendar-new"> <div class="calendar-new">
<div class="row justify-content-center">
<div id="calendar"></div> {# <=== vue component #} <div class="col-md-10 col-xxl">
{% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'accompanyingCourse'} %}
<div id="calendar"></div> {# <=== vue component #}
</div> {% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'accompanyingCourse'} %}
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}'); '{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
}); });
window.entity = {{ entity_json|json_encode|raw }}; window.entity = {{ entity_json|json_encode|raw }};
</script> </script>
{{ encore_entry_script_tags('vue_calendar') }} {{ encore_entry_script_tags('vue_calendar') }}
{% endblock %} {% endblock %}

View File

@@ -36,20 +36,6 @@
<dt class="inline">{{ 'status'|trans }}</dt> <dt class="inline">{{ 'status'|trans }}</dt>
<dd>{{ entity.status }}</dd> <dd>{{ entity.status }}</dd>
<dt class="inline">{{ 'calendar location'|trans }}</dt>
<dd>
{% if entity.location is not null %}
<p>
<span>{{ entity.location.locationType.title|localize_translatable_string }}</span>
{{ entity.location.name }}
</p>
{{ entity.location.address|chill_entity_render_box }}
{% else %}
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
{% endif %}
</dd>
{% if not entity.comment.isEmpty %} {% if not entity.comment.isEmpty %}
<dt class="inline">{{ 'calendar comment'|trans }}</dt> <dt class="inline">{{ 'calendar comment'|trans }}</dt>
<dd>{{ entity.comment|chill_entity_render_box }}</dd> <dd>{{ entity.comment|chill_entity_render_box }}</dd>

View File

@@ -6,8 +6,10 @@
{% block content -%} {% block content -%}
<div class="calendar-show"> <div class="calendar-show">
<div class="row justify-content-center">
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'accompanyingCourse'} %} <div class="col-md-10 col-xxl">
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'accompanyingCourse'} %}
</div> </div>
</div>
</div>
{% endblock content %} {% endblock content %}

View File

@@ -4,8 +4,10 @@
{% block content -%} {% block content -%}
<div class="calendar-show"> <div class="calendar-show">
<div class="row justify-content-center">
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'user'} %} <div class="col-md-10 col-xxl">
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'user'} %}
</div> </div>
</div>
</div>
{% endblock content %} {% endblock content %}

View File

@@ -50,7 +50,10 @@ final class Version20210715141731 extends AbstractMigration
$this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.startDate IS \'(DC2Type:date_immutable)\''); $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.startDate IS \'(DC2Type:date_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.endDate IS \'(DC2Type:date_immutable)\''); $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.endDate IS \'(DC2Type:date_immutable)\'');
$this->addSql('CREATE TABLE chill_calendar.cancel_reason (id INT NOT NULL, active BOOLEAN NOT NULL, canceledBy JSON NOT NULL, name JSON NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_calendar.cancel_reason (id INT NOT NULL, active BOOLEAN NOT NULL, canceledBy JSON NOT NULL, name JSON NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.name IS \'(DC2Type:json_array)\'');
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.canceledBy IS \'(DC2Type:json_array)\'');
$this->addSql('CREATE TABLE chill_calendar.invite (id INT NOT NULL, user_id INT DEFAULT NULL, status JSON NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_calendar.invite (id INT NOT NULL, user_id INT DEFAULT NULL, status JSON NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN chill_calendar.invite.status IS \'(DC2Type:json_array)\'');
$this->addSql('CREATE INDEX IDX_F517FFA7A76ED395 ON chill_calendar.invite (user_id)'); $this->addSql('CREATE INDEX IDX_F517FFA7A76ED395 ON chill_calendar.invite (user_id)');
$this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACD7FA8EF0 FOREIGN KEY (accompanyingPeriod_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACD7FA8EF0 FOREIGN KEY (accompanyingPeriod_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
@@ -71,7 +74,7 @@ final class Version20210715141731 extends AbstractMigration
} }
public function down(Schema $schema): void public function down(Schema $schema): void
{ {
$this->addSql('ALTER TABLE chill_calendar.calendar_to_persons DROP CONSTRAINT FK_AEE94715A40A2C8'); $this->addSql('ALTER TABLE chill_calendar.calendar_to_persons DROP CONSTRAINT FK_AEE94715A40A2C8');
$this->addSql('ALTER TABLE chill_calendar.calendar_to_non_professionals DROP CONSTRAINT FK_FADF2C77A40A2C8'); $this->addSql('ALTER TABLE chill_calendar.calendar_to_non_professionals DROP CONSTRAINT FK_FADF2C77A40A2C8');
$this->addSql('ALTER TABLE chill_calendar.calendar_to_thirdparties DROP CONSTRAINT FK_2BAB7EFDA40A2C8'); $this->addSql('ALTER TABLE chill_calendar.calendar_to_thirdparties DROP CONSTRAINT FK_2BAB7EFDA40A2C8');

View File

@@ -34,5 +34,6 @@ final class Version20210723074557 extends AbstractMigration
$this->addSql('ALTER TABLE chill_calendar.calendar_to_non_professionals ADD CONSTRAINT fk_fadf2c77217bbb47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_calendar.calendar_to_non_professionals ADD CONSTRAINT fk_fadf2c77217bbb47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy TYPE JSON'); $this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy TYPE JSON');
$this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy DROP DEFAULT'); $this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy DROP DEFAULT');
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.canceledby IS \'(DC2Type:json_array)\'');
} }
} }

View File

@@ -14,7 +14,6 @@ start date: début du rendez-vous
end date: fin du rendez-vous end date: fin du rendez-vous
cancel reason: motif d'annulation cancel reason: motif d'annulation
status: Statut du rendez-vous status: Statut du rendez-vous
calendar location: Localistion du rendez-vous
calendar comment: Remarque sur le rendez-vous calendar comment: Remarque sur le rendez-vous
sendSMS: Envoi d'un SMS sendSMS: Envoi d'un SMS
Send s m s: Envoi d'un SMS ? Send s m s: Envoi d'un SMS ?

View File

@@ -28,31 +28,27 @@ class ChillCustomFieldsExtension extends Extension implements PrependExtensionIn
$loader->load('services/fixtures.yaml'); $loader->load('services/fixtures.yaml');
$loader->load('services/controller.yaml'); $loader->load('services/controller.yaml');
$loader->load('services/command.yaml'); $loader->load('services/command.yaml');
//add at least a blank array at 'customizable_entities' options //add at least a blank array at 'customizable_entities' options
//$customizable_entities = (isset($config['customizables_entities']) //$customizable_entities = (isset($config['customizables_entities'])
// && $config['customizables_entities'] !== FALSE) // && $config['customizables_entities'] !== FALSE)
// ? $config['customizables_entities'] : array(); // ? $config['customizables_entities'] : array();
$container->setParameter('chill_custom_fields.customizables_entities', $container->setParameter('chill_custom_fields.customizables_entities',
$config['customizables_entities']); $config['customizables_entities']);
$container->setParameter('chill_custom_fields.show_empty_values', $container->setParameter('chill_custom_fields.show_empty_values',
$config['show_empty_values_in_views']); $config['show_empty_values_in_views']);
} }
/* (non-PHPdoc) /* (non-PHPdoc)
* @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend() * @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
*/ */
public function prepend(ContainerBuilder $container) public function prepend(ContainerBuilder $container)
{ {
// add form layout to twig resources // add form layout to twig resources
$twigConfig = [ $twigConfig['form_themes'][] = 'ChillCustomFieldsBundle:Form:fields.html.twig';
'form_themes' => [
'ChillCustomFieldsBundle:Form:fields.html.twig',
],
];
$container->prependExtensionConfig('twig', $twigConfig); $container->prependExtensionConfig('twig', $twigConfig);
//add routes for custom bundle //add routes for custom bundle
$container->prependExtensionConfig('chill_main', array( $container->prependExtensionConfig('chill_main', array(
'routing' => array( 'routing' => array(

View File

@@ -40,7 +40,7 @@ class CustomField
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
*/ */
private $id; private $id;
/** /**
* @var string * @var string
* *
@@ -61,18 +61,18 @@ class CustomField
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $active = true; private $active = true;
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $options = array(); private $options = array();
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name; private $name;
@@ -82,14 +82,14 @@ class CustomField
* @ORM\Column(type="float") * @ORM\Column(type="float")
*/ */
private $ordering; private $ordering;
/** /**
* @var boolean * @var boolean
* *
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $required = FALSE; private $required = FALSE;
const ONE_TO_ONE = 1; const ONE_TO_ONE = 1;
const ONE_TO_MANY = 2; const ONE_TO_MANY = 2;
@@ -102,7 +102,7 @@ class CustomField
*/ */
private $customFieldGroup; private $customFieldGroup;
/** /**
* Get id * Get id
* *
@@ -112,7 +112,7 @@ class CustomField
{ {
return $this->id; return $this->id;
} }
/** /**
* @return string * @return string
*/ */
@@ -120,15 +120,15 @@ class CustomField
{ {
return $this->slug; return $this->slug;
} }
/** /**
* @return array * @return array
*/ */
function getOptions() function getOptions()
{ {
return $this->options; return $this->options;
} }
/** /**
* Set type * Set type
* *
@@ -152,7 +152,7 @@ class CustomField
return $this->type; return $this->type;
} }
/** /**
* Set active * Set active
* *
@@ -229,9 +229,9 @@ class CustomField
} }
} }
} }
return ''; return '';
} else { } else {
return $this->name; return $this->name;
}; };
@@ -272,7 +272,7 @@ class CustomField
return $this; return $this;
} }
/** /**
* @param $slug * @param $slug
* @return $this * @return $this
@@ -282,20 +282,20 @@ class CustomField
$this->slug = $slug; $this->slug = $slug;
return $this; return $this;
} }
/** /**
* alias for isRequired * alias for isRequired
* *
* @return boolean * @return boolean
*/ */
public function getRequired() public function getRequired()
{ {
return $this->isRequired(); return $this->isRequired();
} }
/** /**
* return true if the field required * return true if the field required
* *
* @return boolean * @return boolean
*/ */
public function isRequired() public function isRequired()
@@ -308,5 +308,5 @@ class CustomField
$this->required = $required; $this->required = $required;
return $this; return $this;
} }
} }

View File

@@ -39,21 +39,21 @@ class Option
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
*/ */
private $id; private $id;
/** /**
* @var string * @var string
* @ORM\Column(type="string", length=15) * @ORM\Column(type="string", length=15)
*/ */
private $key; private $key;
/** /**
* A json representation of text (multilingual) * A json representation of text (multilingual)
* *
* @var array * @var array
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $text; private $text;
/** /**
* @var Collection * @var Collection
* @ORM\OneToMany( * @ORM\OneToMany(
@@ -61,7 +61,7 @@ class Option
* mappedBy="parent") * mappedBy="parent")
*/ */
private $children; private $children;
/** /**
* @var Option * @var Option
* @ORM\ManyToOne( * @ORM\ManyToOne(
@@ -70,19 +70,19 @@ class Option
* @ORM\JoinColumn(nullable=true) * @ORM\JoinColumn(nullable=true)
*/ */
private $parent; private $parent;
/** /**
* @var string * @var string
* @ORM\Column(type="string", length=50, name="internal_key") * @ORM\Column(type="string", length=50, name="internal_key")
*/ */
private $internalKey = ''; private $internalKey = '';
/** /**
* @var boolean * @var boolean
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $active = true; private $active = true;
/** /**
* @return int * @return int
*/ */
@@ -90,7 +90,7 @@ class Option
{ {
return $this->id; return $this->id;
} }
/** /**
* @return string * @return string
*/ */
@@ -98,7 +98,7 @@ class Option
{ {
return $this->key; return $this->key;
} }
/** /**
* @return array * @return array
*/ */
@@ -106,7 +106,7 @@ class Option
{ {
return $this->text; return $this->text;
} }
/** /**
* @return Collection * @return Collection
*/ */
@@ -114,7 +114,7 @@ class Option
{ {
return $this->children; return $this->children;
} }
/** /**
* @return Option * @return Option
*/ */
@@ -122,7 +122,7 @@ class Option
{ {
return $this->parent; return $this->parent;
} }
/** /**
* @param $key * @param $key
* @return $this * @return $this
@@ -132,7 +132,7 @@ class Option
$this->key = $key; $this->key = $key;
return $this; return $this;
} }
/** /**
* @param array $text * @param array $text
* @return $this * @return $this
@@ -142,7 +142,7 @@ class Option
$this->text = $text; $this->text = $text;
return $this; return $this;
} }
/** /**
* @param Option|null $parent * @param Option|null $parent
* @return $this * @return $this
@@ -153,16 +153,16 @@ class Option
$this->key = $parent->getKey(); $this->key = $parent->getKey();
return $this; return $this;
} }
/** /**
* *
* @return boolean * @return boolean
*/ */
public function hasParent() public function hasParent()
{ {
return $this->parent === NULL ? false : true; return $this->parent === NULL ? false : true;
} }
/** /**
* @return string * @return string
*/ */
@@ -170,7 +170,7 @@ class Option
{ {
return $this->internalKey; return $this->internalKey;
} }
/** /**
* @return bool * @return bool
*/ */
@@ -178,7 +178,7 @@ class Option
{ {
return $this->active; return $this->active;
} }
/** /**
* @return bool * @return bool
*/ */
@@ -186,7 +186,7 @@ class Option
{ {
return $this->isActive(); return $this->isActive();
} }
/** /**
* @param $internal_key * @param $internal_key
* @return $this * @return $this
@@ -196,7 +196,7 @@ class Option
$this->internalKey = $internal_key; $this->internalKey = $internal_key;
return $this; return $this;
} }
/** /**
* @param $active * @param $active
* @return $this * @return $this
@@ -206,5 +206,5 @@ class Option
$this->active = $active; $this->active = $active;
return $this; return $this;
} }
} }

View File

@@ -45,7 +45,7 @@ class CustomFieldsGroup
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name; private $name;
@@ -76,15 +76,15 @@ class CustomFieldsGroup
* @var array|null * @var array|null
*/ */
private $activeCustomFields = null; private $activeCustomFields = null;
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $options = array(); private $options = array();
/** /**
* CustomFieldsGroup constructor. * CustomFieldsGroup constructor.
*/ */
@@ -115,7 +115,7 @@ class CustomFieldsGroup
{ {
$this->customFields->removeElement($customField); $this->customFields->removeElement($customField);
} }
/** /**
* @return Collection * @return Collection
*/ */
@@ -142,7 +142,7 @@ class CustomFieldsGroup
return $this->activeCustomFields; return $this->activeCustomFields;
} }
/** /**
* Get id * Get id
* *
@@ -184,9 +184,9 @@ class CustomFieldsGroup
} }
} }
} }
return ''; return '';
} else { } else {
return $this->name; return $this->name;
} }
@@ -214,10 +214,10 @@ class CustomFieldsGroup
{ {
return $this->entity; return $this->entity;
} }
/** /**
* get options array * get options array
* *
* @return array * @return array
*/ */
public function getOptions() public function getOptions()
@@ -227,7 +227,7 @@ class CustomFieldsGroup
/** /**
* set options array * set options array
* *
* @param array $options * @param array $options
* @return CustomFieldsGroup * @return CustomFieldsGroup
*/ */

View File

@@ -145,7 +145,5 @@ class DocGeneratorTemplateController extends AbstractController
} catch (TransferException $e) { } catch (TransferException $e) {
throw $e; throw $e;
} }
throw new \Exception('Unable to generate document.');
} }
} }

View File

@@ -21,7 +21,7 @@ class DocGeneratorTemplate
private int $id; private int $id;
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
*/ */
private array $name = []; private array $name = [];

View File

@@ -22,6 +22,7 @@ final class Version20210812214310 extends AbstractMigration
$this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT'); $this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT');
$this->addSql('ALTER TABLE chill_docgen_template ADD context VARCHAR(255)'); $this->addSql('ALTER TABLE chill_docgen_template ADD context VARCHAR(255)');
$this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\''); $this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\'');
$this->addSql('COMMENT ON COLUMN chill_docgen_template.name IS \'(DC2Type:json_array)\'');
} }
public function down(Schema $schema): void public function down(Schema $schema): void

View File

@@ -4,7 +4,6 @@ namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Form\AccompanyingCourseDocumentType; use Chill\DocStoreBundle\Form\AccompanyingCourseDocumentType;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Privacy\PrivacyEvent;
@@ -17,27 +16,32 @@ use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
/** /**
* Class DocumentAccompanyingCourseController
*
* @package Chill\DocStoreBundle\Controller
* @Route("/{_locale}/parcours/{course}/document") * @Route("/{_locale}/parcours/{course}/document")
*
* TODO faire un controller abstrait ?
*/ */
class DocumentAccompanyingCourseController extends AbstractController class DocumentAccompanyingCourseController extends AbstractController
{ {
/** /**
* *
* @var TranslatorInterface * @var TranslatorInterface
*/ */
protected $translator; protected $translator;
/** /**
* @var EventDispatcherInterface * @var EventDispatcherInterface
*/ */
protected $eventDispatcher; protected $eventDispatcher;
/** /**
* @var AuthorizationHelper * @var AuthorizationHelper
*/ */
protected $authorizationHelper; protected $authorizationHelper;
/** /**
* DocumentAccompanyingCourseController constructor. * DocumentAccompanyingCourseController constructor.
@@ -46,15 +50,15 @@ class DocumentAccompanyingCourseController extends AbstractController
* @param AuthorizationHelper $authorizationHelper * @param AuthorizationHelper $authorizationHelper
*/ */
public function __construct( public function __construct(
TranslatorInterface $translator, TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher, EventDispatcherInterface $eventDispatcher,
AuthorizationHelper $authorizationHelper AuthorizationHelper $authorizationHelper
) { ) {
$this->translator = $translator; $this->translator = $translator;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
} }
/** /**
* @Route("/", name="accompanying_course_document_index", methods="GET") * @Route("/", name="accompanying_course_document_index", methods="GET")
*/ */
@@ -66,7 +70,7 @@ class DocumentAccompanyingCourseController extends AbstractController
throw $this->createNotFoundException('Accompanying period not found'); throw $this->createNotFoundException('Accompanying period not found');
} }
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE, $course); $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $course);
$documents = $em $documents = $em
->getRepository("ChillDocStoreBundle:AccompanyingCourseDocument") ->getRepository("ChillDocStoreBundle:AccompanyingCourseDocument")
@@ -74,7 +78,7 @@ class DocumentAccompanyingCourseController extends AbstractController
['course' => $course], ['course' => $course],
['date' => 'DESC'] ['date' => 'DESC']
); );
return $this->render( return $this->render(
'ChillDocStoreBundle:AccompanyingCourseDocument:index.html.twig', 'ChillDocStoreBundle:AccompanyingCourseDocument:index.html.twig',
[ [
@@ -92,13 +96,13 @@ class DocumentAccompanyingCourseController extends AbstractController
throw $this->createNotFoundException('Accompanying period not found'); throw $this->createNotFoundException('Accompanying period not found');
} }
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $course);
$document = new AccompanyingCourseDocument(); $document = new AccompanyingCourseDocument();
$document->setUser($this->getUser()); $document->setUser($this->getUser());
$document->setCourse($course); $document->setCourse($course);
$document->setDate(new \DateTime('Now')); $document->setDate(new \DateTime('Now'));
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::CREATE, $document);
$form = $this->createForm(AccompanyingCourseDocumentType::class, $document); $form = $this->createForm(AccompanyingCourseDocumentType::class, $document);
$form->handleRequest($request); $form->handleRequest($request);
@@ -110,7 +114,7 @@ class DocumentAccompanyingCourseController extends AbstractController
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$em->persist($document); $em->persist($document);
$em->flush(); $em->flush();
$this->addFlash('success', $this->translator->trans("The document is successfully registered")); $this->addFlash('success', $this->translator->trans("The document is successfully registered"));
return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]); return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]);
@@ -130,8 +134,9 @@ class DocumentAccompanyingCourseController extends AbstractController
*/ */
public function show(AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response public function show(AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response
{ {
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE_DETAILS, $document); $this->denyAccessUnlessGranted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course);
$this->denyAccessUnlessGranted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', $document);
return $this->render( return $this->render(
'ChillDocStoreBundle:AccompanyingCourseDocument:show.html.twig', 'ChillDocStoreBundle:AccompanyingCourseDocument:show.html.twig',
['document' => $document, 'accompanyingCourse' => $course]); ['document' => $document, 'accompanyingCourse' => $course]);
@@ -142,7 +147,8 @@ class DocumentAccompanyingCourseController extends AbstractController
*/ */
public function edit(Request $request, AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response public function edit(Request $request, AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response
{ {
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::UPDATE, $document); $this->denyAccessUnlessGranted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course);
$this->denyAccessUnlessGranted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', $document);
$document->setUser($this->getUser()); $document->setUser($this->getUser());
$document->setDate(new \DateTime('Now')); $document->setDate(new \DateTime('Now'));
@@ -153,17 +159,17 @@ class DocumentAccompanyingCourseController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush(); $this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->translator->trans("The document is successfully updated")); $this->addFlash('success', $this->translator->trans("The document is successfully updated"));
return $this->redirectToRoute( return $this->redirectToRoute(
'accompanying_course_document_edit', 'accompanying_course_document_edit',
['id' => $document->getId(), 'course' => $course->getId()]); ['id' => $document->getId(), 'course' => $course->getId()]);
} elseif ($form->isSubmitted() and !$form->isValid()) { } elseif ($form->isSubmitted() and !$form->isValid()) {
$this->addFlash('error', $this->translator->trans("This form contains errors")); $this->addFlash('error', $this->translator->trans("This form contains errors"));
} }
return $this->render( return $this->render(
'ChillDocStoreBundle:AccompanyingCourseDocument:edit.html.twig', 'ChillDocStoreBundle:AccompanyingCourseDocument:edit.html.twig',
[ [
@@ -178,7 +184,8 @@ class DocumentAccompanyingCourseController extends AbstractController
*/ */
public function delete(Request $request, AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response public function delete(Request $request, AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response
{ {
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::DELETE, $document); $this->denyAccessUnlessGranted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course);
$this->denyAccessUnlessGranted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', $document);
if ($this->isCsrfTokenValid('delete'.$document->getId(), $request->request->get('_token'))) { if ($this->isCsrfTokenValid('delete'.$document->getId(), $request->request->get('_token'))) {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();

View File

@@ -38,7 +38,7 @@ class LoadDocumentACL extends AbstractFixture implements OrderedFixtureInterface
return 35000; return 35000;
} }
public function load(ObjectManager $manager) public function load(ObjectManager $manager)
{ {
foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) { foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) {
@@ -57,15 +57,15 @@ class LoadDocumentACL extends AbstractFixture implements OrderedFixtureInterface
break; break;
case 'administrative': case 'administrative':
case 'direction': case 'direction':
if (in_array($scope->getName()['en'], array('administrative', 'social'), true)) { if (in_array($scope->getName()['en'], array('administrative', 'social'))) {
printf("denying power on %s\n", $scope->getName()['en']); printf("denying power on %s\n", $scope->getName()['en']);
break 2; // we do not want any power on social or administrative break 2; // we do not want any power on social or administrative
} }
break; break;
} }
printf("Adding Person report acl to %s " printf("Adding Person report acl to %s "
. "permission group, scope '%s' \n", . "permission group, scope '%s' \n",
$permissionsGroup->getName(), $scope->getName()['en']); $permissionsGroup->getName(), $scope->getName()['en']);
$roleScopeUpdate = (new RoleScope()) $roleScopeUpdate = (new RoleScope())
->setRole(PersonDocumentVoter::CREATE) ->setRole(PersonDocumentVoter::CREATE)
@@ -83,9 +83,9 @@ class LoadDocumentACL extends AbstractFixture implements OrderedFixtureInterface
$manager->persist($roleScopeCreate); $manager->persist($roleScopeCreate);
$manager->persist($roleScopeDelete); $manager->persist($roleScopeDelete);
} }
} }
$manager->flush(); $manager->flush();
} }

View File

@@ -2,7 +2,6 @@
namespace Chill\DocStoreBundle\DependencyInjection; namespace Chill\DocStoreBundle\DependencyInjection;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
@@ -40,7 +39,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
$this->prependAuthorization($container); $this->prependAuthorization($container);
$this->prependTwig($container); $this->prependTwig($container);
} }
protected function prependRoute(ContainerBuilder $container) protected function prependRoute(ContainerBuilder $container)
{ {
//declare routes for task bundle //declare routes for task bundle
@@ -53,7 +52,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
) )
)); ));
} }
protected function prependAuthorization(ContainerBuilder $container) protected function prependAuthorization(ContainerBuilder $container)
{ {
$container->prependExtensionConfig('security', array( $container->prependExtensionConfig('security', array(
@@ -62,14 +61,10 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
PersonDocumentVoter::CREATE => [PersonDocumentVoter::SEE_DETAILS], PersonDocumentVoter::CREATE => [PersonDocumentVoter::SEE_DETAILS],
PersonDocumentVoter::DELETE => [PersonDocumentVoter::SEE_DETAILS], PersonDocumentVoter::DELETE => [PersonDocumentVoter::SEE_DETAILS],
PersonDocumentVoter::SEE_DETAILS => [PersonDocumentVoter::SEE], PersonDocumentVoter::SEE_DETAILS => [PersonDocumentVoter::SEE],
AccompanyingCourseDocumentVoter::UPDATE => [AccompanyingCourseDocumentVoter::SEE_DETAILS],
AccompanyingCourseDocumentVoter::CREATE => [AccompanyingCourseDocumentVoter::SEE_DETAILS],
AccompanyingCourseDocumentVoter::DELETE => [AccompanyingCourseDocumentVoter::SEE_DETAILS],
AccompanyingCourseDocumentVoter::SEE_DETAILS => [AccompanyingCourseDocumentVoter::SEE],
) )
)); ));
} }
protected function prependTwig(ContainerBuilder $container) protected function prependTwig(ContainerBuilder $container)
{ {
$twigConfig = array( $twigConfig = array(

View File

@@ -34,10 +34,10 @@ class DocumentCategory
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name; private $name;
public function __construct($bundleId, $idInsideBundle) public function __construct($bundleId, $idInsideBundle)
{ {
$this->bundleId = $bundleId; $this->bundleId = $bundleId;

View File

@@ -39,14 +39,14 @@ class StoredObject implements AsyncFileInterface, Document
private $filename; private $filename;
/** /**
* @ORM\Column(type="json", name="key") * @ORM\Column(type="json_array", name="key")
*/ */
private array $keyInfos = []; private array $keyInfos = [];
/** /**
* *
* @var int[] * @var int[]
* @ORM\Column(type="json", name="iv") * @ORM\Column(type="json_array", name="iv")
*/ */
private array $iv = []; private array $iv = [];
@@ -69,7 +69,7 @@ class StoredObject implements AsyncFileInterface, Document
private string $type = ''; private string $type = '';
/** /**
* @ORM\Column(type="json", name="datas") * @ORM\Column(type="json_array", name="datas")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
*/ */
private array $datas = []; private array $datas = [];

View File

@@ -1,10 +1,8 @@
<?php <?php
declare(strict_types=1); namespace Chill\DocStoreBundle\Repository;
namespace Chill\DocStoreBundle\EntityRepository; use App\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;

View File

@@ -3,27 +3,50 @@
*/ */
namespace Chill\DocStoreBundle\Menu; namespace Chill\DocStoreBundle\Menu;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Knp\Menu\MenuItem; use Knp\Menu\MenuItem;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Role\Role;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorInterface;
final class MenuBuilder implements LocalMenuBuilderInterface /**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class MenuBuilder implements LocalMenuBuilderInterface
{ {
private Security $security; /**
protected TranslatorInterface $translator; *
* @var TokenStorageInterface
*/
protected $tokenStorage;
/**
*
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/**
*
* @var TranslatorInterface
*/
protected $translator;
public function __construct( public function __construct(
Security $security, TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper,
TranslatorInterface $translator TranslatorInterface $translator
) { ){
$this->security = $security; $this->tokenStorage = $tokenStorage;
$this->authorizationHelper = $authorizationHelper;
$this->translator = $translator; $this->translator = $translator;
} }
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)
{ {
switch($menuId) { switch($menuId) {
@@ -42,8 +65,11 @@ final class MenuBuilder implements LocalMenuBuilderInterface
{ {
/* @var $person \Chill\PersonBundle\Entity\Person */ /* @var $person \Chill\PersonBundle\Entity\Person */
$person = $parameters['person']; $person = $parameters['person'];
$user = $this->tokenStorage->getToken()->getUser();
if ($this->security->isGranted(PersonDocumentVoter::SEE, $person)) {
if ($this->authorizationHelper->userHasAccess($user,
$person->getCenter(), PersonDocumentVoter::SEE)) {
$menu->addChild($this->translator->trans('Documents'), [ $menu->addChild($this->translator->trans('Documents'), [
'route' => 'person_document_index', 'route' => 'person_document_index',
'routeParameters' => [ 'routeParameters' => [
@@ -54,22 +80,24 @@ final class MenuBuilder implements LocalMenuBuilderInterface
'order'=> 350 'order'=> 350
]); ]);
} }
} }
protected function buildMenuAccompanyingCourse(MenuItem $menu, array $parameters){ protected function buildMenuAccompanyingCourse(MenuItem $menu, array $parameters){
$course = $parameters['accompanyingCourse']; $course = $parameters['accompanyingCourse'];
// $user = $this->tokenStorage->getToken()->getUser();
if ($this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $course)) { //TODO : add condition to check user rights?
$menu->addChild($this->translator->trans('Documents'), [
$menu->addChild($this->translator->trans('Documents'), [
'route' => 'accompanying_course_document_index', 'route' => 'accompanying_course_document_index',
'routeParameters' => [ 'routeParameters' => [
'course' => $course->getId() 'course' => $course->getId()
] ]
]) ])
->setExtras([ ->setExtras([
'order' => 400 'order'=> 400
]); ]);
}
} }
public static function getMenuIds(): array public static function getMenuIds(): array

View File

@@ -3,20 +3,13 @@
namespace Chill\DocStoreBundle\Security\Authorization; namespace Chill\DocStoreBundle\Security\Authorization;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter; use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Security;
/** /**
* *
@@ -29,22 +22,30 @@ class AccompanyingCourseDocumentVoter extends AbstractChillVoter implements Prov
const UPDATE = 'CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE'; const UPDATE = 'CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE';
const DELETE = 'CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE'; const DELETE = 'CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE';
protected LoggerInterface $logger; /**
protected VoterHelperInterface $voterHelper; * @var AuthorizationHelper
protected Security $security; */
protected $authorizationHelper;
/**
* @var AccessDecisionManagerInterface
*/
protected $accessDecisionManager;
/**
* @var LoggerInterface
*/
protected $logger;
public function __construct( public function __construct(
LoggerInterface $logger, AccessDecisionManagerInterface $accessDecisionManager,
Security $security, AuthorizationHelper $authorizationHelper,
VoterHelperFactoryInterface $voterHelperFactory LoggerInterface $logger
) { )
{
$this->accessDecisionManager = $accessDecisionManager;
$this->authorizationHelper = $authorizationHelper;
$this->logger = $logger; $this->logger = $logger;
$this->security = $security;
$this->voterHelper = $voterHelperFactory
->generate(self::class)
->addCheckFor(AccompanyingCourseDocument::class, $this->getRoles())
->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE])
->build();
} }
public function getRoles() public function getRoles()
@@ -60,30 +61,26 @@ class AccompanyingCourseDocumentVoter extends AbstractChillVoter implements Prov
protected function supports($attribute, $subject) protected function supports($attribute, $subject)
{ {
return $this->voterHelper->supports($attribute, $subject);
if (\in_array($attribute, $this->getRoles())) {
return true;
}
return false;
} }
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{ {
$this->logger->debug(sprintf("Voting from %s class", self::class)); return true;
if (!$token->getUser() instanceof User) {
return false;
}
if ($subject instanceof AccompanyingCourseDocument
&& !$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getCourse())) {
return false;
}
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
} }
public function getRolesWithoutScope() public function getRolesWithoutScope()
{ {
return array(); return array();
} }
public function getRolesWithHierarchy() public function getRolesWithHierarchy()
{ {
return ['accompanyingCourseDocument' => $this->getRoles() ]; return ['accompanyingCourseDocument' => $this->getRoles() ];

View File

@@ -19,11 +19,8 @@
namespace Chill\DocStoreBundle\Security\Authorization; namespace Chill\DocStoreBundle\Security\Authorization;
use App\Security\Authorization\VoterHelperFactory;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter; use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
@@ -34,7 +31,6 @@ use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Security;
/** /**
* *
@@ -47,22 +43,25 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
const UPDATE = 'CHILL_PERSON_DOCUMENT_UPDATE'; const UPDATE = 'CHILL_PERSON_DOCUMENT_UPDATE';
const DELETE = 'CHILL_PERSON_DOCUMENT_DELETE'; const DELETE = 'CHILL_PERSON_DOCUMENT_DELETE';
protected AuthorizationHelper $authorizationHelper;
protected AccessDecisionManagerInterface $accessDecisionManager;
protected LoggerInterface $logger; protected LoggerInterface $logger;
protected Security $security;
protected VoterHelperInterface $voterHelper; protected CenterResolverDispatcher $centerResolverDispatcher;
public function __construct( public function __construct(
LoggerInterface $logger, AccessDecisionManagerInterface $accessDecisionManager,
Security $security, AuthorizationHelper $authorizationHelper,
VoterHelperFactoryInterface $voterHelperFactory LoggerInterface $logger//,
) { //CenterResolverDispatcher $centerResolverDispatcher
)
{
$this->accessDecisionManager = $accessDecisionManager;
$this->authorizationHelper = $authorizationHelper;
$this->logger = $logger; $this->logger = $logger;
$this->security = $security; //$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->voterHelper = $voterHelperFactory
->generate(self::class)
->addCheckFor(PersonDocument::class, $this->getRoles())
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->build();
} }
public function getRoles() public function getRoles()
@@ -78,7 +77,16 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
protected function supports($attribute, $subject) protected function supports($attribute, $subject)
{ {
return $this->voterHelper->supports($attribute, $subject); if (\in_array($attribute, $this->getRoles()) && $subject instanceof PersonDocument) {
return true;
}
if ($subject instanceof Person
&& \in_array($attribute, [self::CREATE, self::SEE])) {
return true;
}
return false;
} }
/** /**
@@ -96,12 +104,42 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
return false; return false;
} }
if ($subject instanceof PersonDocument $center = $this->centerResolverDispatcher->resolveCenter($subject);
&& !$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) {
if ($subject instanceof PersonDocument) {
return $this->authorizationHelper->userHasAccess($token->getUser(), $subject, $attribute);
} elseif ($subject instanceof Person) {
return $this->authorizationHelper->userHasAccess($token->getUser(), $subject, $attribute);
} else {
// subject is null. We check that at least one center is reachable
$centers = $this->authorizationHelper
->getReachableCenters($token->getUser(), new Role($attribute));
return count($centers) > 0;
}
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
return false; return false;
} }
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token); return $this->authorizationHelper->userHasAccess(
$token->getUser(),
$subject,
$attribute
);
}
protected function isGranted($attribute, $report, $user = null)
{
if (! $user instanceof User){
return false;
}
return $this->helper->userHasAccess($user, $report, $attribute);
} }
public function getRolesWithoutScope() public function getRolesWithoutScope()

View File

@@ -1,4 +1,8 @@
services: services:
Chill\DocStoreBundle\Menu\MenuBuilder: Chill\DocStoreBundle\Menu\MenuBuilder:
autowire: true arguments:
autoconfigure: true $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
$translator: '@Symfony\Component\Translation\TranslatorInterface'
tags:
- { name: 'chill.menu_builder' }

View File

@@ -17,6 +17,7 @@ final class Version20180605102533 extends AbstractMigration
$this->addSql('CREATE SCHEMA chill_doc'); $this->addSql('CREATE SCHEMA chill_doc');
$this->addSql('CREATE SEQUENCE chill_doc.person_document_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_doc.person_document_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_doc.document_category (bundle_id VARCHAR(255) NOT NULL, id_inside_bundle INT NOT NULL, document_class VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(bundle_id, id_inside_bundle))'); $this->addSql('CREATE TABLE chill_doc.document_category (bundle_id VARCHAR(255) NOT NULL, id_inside_bundle INT NOT NULL, document_class VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(bundle_id, id_inside_bundle))');
$this->addSql('COMMENT ON COLUMN chill_doc.document_category.name IS \'(DC2Type:json_array)\'');
$this->addSql('CREATE TABLE chill_doc.person_document (id INT NOT NULL, category_bundle_id VARCHAR(255) DEFAULT NULL, category_id_inside_bundle INT DEFAULT NULL, scope_id INT DEFAULT NULL, user_id INT DEFAULT NULL, person_id INT DEFAULT NULL, title TEXT NOT NULL, description TEXT NOT NULL, content TEXT NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_doc.person_document (id INT NOT NULL, category_bundle_id VARCHAR(255) DEFAULT NULL, category_id_inside_bundle INT DEFAULT NULL, scope_id INT DEFAULT NULL, user_id INT DEFAULT NULL, person_id INT DEFAULT NULL, title TEXT NOT NULL, description TEXT NOT NULL, content TEXT NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_41DA53C369A0BE36EF62EFC ON chill_doc.person_document (category_bundle_id, category_id_inside_bundle)'); $this->addSql('CREATE INDEX IDX_41DA53C369A0BE36EF62EFC ON chill_doc.person_document (category_bundle_id, category_id_inside_bundle)');
$this->addSql('CREATE INDEX IDX_41DA53C682B5931 ON chill_doc.person_document (scope_id)'); $this->addSql('CREATE INDEX IDX_41DA53C682B5931 ON chill_doc.person_document (scope_id)');
@@ -33,6 +34,6 @@ final class Version20180605102533 extends AbstractMigration
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('DROP SCHEMA chill_doc CASCADE'); $this->addSql('DROP SCHEMA chill_doc CASCADE');
} }
} }

View File

@@ -16,6 +16,9 @@ final class Version20180606133338 extends AbstractMigration
$this->addSql('CREATE SEQUENCE chill_doc.stored_object_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_doc.stored_object_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_doc.stored_object (id INT NOT NULL, filename TEXT NOT NULL, key JSON NOT NULL, iv JSON NOT NULL, creation_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, type TEXT NOT NULL, datas JSON NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_doc.stored_object (id INT NOT NULL, filename TEXT NOT NULL, key JSON NOT NULL, iv JSON NOT NULL, creation_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, type TEXT NOT NULL, datas JSON NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.key IS \'(DC2Type:json_array)\'');
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.iv IS \'(DC2Type:json_array)\'');
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.datas IS \'(DC2Type:json_array)\'');
$this->addSql('ALTER TABLE chill_doc.person_document ADD object_id INT DEFAULT NULL'); $this->addSql('ALTER TABLE chill_doc.person_document ADD object_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.person_document DROP content'); $this->addSql('ALTER TABLE chill_doc.person_document DROP content');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C232D562B FOREIGN KEY (object_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C232D562B FOREIGN KEY (object_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');

View File

@@ -39,12 +39,12 @@ use Symfony\Component\Form\Extension\Core\Type\CollectionType;
*/ */
class ParticipationController extends AbstractController class ParticipationController extends AbstractController
{ {
/** /**
* @var \Psr\Log\LoggerInterface * @var \Psr\Log\LoggerInterface
*/ */
private $logger; private $logger;
/** /**
* ParticipationController constructor. * ParticipationController constructor.
* *
@@ -54,10 +54,10 @@ class ParticipationController extends AbstractController
{ {
$this->logger = $logger; $this->logger = $logger;
} }
/** /**
* Show a form to add a participation * Show a form to add a participation
* *
* This function parse the person_id / persons_ids query argument * This function parse the person_id / persons_ids query argument
* and decide if it should process a single or multiple participation. Depending * and decide if it should process a single or multiple participation. Depending
* on this, the appropriate layout and form. * on this, the appropriate layout and form.
@@ -67,46 +67,46 @@ class ParticipationController extends AbstractController
*/ */
public function newAction(Request $request) public function newAction(Request $request)
{ {
// test the request is correct // test the request is correct
try { try {
$this->testRequest($request); $this->testRequest($request);
} catch (\RuntimeException $ex) { } catch (\RuntimeException $ex) {
$this->logger->warning($ex->getMessage()); $this->logger->warning($ex->getMessage());
return (new Response()) return (new Response())
->setStatusCode(Response::HTTP_BAD_REQUEST) ->setStatusCode(Response::HTTP_BAD_REQUEST)
->setContent($ex->getMessage()); ->setContent($ex->getMessage());
} }
// forward to other action // forward to other action
$single = $request->query->has('person_id'); $single = $request->query->has('person_id');
$multiple = $request->query->has('persons_ids'); $multiple = $request->query->has('persons_ids');
if ($single === true) { if ($single === true) {
return $this->newSingle($request); return $this->newSingle($request);
} }
if ($multiple === true) { if ($multiple === true) {
return $this->newMultiple($request); return $this->newMultiple($request);
} }
// at this point, we miss the required fields. Throw an error // at this point, we miss the required fields. Throw an error
return (new Response()) return (new Response())
->setStatusCode(Response::HTTP_BAD_REQUEST) ->setStatusCode(Response::HTTP_BAD_REQUEST)
->setContent("You must provide either 'person_id' or " ->setContent("You must provide either 'person_id' or "
. "'persons_ids' argument in query"); . "'persons_ids' argument in query");
} }
/** /**
* *
* Test that the query parameters are valid : * Test that the query parameters are valid :
* *
* - an `event_id` is existing ; * - an `event_id` is existing ;
* - `person_id` and `persons_ids` are **not** both present ; * - `person_id` and `persons_ids` are **not** both present ;
* - `persons_id` is correct (contains only numbers and a ','. * - `persons_id` is correct (contains only numbers and a ','.
* *
* @param Request $request * @param Request $request
* @throws \RuntimeException if an error is detected * @throws \RuntimeException if an error is detected
*/ */
@@ -114,64 +114,64 @@ class ParticipationController extends AbstractController
{ {
$single = $request->query->has('person_id'); $single = $request->query->has('person_id');
$multiple = $request->query->has('persons_ids'); $multiple = $request->query->has('persons_ids');
if ($single === true AND $multiple === true) { if ($single === true AND $multiple === true) {
// we are not allowed to have both person_id and persons_ids // we are not allowed to have both person_id and persons_ids
throw new \RuntimeException("You are not allow to provide both 'person_id' and " throw new \RuntimeException("You are not allow to provide both 'person_id' and "
. "'persons_ids' simulaneously"); . "'persons_ids' simulaneously");
} }
if ($multiple === true) { if ($multiple === true) {
$persons_ids = $request->query->get('persons_ids'); $persons_ids = $request->query->get('persons_ids');
if (!preg_match('/^([0-9]{1,},{0,1}){1,}[0-9]{0,}$/', $persons_ids)) { if (!preg_match('/^([0-9]{1,},{0,1}){1,}[0-9]{0,}$/', $persons_ids)) {
throw new \RuntimeException("The persons_ids value should " throw new \RuntimeException("The persons_ids value should "
. "contains int separated by ','"); . "contains int separated by ','");
} }
} }
// check for event_id - this could be removed later // check for event_id - this could be removed later
if ($request->query->has('event_id') === FALSE) { if ($request->query->has('event_id') === FALSE) {
throw new \RuntimeException("You must provide an event_id"); throw new \RuntimeException("You must provide an event_id");
} }
} }
/** /**
* Show a form with single participation. * Show a form with single participation.
* *
* @param Request $request * @param Request $request
* @return Response * @return Response
*/ */
protected function newSingle(Request $request) protected function newSingle(Request $request)
{ {
$returnPath = $request->query->get('return_path') ? $returnPath = $request->query->get('return_path') ?
$request->query->get('return_path') : null; $request->query->get('return_path') : null;
$participation = $this->handleRequest($request, new Participation(), false); $participation = $this->handleRequest($request, new Participation(), false);
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE, $this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
$participation, 'The user is not allowed to create this participation'); $participation, 'The user is not allowed to create this participation');
$form = $this->createCreateForm($participation, $returnPath); $form = $this->createCreateForm($participation, $returnPath);
return $this->render('ChillEventBundle:Participation:new.html.twig', array( return $this->render('ChillEventBundle:Participation:new.html.twig', array(
'form' => $form->createView(), 'form' => $form->createView(),
'participation' => $participation, 'participation' => $participation,
'ignored_participations' => array() // this is required, see self::newMultiple 'ignored_participations' => array() // this is required, see self::newMultiple
)); ));
} }
/** /**
* Show a form with multiple participation. * Show a form with multiple participation.
* *
* If a person is already participating on the event (if a participation with * If a person is already participating on the event (if a participation with
* the same person is associated with the event), the participation is ignored. * the same person is associated with the event), the participation is ignored.
* *
* If all but one participation is ignored, the page show the same response * If all but one participation is ignored, the page show the same response
* than the newSingle function. * than the newSingle function.
* *
* If all participations must be ignored, an error is shown and the method redirects * If all participations must be ignored, an error is shown and the method redirects
* to the event 'show' view with an appropriate flash message. * to the event 'show' view with an appropriate flash message.
* *
@@ -181,24 +181,24 @@ class ParticipationController extends AbstractController
protected function newMultiple(Request $request) protected function newMultiple(Request $request)
{ {
$participations = $this->handleRequest($request, new Participation(), true); $participations = $this->handleRequest($request, new Participation(), true);
$ignoredParticipations = $newParticipations = [];
foreach ($participations as $i => $participation) { foreach ($participations as $i => $participation) {
// check for authorization // check for authorization
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE, $this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
$participation, 'The user is not allowed to create this participation'); $participation, 'The user is not allowed to create this participation');
// create a collection of person's id participating to the event // create a collection of person's id participating to the event
/* @var $peopleParticipating \Doctrine\Common\Collections\ArrayCollection */ /* @var $peopleParticipating \Doctrine\Common\Collections\ArrayCollection */
$peopleParticipating = isset($peopleParticipating) ? $peopleParticipating : $peopleParticipating = isset($peopleParticipating) ? $peopleParticipating :
$participation->getEvent()->getParticipations()->map( $participation->getEvent()->getParticipations()->map(
function(Participation $p) { return $p->getPerson()->getId(); } function(Participation $p) { return $p->getPerson()->getId(); }
); );
// check that the user is not already in the event // check that the user is not already in the event
if ($peopleParticipating->contains($participation->getPerson()->getId())) { if ($peopleParticipating->contains($participation->getPerson()->getId())) {
$ignoredParticipations[] = $participation $ignoredParticipations[] = $participation
->getEvent()->getParticipations()->filter( ->getEvent()->getParticipations()->filter(
function (Participation $p) use ($participation) { function (Participation $p) use ($participation) {
return $p->getPerson()->getId() === $participation->getPerson()->getId(); return $p->getPerson()->getId() === $participation->getPerson()->getId();
} }
)->first(); )->first();
@@ -206,15 +206,15 @@ class ParticipationController extends AbstractController
$newParticipations[] = $participation; $newParticipations[] = $participation;
} }
} }
// this is where the function redirect depending on valid participation // this is where the function redirect depending on valid participation
if ([] === $newParticipations) { if (!isset($newParticipations)) {
// if we do not have nay participants, redirect to event view // if we do not have nay participants, redirect to event view
$this->addFlash('error', $this->get('translator')->trans( $this->addFlash('error', $this->get('translator')->trans(
'None of the requested people may participate ' 'None of the requested people may participate '
. 'the event: they are maybe already participating.')); . 'the event: they are maybe already participating.'));
return $this->redirectToRoute('chill_event__event_show', array( return $this->redirectToRoute('chill_event__event_show', array(
'event_id' => $request->query->getInt('event_id', 0) 'event_id' => $request->query->getInt('event_id', 0)
)); ));
@@ -222,29 +222,24 @@ class ParticipationController extends AbstractController
// if we have multiple participations, show a form with multiple participations // if we have multiple participations, show a form with multiple participations
$form = $this->createCreateFormMultiple($newParticipations); $form = $this->createCreateFormMultiple($newParticipations);
return $this->render( return $this->render('ChillEventBundle:Participation:new-multiple.html.twig', array(
'ChillEventBundle:Participation:new-multiple.html.twig',
[
'form' => $form->createView(), 'form' => $form->createView(),
'participations' => $newParticipations, 'participations' => $newParticipations,
'ignored_participations' => $ignoredParticipations 'ignored_participations' => isset($ignoredParticipations) ? $ignoredParticipations : array()
] ));
); } else {
} // if we have only one participation, show the same form than for single participation
$form = $this->createCreateForm($participation);
// if we have only one participation, show the same form than for single participation
$form = $this->createCreateForm($participation); return $this->render('ChillEventBundle:Participation:new.html.twig', array(
return $this->render(
'ChillEventBundle:Participation:new.html.twig',
[
'form' => $form->createView(), 'form' => $form->createView(),
'participation' => $participation, 'participation' => $participation,
'ignored_participations' => $ignoredParticipations, 'ignored_participations' => isset($ignoredParticipations) ? $ignoredParticipations : array()
] ));
);
}
} }
/** /**
* @param Request $request * @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
@@ -256,32 +251,32 @@ class ParticipationController extends AbstractController
$this->testRequest($request); $this->testRequest($request);
} catch (\RuntimeException $ex) { } catch (\RuntimeException $ex) {
$this->logger->warning($ex->getMessage()); $this->logger->warning($ex->getMessage());
return (new Response()) return (new Response())
->setStatusCode(Response::HTTP_BAD_REQUEST) ->setStatusCode(Response::HTTP_BAD_REQUEST)
->setContent($ex->getMessage()); ->setContent($ex->getMessage());
} }
// forward to other action // forward to other action
$single = $request->query->has('person_id'); $single = $request->query->has('person_id');
$multiple = $request->query->has('persons_ids'); $multiple = $request->query->has('persons_ids');
if ($single === true) { if ($single === true) {
return $this->createSingle($request); return $this->createSingle($request);
} }
if ($multiple === true) { if ($multiple === true) {
return $this->createMultiple($request); return $this->createMultiple($request);
} }
// at this point, we miss the required fields. Throw an error // at this point, we miss the required fields. Throw an error
return (new Response()) return (new Response())
->setStatusCode(Response::HTTP_BAD_REQUEST) ->setStatusCode(Response::HTTP_BAD_REQUEST)
->setContent("You must provide either 'person_id' or " ->setContent("You must provide either 'person_id' or "
. "'persons_ids' argument in query"); . "'persons_ids' argument in query");
} }
/** /**
* @param Request $request * @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
@@ -289,41 +284,41 @@ class ParticipationController extends AbstractController
public function createSingle(Request $request) public function createSingle(Request $request)
{ {
$participation = $this->handleRequest($request, new Participation(), false); $participation = $this->handleRequest($request, new Participation(), false);
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE, $this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
$participation, 'The user is not allowed to create this participation'); $participation, 'The user is not allowed to create this participation');
$form = $this->createCreateForm($participation); $form = $this->createCreateForm($participation);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$em->persist($participation); $em->persist($participation);
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator')->trans( $this->addFlash('success', $this->get('translator')->trans(
'The participation was created' 'The participation was created'
)); ));
if ($request->query->get('return_path')) if ($request->query->get('return_path'))
{ {
return $this->redirect($request->query->get('return_path')); return $this->redirect($request->query->get('return_path'));
} else { } else {
return $this->redirectToRoute('chill_event__event_show', array( return $this->redirectToRoute('chill_event__event_show', array(
'event_id' => $participation->getEvent()->getId() 'event_id' => $participation->getEvent()->getId()
)); ));
} }
} }
return $this->render('ChillEventBundle:Participation:new.html.twig', array( return $this->render('ChillEventBundle:Participation:new.html.twig', array(
'form' => $form->createView(), 'form' => $form->createView(),
'participation' => $participation 'participation' => $participation
)); ));
} }
/** /**
* @param Request $request * @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
@@ -331,56 +326,56 @@ class ParticipationController extends AbstractController
public function createMultiple(Request $request) public function createMultiple(Request $request)
{ {
$participations = $this->handleRequest($request, new Participation(), true); $participations = $this->handleRequest($request, new Participation(), true);
foreach($participations as $participation) { foreach($participations as $participation) {
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE, $this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
$participation, 'The user is not allowed to create this participation'); $participation, 'The user is not allowed to create this participation');
} }
$form = $this->createCreateFormMultiple($participations); $form = $this->createCreateFormMultiple($participations);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$data = $form->getData(); $data = $form->getData();
foreach($data['participations'] as $participation) { foreach($data['participations'] as $participation) {
$em->persist($participation); $em->persist($participation);
} }
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator')->trans( $this->addFlash('success', $this->get('translator')->trans(
'The participations were created' 'The participations were created'
)); ));
return $this->redirectToRoute('chill_event__event_show', array( return $this->redirectToRoute('chill_event__event_show', array(
'event_id' => $participations[0]->getEvent()->getId() 'event_id' => $participations[0]->getEvent()->getId()
)); ));
} }
return $this->render('ChillEventBundle:Participation:new.html.twig', array( return $this->render('ChillEventBundle:Participation:new.html.twig', array(
'form' => $form->createView(), 'form' => $form->createView(),
'participation' => $participation 'participation' => $participation
)); ));
} }
/** /**
* *
* Handle the request to adapt $participation. * Handle the request to adapt $participation.
* *
* If the request is multiple, the $participation object is cloned. * If the request is multiple, the $participation object is cloned.
* Limitations: the $participation should not be persisted. * Limitations: the $participation should not be persisted.
* *
* @param Request $request * @param Request $request
* @param Participation $participation * @param Participation $participation
* @param boolean $multiple (default false) * @param boolean $multiple (default false)
* @return Participation|Participations[] return one single participation if $multiple == false * @return Participation|Participations[] return one single participation if $multiple == false
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the event/person is not found * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the event/person is not found
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException if the user does not have access to event/person * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException if the user does not have access to event/person
*/ */
protected function handleRequest( protected function handleRequest(
Request $request, Request $request,
Participation $participation, Participation $participation,
$multiple = false) $multiple = false)
{ {
@@ -389,37 +384,37 @@ class ParticipationController extends AbstractController
throw new \LogicException("The participation object should not be managed by " throw new \LogicException("The participation object should not be managed by "
. "the object manager using the method ".__METHOD__); . "the object manager using the method ".__METHOD__);
} }
$event_id = $request->query->getInt('event_id', 0); // sf4 check: $event_id = $request->query->getInt('event_id', 0); // sf4 check:
// prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given` // prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given`
if ($event_id !== NULL) { if ($event_id !== NULL) {
$event = $em->getRepository('ChillEventBundle:Event') $event = $em->getRepository('ChillEventBundle:Event')
->find($event_id); ->find($event_id);
if ($event === NULL) { if ($event === NULL) {
throw $this->createNotFoundException('The event with id '.$event_id.' is not found'); throw $this->createNotFoundException('The event with id '.$event_id.' is not found');
} }
$this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, $this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event,
'The user is not allowed to see the event'); 'The user is not allowed to see the event');
$participation->setEvent($event); $participation->setEvent($event);
} }
// this script should be able to handle multiple, so we translate // this script should be able to handle multiple, so we translate
// single person_id in an array // single person_id in an array
$persons_ids = $request->query->has('person_id') ? $persons_ids = $request->query->has('person_id') ?
[$request->query->getInt('person_id', 0)] // sf4 check: [$request->query->getInt('person_id', 0)] // sf4 check:
// prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given` // prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given`
: explode(',', $request->query->get('persons_ids')); : explode(',', $request->query->get('persons_ids'));
$participations = array(); $participations = array();
foreach($persons_ids as $person_id) { foreach($persons_ids as $person_id) {
// clone if we have to reuse the $participation // clone if we have to reuse the $participation
$participation = count($persons_ids) > 1 ? clone $participation : $participation; $participation = count($persons_ids) > 1 ? clone $participation : $participation;
if ($person_id !== NULL) { if ($person_id !== NULL) {
$person = $em->getRepository('ChillPersonBundle:Person') $person = $em->getRepository('ChillPersonBundle:Person')
->find($person_id); ->find($person_id);
@@ -433,13 +428,13 @@ class ParticipationController extends AbstractController
$participation->setPerson($person); $participation->setPerson($person);
} }
$participations[] = $participation; $participations[] = $participation;
} }
return $multiple ? $participations : $participations[0]; return $multiple ? $participations : $participations[0];
} }
/** /**
* @param Participation $participation * @param Participation $participation
* @param null $return_path * @param null $return_path
@@ -447,7 +442,7 @@ class ParticipationController extends AbstractController
*/ */
public function createCreateForm(Participation $participation, $return_path = null) public function createCreateForm(Participation $participation, $return_path = null)
{ {
$form = $this->createForm(ParticipationType::class, $participation, array( $form = $this->createForm(ParticipationType::class, $participation, array(
'event_type' => $participation->getEvent()->getType(), 'event_type' => $participation->getEvent()->getType(),
'action' => $this->generateUrl('chill_event_participation_create', array( 'action' => $this->generateUrl('chill_event_participation_create', array(
@@ -456,14 +451,14 @@ class ParticipationController extends AbstractController
'person_id' => $participation->getPerson()->getId() 'person_id' => $participation->getPerson()->getId()
)) ))
)); ));
$form->add('submit', SubmitType::class, array( $form->add('submit', SubmitType::class, array(
'label' => 'Create' 'label' => 'Create'
)); ));
return $form; return $form;
} }
/** /**
* @param array $participations * @param array $participations
* @return \Symfony\Component\Form\FormInterface * @return \Symfony\Component\Form\FormInterface
@@ -475,7 +470,7 @@ class ParticipationController extends AbstractController
'action' => $this->generateUrl('chill_event_participation_create', array( 'action' => $this->generateUrl('chill_event_participation_create', array(
'event_id' => current($participations)->getEvent()->getId(), 'event_id' => current($participations)->getEvent()->getId(),
'persons_ids' => implode(',', array_map( 'persons_ids' => implode(',', array_map(
function(Participation $p) { return $p->getPerson()->getId(); }, function(Participation $p) { return $p->getPerson()->getId(); },
$participations)) $participations))
) )
))); )));
@@ -486,90 +481,90 @@ class ParticipationController extends AbstractController
), ),
) )
); );
$form->add('submit', SubmitType::class, array( $form->add('submit', SubmitType::class, array(
'label' => 'Create' 'label' => 'Create'
)); ));
return $form; return $form;
} }
/** /**
* show an edit form for the participation with the given id. * show an edit form for the participation with the given id.
* *
* @param int $participation_id * @param int $participation_id
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the participation is not found * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the participation is not found
* @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException if the user is not allowed to edit the participation * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException if the user is not allowed to edit the participation
*/ */
public function editAction($participation_id) public function editAction($participation_id)
{ {
/* @var $participation Participation */ /* @var $participation Participation */
$participation = $this->getDoctrine()->getManager() $participation = $this->getDoctrine()->getManager()
->getRepository('ChillEventBundle:Participation') ->getRepository('ChillEventBundle:Participation')
->find($participation_id); ->find($participation_id);
if ($participation === NULL) { if ($participation === NULL) {
throw $this->createNotFoundException('The participation is not found'); throw $this->createNotFoundException('The participation is not found');
} }
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation, $this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
'You are not allowed to edit this participation'); 'You are not allowed to edit this participation');
$form = $this->createEditForm($participation); $form = $this->createEditForm($participation);
return $this->render('ChillEventBundle:Participation:edit.html.twig', array( return $this->render('ChillEventBundle:Participation:edit.html.twig', array(
'form' => $form->createView(), 'form' => $form->createView(),
'participation' => $participation 'participation' => $participation
)); ));
} }
/** /**
* @param $participation_id * @param $participation_id
* @param Request $request * @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
*/ */
public function updateAction($participation_id, Request $request) public function updateAction($participation_id, Request $request)
{ {
/* @var $participation Participation */ /* @var $participation Participation */
$participation = $this->getDoctrine()->getManager() $participation = $this->getDoctrine()->getManager()
->getRepository('ChillEventBundle:Participation') ->getRepository('ChillEventBundle:Participation')
->find($participation_id); ->find($participation_id);
if ($participation === NULL) { if ($participation === NULL) {
throw $this->createNotFoundException('The participation is not found'); throw $this->createNotFoundException('The participation is not found');
} }
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation, $this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
'You are not allowed to edit this participation'); 'You are not allowed to edit this participation');
$form = $this->createEditForm($participation); $form = $this->createEditForm($participation);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator')->trans( $this->addFlash('success', $this->get('translator')->trans(
'The participation was updated' 'The participation was updated'
)); ));
return $this->redirectToRoute('chill_event__event_show', array( return $this->redirectToRoute('chill_event__event_show', array(
'event_id' => $participation->getEvent()->getId() 'event_id' => $participation->getEvent()->getId()
)); ));
} }
return $this->render('ChillEventBundle:Participation:edit.html.twig', array( return $this->render('ChillEventBundle:Participation:edit.html.twig', array(
'form' => $form->createView(), 'form' => $form->createView(),
'participation' => $participation 'participation' => $participation
)); ));
} }
/** /**
* *
* @param Participation $participation * @param Participation $participation
* @return \Symfony\Component\Form\FormInterface * @return \Symfony\Component\Form\FormInterface
*/ */
@@ -581,14 +576,14 @@ class ParticipationController extends AbstractController
'participation_id' => $participation->getId() 'participation_id' => $participation->getId()
)) ))
)); ));
$form->add('submit', SubmitType::class, array( $form->add('submit', SubmitType::class, array(
'label' => 'Edit' 'label' => 'Edit'
)); ));
return $form; return $form;
} }
/** /**
* show a form to edit multiple participation for the same event. * show a form to edit multiple participation for the same event.
* *
@@ -599,84 +594,84 @@ class ParticipationController extends AbstractController
{ {
$event = $this->getDoctrine()->getRepository('ChillEventBundle:Event') $event = $this->getDoctrine()->getRepository('ChillEventBundle:Event')
->find($event_id); ->find($event_id);
if ($event === null) { if ($event === null) {
throw $this->createNotFoundException("The event with id $event_id is not found"); throw $this->createNotFoundException("The event with id $event_id is not found");
} }
// check for ACL, on Event level and on Participation Level // check for ACL, on Event level and on Participation Level
$this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, "You are not allowed " $this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, "You are not allowed "
. "to see this event"); . "to see this event");
foreach ($event->getParticipations() as $participation) { foreach ($event->getParticipations() as $participation) {
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation, $this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
"You are not allowed to update participation with id ".$participation->getId()); "You are not allowed to update participation with id ".$participation->getId());
} }
switch ($event->getParticipations()->count()) { switch ($event->getParticipations()->count()) {
case 0: case 0:
// if there aren't any participation, redirect to the 'show' view with an add flash // if there aren't any participation, redirect to the 'show' view with an add flash
$this->addFlash('warning', $this->get('translator') $this->addFlash('warning', $this->get('translator')
->trans( "There are no participation to edit for this event")); ->trans( "There are no participation to edit for this event"));
return $this->redirectToRoute('chill_event__event_show', return $this->redirectToRoute('chill_event__event_show',
array('event_id' => $event->getId())); array('event_id' => $event->getId()));
case 1: case 1:
// redirect to the form for a single participation // redirect to the form for a single participation
return $this->redirectToRoute('chill_event_participation_edit', array( return $this->redirectToRoute('chill_event_participation_edit', array(
'participation_id' => $event->getParticipations()->current()->getId() 'participation_id' => $event->getParticipations()->current()->getId()
)); ));
} }
$form = $this->createEditFormMultiple($event->getParticipations(), $event); $form = $this->createEditFormMultiple($event->getParticipations(), $event);
return $this->render('ChillEventBundle:Participation:edit-multiple.html.twig', array( return $this->render('ChillEventBundle:Participation:edit-multiple.html.twig', array(
'event' => $event, 'event' => $event,
'participations' => $event->getParticipations(), 'participations' => $event->getParticipations(),
'form' => $form->createView() 'form' => $form->createView()
)); ));
} }
public function updateMultipleAction($event_id, Request $request) public function updateMultipleAction($event_id, Request $request)
{ {
/* @var $event \Chill\EventBundle\Entity\Event */ /* @var $event \Chill\EventBundle\Entity\Event */
$event = $this->getDoctrine()->getRepository('ChillEventBundle:Event') $event = $this->getDoctrine()->getRepository('ChillEventBundle:Event')
->find($event_id); ->find($event_id);
if ($event === null) { if ($event === null) {
throw $this->createNotFoundException("The event with id $event_id is not found"); throw $this->createNotFoundException("The event with id $event_id is not found");
} }
$this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, "You are not allowed " $this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, "You are not allowed "
. "to see this event"); . "to see this event");
foreach ($event->getParticipations() as $participation) { foreach ($event->getParticipations() as $participation) {
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation, $this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
"You are not allowed to update participation with id ".$participation->getId()); "You are not allowed to update participation with id ".$participation->getId());
} }
$form = $this->createEditFormMultiple($event->getParticipations(), $event); $form = $this->createEditFormMultiple($event->getParticipations(), $event);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush(); $this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->get('translator')->trans("The participations " $this->addFlash('success', $this->get('translator')->trans("The participations "
. "have been successfully updated.")); . "have been successfully updated."));
return $this->redirectToRoute('chill_event__event_show', return $this->redirectToRoute('chill_event__event_show',
array('event_id' => $event->getId())); array('event_id' => $event->getId()));
} }
return $this->render('ChillEventBundle:Participation:edit-multiple.html.twig', array( return $this->render('ChillEventBundle:Participation:edit-multiple.html.twig', array(
'event' => $event, 'event' => $event,
'participations' => $event->getParticipations(), 'participations' => $event->getParticipations(),
'form' => $form->createView() 'form' => $form->createView()
)); ));
} }
/** /**
* @param ArrayIterator $participations * @param ArrayIterator $participations
* @param Event $event * @param Event $event
@@ -684,14 +679,14 @@ class ParticipationController extends AbstractController
*/ */
protected function createEditFormMultiple(ArrayIterator $participations, Event $event) protected function createEditFormMultiple(ArrayIterator $participations, Event $event)
{ {
$form = $this->createForm(\Symfony\Component\Form\Extension\Core\Type\FormType::class, $form = $this->createForm(\Symfony\Component\Form\Extension\Core\Type\FormType::class,
array('participations' => $participations), array( array('participations' => $participations), array(
'method' => 'POST', 'method' => 'POST',
'action' => $this->generateUrl('chill_event_participation_update_multiple', array( 'action' => $this->generateUrl('chill_event_participation_update_multiple', array(
'event_id' => $event->getId() 'event_id' => $event->getId()
)) ))
)); ));
$form->add('participations', CollectionType::class, array( $form->add('participations', CollectionType::class, array(
'entry_type' => ParticipationType::class, 'entry_type' => ParticipationType::class,
'entry_options' => array( 'entry_options' => array(
@@ -699,14 +694,14 @@ class ParticipationController extends AbstractController
), ),
) )
); );
$form->add('submit', SubmitType::class, array( $form->add('submit', SubmitType::class, array(
'label' => 'Update' 'label' => 'Update'
)); ));
return $form; return $form;
} }
/** /**
* @param integer $participation_id * @param integer $participation_id
* @param Request $request * @param Request $request
@@ -718,28 +713,28 @@ class ParticipationController extends AbstractController
$participation = $em->getRepository('ChillEventBundle:Participation')->findOneBy([ $participation = $em->getRepository('ChillEventBundle:Participation')->findOneBy([
'id' => $participation_id 'id' => $participation_id
]); ]);
if (! $participation) { if (! $participation) {
throw $this->createNotFoundException('Unable to find participation.'); throw $this->createNotFoundException('Unable to find participation.');
} }
/** @var Event $event */ /** @var Event $event */
$event = $participation->getEvent(); $event = $participation->getEvent();
$form = $this->createDeleteForm($participation_id); $form = $this->createDeleteForm($participation_id);
if ($request->getMethod() === Request::METHOD_DELETE) { if ($request->getMethod() === Request::METHOD_DELETE) {
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isValid()) { if ($form->isValid()) {
$em->remove($participation); $em->remove($participation);
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator') $this->addFlash('success', $this->get('translator')
->trans("The participation has been sucessfully removed") ->trans("The participation has been sucessfully removed")
); );
return $this->redirectToRoute('chill_event__event_show', [ return $this->redirectToRoute('chill_event__event_show', [
'event_id' => $event->getId() 'event_id' => $event->getId()
]); ]);
@@ -749,9 +744,9 @@ class ParticipationController extends AbstractController
'event_id' => $event->getId(), 'event_id' => $event->getId(),
'delete_form' => $form->createView() 'delete_form' => $form->createView()
]); ]);
} }
/** /**
* @param $participation_id * @param $participation_id
* @return \Symfony\Component\Form\FormInterface * @return \Symfony\Component\Form\FormInterface
@@ -767,5 +762,5 @@ class ParticipationController extends AbstractController
->getForm() ->getForm()
; ;
} }
} }

View File

@@ -50,19 +50,19 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface
break; break;
case 'administrative': case 'administrative':
case 'direction': case 'direction':
if (in_array($scope->getName()['en'], array('administrative', 'social'), true)) { if (in_array($scope->getName()['en'], array('administrative', 'social'))) {
break 2; // we do not want any power on social or administrative break 2; // we do not want any power on social or administrative
} }
break; break;
} }
printf("Adding CHILL_EVENT_UPDATE & CHILL_EVENT_CREATE " printf("Adding CHILL_EVENT_UPDATE & CHILL_EVENT_CREATE "
. "& CHILL_EVENT_PARTICIPATION_UPDATE & CHILL_EVENT_PARTICIPATION_CREATE " . "& CHILL_EVENT_PARTICIPATION_UPDATE & CHILL_EVENT_PARTICIPATION_CREATE "
. "& CHILL_EVENT_SEE & CHILL_EVENT_SEE_DETAILS " . "& CHILL_EVENT_SEE & CHILL_EVENT_SEE_DETAILS "
. "to %s " . "to %s "
. "permission group, scope '%s' \n", . "permission group, scope '%s' \n",
$permissionsGroup->getName(), $scope->getName()['en']); $permissionsGroup->getName(), $scope->getName()['en']);
$roleScopeUpdate = (new RoleScope()) $roleScopeUpdate = (new RoleScope())
->setRole('CHILL_EVENT_UPDATE') ->setRole('CHILL_EVENT_UPDATE')
->setScope($scope); ->setScope($scope);
@@ -71,7 +71,7 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface
->setScope($scope); ->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeUpdate); $permissionsGroup->addRoleScope($roleScopeUpdate);
$permissionsGroup->addRoleScope($roleScopeUpdate2); $permissionsGroup->addRoleScope($roleScopeUpdate2);
$roleScopeCreate = (new RoleScope()) $roleScopeCreate = (new RoleScope())
->setRole('CHILL_EVENT_CREATE') ->setRole('CHILL_EVENT_CREATE')
->setScope($scope); ->setScope($scope);
@@ -80,7 +80,7 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface
->setScope($scope); ->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeCreate); $permissionsGroup->addRoleScope($roleScopeCreate);
$permissionsGroup->addRoleScope($roleScopeCreate2); $permissionsGroup->addRoleScope($roleScopeCreate2);
$roleScopeSee = (new RoleScope()) $roleScopeSee = (new RoleScope())
->setRole('CHILL_EVENT_SEE') ->setRole('CHILL_EVENT_SEE')
->setScope($scope); ->setScope($scope);
@@ -89,7 +89,7 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface
->setScope($scope); ->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeSee); $permissionsGroup->addRoleScope($roleScopeSee);
$permissionsGroup->addRoleScope($roleScopeSee2); $permissionsGroup->addRoleScope($roleScopeSee2);
$manager->persist($roleScopeUpdate); $manager->persist($roleScopeUpdate);
$manager->persist($roleScopeUpdate2); $manager->persist($roleScopeUpdate2);
$manager->persist($roleScopeCreate); $manager->persist($roleScopeCreate);
@@ -97,9 +97,9 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface
$manager->persist($roleScopeSee); $manager->persist($roleScopeSee);
$manager->persist($roleScopeSee2); $manager->persist($roleScopeSee2);
} }
} }
$manager->flush(); $manager->flush();
} }

View File

@@ -46,7 +46,7 @@ class EventType
/** /**
* @var array * @var array
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name; private $name;
@@ -55,7 +55,7 @@ class EventType
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $active; private $active;
/** /**
* @var Collection * @var Collection
* @ORM\OneToMany( * @ORM\OneToMany(

View File

@@ -44,7 +44,7 @@ class Role
/** /**
* @var array * @var array
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name; private $name;
@@ -53,7 +53,7 @@ class Role
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $active; private $active;
/** /**
* @var EventType * @var EventType
* @ORM\ManyToOne( * @ORM\ManyToOne(

View File

@@ -41,19 +41,19 @@ class Status
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
*/ */
private $id; private $id;
/** /**
* @var array * @var array
* @ORM\Column(type="json") * @ORM\Column(type="json_array")
*/ */
private $name; private $name;
/** /**
* @var boolean * @var boolean
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $active; private $active;
/** /**
* @var EventType * @var EventType
* @ORM\ManyToOne( * @ORM\ManyToOne(

View File

@@ -504,8 +504,6 @@ class ApiController extends AbstractCRUDController
$this->getContextForSerializationPostAlter($action, $request, $_format, $entity, [$postedData]) $this->getContextForSerializationPostAlter($action, $request, $_format, $entity, [$postedData])
); );
} }
throw new \Exception('Unable to handle such request method.');
} }
/** /**

View File

@@ -342,19 +342,13 @@ class CRUDController extends AbstractController
*/ */
protected function buildQueryEntities(string $action, Request $request) protected function buildQueryEntities(string $action, Request $request)
{ {
$query = $this->getDoctrine()->getManager() return $this->getDoctrine()->getManager()
->createQueryBuilder() ->createQueryBuilder()
->select('e') ->select('e')
->from($this->getEntityClass(), 'e') ->from($this->getEntityClass(), 'e')
; ;
$this->customizeQuery($action, $request, $query);
return $query;
} }
protected function customizeQuery(string $action, Request $request, $query): void {}
/** /**
* Query the entity. * Query the entity.
* *

View File

@@ -8,7 +8,6 @@ use Chill\MainBundle\Security\Authorization\ChillVoterInterface;
use Chill\MainBundle\Security\ProvideRoleInterface; use Chill\MainBundle\Security\ProvideRoleInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverInterface; use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface; use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass;
@@ -39,8 +38,6 @@ class ChillMainBundle extends Bundle
->addTag('chill_main.center_resolver'); ->addTag('chill_main.center_resolver');
$container->registerForAutoconfiguration(ScopeResolverInterface::class) $container->registerForAutoconfiguration(ScopeResolverInterface::class)
->addTag('chill_main.scope_resolver'); ->addTag('chill_main.scope_resolver');
$container->registerForAutoconfiguration(ChillEntityRenderInterface::class)
->addTag('chill.render_entity');
$container->addCompilerPass(new SearchableServicesCompilerPass()); $container->addCompilerPass(new SearchableServicesCompilerPass());
$container->addCompilerPass(new ConfigConsistencyCompilerPass()); $container->addCompilerPass(new ConfigConsistencyCompilerPass());

View File

@@ -10,18 +10,18 @@ use Symfony\Component\Console\Output\OutputInterface;
/** /**
* *
* @author Julien Fastré <julien.fastre@champs-libres.coop * @author Julien Fastré <julien.fastre@champs-libres.coop
* *
*/ */
class LoadCountriesCommand extends Command class LoadCountriesCommand extends Command
{ {
/** /**
* @var EntityManager * @var EntityManager
*/ */
private $entityManager; private $entityManager;
private $availableLanguages; private $availableLanguages;
/** /**
* LoadCountriesCommand constructor. * LoadCountriesCommand constructor.
* *
@@ -34,7 +34,7 @@ class LoadCountriesCommand extends Command
$this->availableLanguages=$availableLanguages; $this->availableLanguages=$availableLanguages;
parent::__construct(); parent::__construct();
} }
/* /*
* (non-PHPdoc) * (non-PHPdoc)
* @see \Symfony\Component\Console\Command\Command::configure() * @see \Symfony\Component\Console\Command\Command::configure()
@@ -45,7 +45,7 @@ class LoadCountriesCommand extends Command
->setDescription('Load or update countries in db. This command does not delete existing countries, '. ->setDescription('Load or update countries in db. This command does not delete existing countries, '.
'but will update names according to available languages'); 'but will update names according to available languages');
} }
/* /*
* (non-PHPdoc) * (non-PHPdoc)
* @see \Symfony\Component\Console\Command\Command::execute() * @see \Symfony\Component\Console\Command\Command::execute()
@@ -54,44 +54,43 @@ class LoadCountriesCommand extends Command
{ {
$countries = static::prepareCountryList($this->availableLanguages); $countries = static::prepareCountryList($this->availableLanguages);
$em = $this->entityManager; $em = $this->entityManager;
foreach($countries as $country) { foreach($countries as $country) {
$countryStored = $em->getRepository('ChillMainBundle:Country') $countryStored = $em->getRepository('ChillMainBundle:Country')
->findOneBy(array('countryCode' => $country->getCountryCode())); ->findOneBy(array('countryCode' => $country->getCountryCode()));
if (NULL === $countryStored) { if (NULL === $countryStored) {
$em->persist($country); $em->persist($country);
} else { } else {
$countryStored->setName($country->getName()); $countryStored->setName($country->getName());
} }
} }
$em->flush(); $em->flush();
} }
public static function prepareCountryList($languages) public static function prepareCountryList($languages)
{ {
$regionBundle = Intl::getRegionBundle(); $regionBundle = Intl::getRegionBundle();
$countries = [];
foreach ($languages as $language) { foreach ($languages as $language) {
$countries[$language] = $regionBundle->getCountryNames($language); $countries[$language] = $regionBundle->getCountryNames($language);
} }
$countryEntities = array(); $countryEntities = array();
foreach ($countries[$languages[0]] as $countryCode => $name) { foreach ($countries[$languages[0]] as $countryCode => $name) {
$names = array(); $names = array();
foreach ($languages as $language) { foreach ($languages as $language) {
$names[$language] = $countries[$language][$countryCode]; $names[$language] = $countries[$language][$countryCode];
} }
$country = new \Chill\MainBundle\Entity\Country(); $country = new \Chill\MainBundle\Entity\Country();
$country->setName($names)->setCountryCode($countryCode); $country->setName($names)->setCountryCode($countryCode);
$countryEntities[] = $country; $countryEntities[] = $country;
} }
return $countryEntities; return $countryEntities;
} }
} }

View File

@@ -3,7 +3,6 @@
namespace Chill\MainBundle\Controller; namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
@@ -21,17 +20,8 @@ class AddressReferenceAPIController extends ApiController
$qb->where('e.postcode = :postal_code') $qb->where('e.postcode = :postal_code')
->setParameter('postal_code', $request->query->get('postal_code')); ->setParameter('postal_code', $request->query->get('postal_code'));
} }
}
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
$query->addOrderBy('e.street', 'ASC');
$query->addOrderBy('e.streetNumber', 'ASC');
return $query;
} }
} }

View File

@@ -41,9 +41,4 @@ class AdminController extends AbstractController
return $this->render('@ChillMain/Admin/layout_permissions.html.twig'); return $this->render('@ChillMain/Admin/layout_permissions.html.twig');
} }
public function indexLocationsAction()
{
return $this->render('@ChillMain/Admin/layout_location.html.twig');
}
} }

View File

@@ -1,36 +0,0 @@
<?php
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
/**
* Class LocationApiController
*
* @package Chill\MainBundle\Controller
* @author Champs Libres
*/
class LocationApiController extends ApiController
{
public function customizeQuery(string $action, Request $request, $query): void
{
$query->andWhere($query->expr()->orX(
$query->expr()->andX(
$query->expr()->eq('e.createdBy', ':user'),
$query->expr()->gte('e.createdAt', ':dateBefore')
),
$query->expr()->andX(
$query->expr()->eq('e.availableForUsers', "'TRUE'"),
$query->expr()->eq('e.active', "'TRUE'"),
$query->expr()->isNotNull('e.name'),
$query->expr()->neq('e.name', ':emptyString'),
)
))
->setParameters([
'user' => $this->getUser(),
'dateBefore' => (new \DateTime())->sub(new \DateInterval('P6M')),
'emptyString' => '',
]);
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Symfony\Component\HttpFoundation\Request;
class LocationController extends CRUDController
{
protected function customizeQuery(string $action, Request $request, $query): void
{
$query->where('e.availableForUsers = true'); //TODO not working
}
protected function createEntity(string $action, Request $request): object
{
$entity = parent::createEntity($action, $request);
$entity->setAvailableForUsers(true);
return $entity;
}
}

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