Merge branch 'master' into household_filiation

This commit is contained in:
Mathieu Jaumotte 2021-11-12 17:20:46 +01:00
commit 52dc89c06f
298 changed files with 8036 additions and 3185 deletions

View File

@ -15,7 +15,9 @@ 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 doctrine:fixtures:load -n - php -d memory_limit=2G tests/app/bin/console cache:clear --env=dev
- 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

@ -0,0 +1,24 @@
# 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

@ -11,6 +11,57 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* 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)
## 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
* [person]: double parentheses removed around age in banner + whitespace
### 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
@ -21,13 +72,14 @@ and this project adheres to
* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location. * [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 * [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 * 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
@ -87,7 +139,7 @@ and this project adheres to
## Test released ## Test released
<!-- <!--
Coming soon... Coming soon...
@ -100,4 +152,4 @@ DO NOT ADD unreleased items here. Add them under "Unreleased" title
## Stable releases ## Stable releases
No stable releases for v2+ No stable releases for v2+

View File

@ -54,6 +54,9 @@
"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",

1542
phpstan-baseline.neon Normal file

File diff suppressed because it is too large Load Diff

21
phpstan.neon.dist Normal file
View File

@ -0,0 +1,21 @@
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,6 +242,11 @@ 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

@ -87,7 +87,9 @@ 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

@ -158,7 +158,7 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
private ?Collection $thirdParties = null; private ?Collection $thirdParties = null;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject") * @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"})
*/ */
private Collection $documents; private Collection $documents;

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_array") * @ORM\Column(type="json")
*/ */
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_array") * @ORM\Column(type="json")
*/ */
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_array") * @ORM\Column(type="json")
*/ */
private array $name = []; private array $name = [];

View File

@ -40,7 +40,7 @@ class ActivityTypeCategory
private ?int $id; private ?int $id;
/** /**
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
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) public function getLabels($key, array $values, $data): \Closure
{ {
// 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) use ($data) { return function($value): string {
if ($value === '_header') { if ($value === '_header') {
return 'Activity type'; return 'Activity type';
} }
@ -120,12 +120,11 @@ class ActivityTypeAggregator implements AggregatorInterface
return $this->stringHelper->localize($t->getName()); return $this->stringHelper->localize($t->getName());
}; };
} }
public function getQueryKeys($data) public function getQueryKeys($data): array
{ {
return array(self::KEY); return [self::KEY];
} }
} }

View File

@ -313,7 +313,7 @@ class ActivityType extends AbstractType
} }
return $location->getId(); return $location->getId();
}, },
function (?string $id): Location { function (?string $id): ?Location {
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]); return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
} }
)) ))

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\Type::BOOLEAN); $qb->setParameter('active', true, \Doctrine\DBAL\Types\Types::BOOLEAN);
} }
} }

View File

@ -2,29 +2,27 @@
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(
TokenStorageInterface $tokenStorage, Security $security,
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
{ {
@ -35,7 +33,8 @@ 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

@ -0,0 +1,48 @@
<?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

@ -20,7 +20,7 @@
v-model="location"> v-model="location">
</VueMultiselect> </VueMultiselect>
<new-location @saveNewLocation="saveNewLocation"></new-location> <new-location v-bind:locations="locations"></new-location>
</div> </div>
</div> </div>
</teleport> </teleport>
@ -30,7 +30,7 @@
import { mapState } from "vuex"; import { mapState } from "vuex";
import VueMultiselect from 'vue-multiselect'; import VueMultiselect from 'vue-multiselect';
import NewLocation from './Location/NewLocation.vue'; import NewLocation from './Location/NewLocation.vue';
import { getLocations, postLocation } from '../api.js'; import { getLocations } from '../api.js';
export default { export default {
name: "Location", name: "Location",
@ -66,30 +66,7 @@ export default {
})) }))
}, },
customLabel(value) { customLabel(value) {
return `${value.locationType.title.fr} ${value.name}`; return `${value.locationType.title.fr} ${value.name ? value.name : ''}`;
},
saveNewLocation(selected) {
console.log('saveNewLocation', selected);
let body = {
type: 'location',
name: selected.name,
address: {
id: selected.addressId
},
locationType: {
id: selected.type,
type: 'location-type'
},
phonenumber1: selected.phonenumber1,
phonenumber2: selected.phonenumber2,
email: selected.email,
}
postLocation(body).then(location => new Promise(resolve => {
console.log('postLocation', location);
this.locations.push(location);
this.$store.dispatch('updateLocation', location);
resolve();
}));
} }
} }
} }

View File

@ -1,75 +1,87 @@
<template> <template>
<div>
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-create" @click="openModal">
{{ $t('activity.create_new_location') }}
</a>
</li>
</ul>
<ul class="record_actions"> <teleport to="body">
<li> <modal v-if="modal.showModal"
<a class="btn btn-sm btn-create" @click="openModal"> :modalDialogClass="modal.modalDialogClass"
{{ $t('activity.create_new_location') }} @close="modal.showModal = false">
</a>
</li>
</ul>
<teleport to="body"> <template v-slot:header>
<modal v-if="modal.showModal" <h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
:modalDialogClass="modal.modalDialogClass" </template>
@close="modal.showModal = false"> <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>
<template v-slot:header> <div class="form-floating mb-3">
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3> <select class="form-select form-select-lg" id="type" required v-model="selectType">
</template> <option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
<template v-slot:body> <option v-for="t in locationTypes" :value="t" :key="t.id">
{{ t.title.fr }}
</option>
</select>
<label>{{ $t('activity.location_fields.type') }}</label>
</div>
<add-address <div class="form-floating mb-3">
:context="addAddress.context" <input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
:options="addAddress.options" <label for="name">{{ $t('activity.location_fields.name') }}</label>
:addressChangedCallback="submitNewAddress" </div>
ref="addAddress">
</add-address>
<div class="form-floating mb-3"> <add-address
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder /> :context="addAddress.context"
<label for="name">{{ $t('activity.location_fields.name') }}</label> :options="addAddress.options"
</div> :addressChangedCallback="submitNewAddress"
v-if="showAddAddress"
ref="addAddress">
</add-address>
<div class="form-floating mb-3"> <div class="form-floating mb-3" v-if="showContactData">
<select class="form-select form-select-lg" id="type" v-model="selectType"> <input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option> <label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label>
<option v-for="t in locationTypes" :value="t.id"> </div>
{{ t.title.fr }} <div class="form-floating mb-3" v-if="hasPhonenumber1">
</option> <input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
</select> <label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label>
<label>{{ $t('activity.location_fields.type') }}</label> </div>
</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>
<div class="form-floating mb-3"> </modal>
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder /> </teleport>
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label> </div>
</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">
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
<label for="email">{{ $t('activity.location_fields.email') }}</label>
</div>
</template>
<template v-slot:footer>
<button class="btn btn-save"
@click.prevent="$emit('saveNewLocation', selected); modal.showModal = false;">
{{ $t('action.save') }}
</button>
</template>
</modal>
</teleport>
</template> </template>
<script> <script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue'; import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"; import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import { getLocationTypes } from "../../api"; import { getLocationTypes, postLocation } from "../../api";
export default { export default {
name: "NewLocation", name: "NewLocation",
@ -77,11 +89,12 @@ export default {
Modal, Modal,
AddAddress, AddAddress,
}, },
emits: ['saveNewLocation'], props: ['locations'],
data() { data() {
return { return {
errors: [],
selected: { selected: {
type: {}, type: null,
name: null, name: null,
addressId: null, addressId: null,
phonenumber1: null, phonenumber1: null,
@ -117,7 +130,7 @@ export default {
return this.selected.type; return this.selected.type;
}, },
set(value) { set(value) {
this.selected.type = value this.selected.type = value;
} }
}, },
inputName: { inputName: {
@ -154,12 +167,52 @@ export default {
}, },
hasPhonenumber1() { hasPhonenumber1() {
return this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""; 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() { mounted() {
this.getLocationTypesList(); this.getLocationTypesList();
}, },
methods: { 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() { getLocationTypesList() {
getLocationTypes().then(response => new Promise(resolve => { getLocationTypes().then(response => new Promise(resolve => {
console.log('getLocationTypes', response); console.log('getLocationTypes', response);
@ -170,6 +223,43 @@ export default {
openModal() { openModal() {
this.modal.showModal = true; 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) { submitNewAddress(payload) {
console.log('submitNewAddress', payload); console.log('submitNewAddress', payload);
this.selected.addressId = payload.addressId; this.selected.addressId = payload.addressId;

View File

@ -4,6 +4,7 @@ const activityMessages = {
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",

View File

@ -40,7 +40,6 @@
}, },
{ 'title': 'Users concerned'|trans, { 'title': 'Users concerned'|trans,
'items': entity.users, 'items': entity.users,
'path' : 'admin_user_show',
'key' : 'id' 'key' : 'id'
}, },
] %} ] %}
@ -58,6 +57,7 @@
<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,6 +66,14 @@
}) }} }) }}
</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>
@ -85,6 +93,7 @@
<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({
@ -93,6 +102,12 @@
}) }} }) }}
</span> </span>
</a> </a>
{% else %}
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
{% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -114,12 +129,19 @@
{% 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_course_id' : accompanyingCourse.id, 'id' : activity.id }, 'cancel_parameters' : { 'accompanying_period_id' : accompanyingCourse.id, 'id' : activity.id },
'form' : delete_form 'form' : delete_form
} ) }} } ) }}
{% endblock %} {% endblock %}

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\Type; use Doctrine\DBAL\Types\Types;
/** /**
* Add an "active" column on activitytype table * Add an "active" column on activitytype table

View File

@ -22,7 +22,6 @@ 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,7 +51,6 @@ 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,7 +6,6 @@ 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
@ -100,10 +99,13 @@ 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,8 +19,6 @@ 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
{ {
@ -111,4 +109,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' => 'Agent', 'label' => 'For 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' => "Configure aside activities categories" 'explain' => "Aside activity type configuration"
]); ]);
} 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,7 +30,9 @@ final class CategoryRender implements ChillEntityRenderInterface
{ {
$options = array_merge(self::DEFAULT_ARGS, $options); $options = array_merge(self::DEFAULT_ARGS, $options);
$titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle()); $titles = [
$this->translatableStringHelper->localize($asideActivityCategory->getTitle()),
];
while ($asideActivityCategory->hasParent()) { while ($asideActivityCategory->hasParent()) {
$asideActivityCategory = $asideActivityCategory->getParent(); $asideActivityCategory = $asideActivityCategory->getParent();

View File

@ -3,6 +3,7 @@ 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
@ -45,7 +46,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
Agent: Utilisateur For agent: Pour l'utilisateur
date: Date date: Date
Duration: Durée Duration: Durée
Note: Note Note: Note
@ -156,6 +157,11 @@ 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
]); ]);
}
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) { if ($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,6 +117,8 @@ class CalendarController extends AbstractController
'paginator' => $paginator 'paginator' => $paginator
]); ]);
} }
throw new \Exception('Unable to list actions.');
} }
/** /**
@ -234,6 +236,7 @@ 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

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

View File

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

View File

@ -188,7 +188,7 @@ class CalendarType extends AbstractType
} }
return $location->getId(); return $location->getId();
}, },
function (?string $id): Location { function (?string $id): ?Location {
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]); return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
} }
)) ))

View File

@ -0,0 +1,92 @@
<?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

@ -50,10 +50,7 @@ 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');
@ -74,7 +71,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,6 +34,5 @@ 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

@ -28,27 +28,31 @@ 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['form_themes'][] = 'ChillCustomFieldsBundle:Form:fields.html.twig'; $twigConfig = [
'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_array") * @ORM\Column(type="json")
*/ */
private $options = array(); private $options = array();
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
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_array") * @ORM\Column(type="json")
*/ */
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_array") * @ORM\Column(type="json")
*/ */
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_array") * @ORM\Column(type="json")
*/ */
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,5 +145,7 @@ 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_array") * @ORM\Column(type="json")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
*/ */
private array $name = []; private array $name = [];

View File

@ -22,7 +22,6 @@ 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,6 +4,7 @@ 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;
@ -16,32 +17,27 @@ 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.
@ -50,15 +46,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")
*/ */
@ -70,7 +66,7 @@ class DocumentAccompanyingCourseController extends AbstractController
throw $this->createNotFoundException('Accompanying period not found'); throw $this->createNotFoundException('Accompanying period not found');
} }
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $course); $this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE, $course);
$documents = $em $documents = $em
->getRepository("ChillDocStoreBundle:AccompanyingCourseDocument") ->getRepository("ChillDocStoreBundle:AccompanyingCourseDocument")
@ -78,7 +74,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',
[ [
@ -96,13 +92,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);
@ -114,7 +110,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()]);
@ -134,9 +130,8 @@ class DocumentAccompanyingCourseController extends AbstractController
*/ */
public function show(AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response public function show(AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response
{ {
$this->denyAccessUnlessGranted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course); $this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE_DETAILS, $document);
$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]);
@ -147,8 +142,7 @@ 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('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course); $this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::UPDATE, $document);
$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'));
@ -159,17 +153,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',
[ [
@ -184,8 +178,7 @@ 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('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course); $this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::DELETE, $document);
$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

@ -2,6 +2,7 @@
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;
@ -39,7 +40,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
@ -52,7 +53,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(
@ -61,10 +62,14 @@ 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_array") * @ORM\Column(type="json")
*/ */
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_array", name="key") * @ORM\Column(type="json", name="key")
*/ */
private array $keyInfos = []; private array $keyInfos = [];
/** /**
* *
* @var int[] * @var int[]
* @ORM\Column(type="json_array", name="iv") * @ORM\Column(type="json", 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_array", name="datas") * @ORM\Column(type="json", name="datas")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
*/ */
private array $datas = []; private array $datas = [];

View File

@ -1,8 +1,10 @@
<?php <?php
namespace Chill\DocStoreBundle\Repository; declare(strict_types=1);
use App\Entity\AccompanyingCourseDocument; namespace Chill\DocStoreBundle\EntityRepository;
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,50 +3,27 @@
*/ */
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\Role\Role; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Translation\TranslatorInterface; use Symfony\Contracts\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(
TokenStorageInterface $tokenStorage, Security $security,
AuthorizationHelper $authorizationHelper,
TranslatorInterface $translator TranslatorInterface $translator
){ ) {
$this->tokenStorage = $tokenStorage; $this->security = $security;
$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) {
@ -65,11 +42,8 @@ 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' => [
@ -80,24 +54,22 @@ 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();
//TODO : add condition to check user rights? if ($this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $course)) {
$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,13 +3,20 @@
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;
/** /**
* *
@ -22,30 +29,22 @@ 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;
* @var AuthorizationHelper protected VoterHelperInterface $voterHelper;
*/ protected Security $security;
protected $authorizationHelper;
/**
* @var AccessDecisionManagerInterface
*/
protected $accessDecisionManager;
/**
* @var LoggerInterface
*/
protected $logger;
public function __construct( public function __construct(
AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger,
AuthorizationHelper $authorizationHelper, Security $security,
LoggerInterface $logger VoterHelperFactoryInterface $voterHelperFactory
) ) {
{
$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()
@ -61,26 +60,30 @@ 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)
{ {
return true; $this->logger->debug(sprintf("Voting from %s class", self::class));
}
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,8 +19,11 @@
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;
@ -31,6 +34,7 @@ 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;
/** /**
* *
@ -43,25 +47,22 @@ 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 CenterResolverDispatcher $centerResolverDispatcher; protected VoterHelperInterface $voterHelper;
public function __construct( public function __construct(
AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger,
AuthorizationHelper $authorizationHelper, Security $security,
LoggerInterface $logger//, VoterHelperFactoryInterface $voterHelperFactory
//CenterResolverDispatcher $centerResolverDispatcher ) {
)
{
$this->accessDecisionManager = $accessDecisionManager;
$this->authorizationHelper = $authorizationHelper;
$this->logger = $logger; $this->logger = $logger;
//$this->centerResolverDispatcher = $centerResolverDispatcher; $this->security = $security;
$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()
@ -77,16 +78,7 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
protected function supports($attribute, $subject) protected function supports($attribute, $subject)
{ {
if (\in_array($attribute, $this->getRoles()) && $subject instanceof PersonDocument) { return $this->voterHelper->supports($attribute, $subject);
return true;
}
if ($subject instanceof Person
&& \in_array($attribute, [self::CREATE, self::SEE])) {
return true;
}
return false;
} }
/** /**
@ -104,42 +96,12 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
return false; return false;
} }
$center = $this->centerResolverDispatcher->resolveCenter($subject); if ($subject instanceof PersonDocument
&& !$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->authorizationHelper->userHasAccess( return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
$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,8 +1,4 @@
services: services:
Chill\DocStoreBundle\Menu\MenuBuilder: Chill\DocStoreBundle\Menu\MenuBuilder:
arguments: autowire: true
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' autoconfigure: true
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
$translator: '@Symfony\Component\Translation\TranslatorInterface'
tags:
- { name: 'chill.menu_builder' }

View File

@ -17,7 +17,6 @@ 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)');
@ -34,6 +33,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,9 +16,6 @@ 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 (!isset($newParticipations)) { if ([] === $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,24 +222,29 @@ 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('ChillEventBundle:Participation:new-multiple.html.twig', array( return $this->render(
'ChillEventBundle:Participation:new-multiple.html.twig',
[
'form' => $form->createView(), 'form' => $form->createView(),
'participations' => $newParticipations, 'participations' => $newParticipations,
'ignored_participations' => isset($ignoredParticipations) ? $ignoredParticipations : array() 'ignored_participations' => $ignoredParticipations
)); ]
} 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
return $this->render('ChillEventBundle:Participation:new.html.twig', array( $form = $this->createCreateForm($participation);
return $this->render(
'ChillEventBundle:Participation:new.html.twig',
[
'form' => $form->createView(), 'form' => $form->createView(),
'participation' => $participation, 'participation' => $participation,
'ignored_participations' => isset($ignoredParticipations) ? $ignoredParticipations : array() 'ignored_participations' => $ignoredParticipations,
)); ]
);
}
} }
/** /**
* @param Request $request * @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
@ -251,32 +256,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
@ -284,41 +289,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
@ -326,56 +331,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)
{ {
@ -384,37 +389,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);
@ -428,13 +433,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
@ -442,7 +447,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(
@ -451,14 +456,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
@ -470,7 +475,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))
) )
))); )));
@ -481,90 +486,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
*/ */
@ -576,14 +581,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.
* *
@ -594,84 +599,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
@ -679,14 +684,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(
@ -694,14 +699,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
@ -713,28 +718,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()
]); ]);
@ -744,9 +749,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
@ -762,5 +767,5 @@ class ParticipationController extends AbstractController
->getForm() ->getForm()
; ;
} }
} }

View File

@ -46,7 +46,7 @@ class EventType
/** /**
* @var array * @var array
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
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_array") * @ORM\Column(type="json")
*/ */
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_array") * @ORM\Column(type="json")
*/ */
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,6 +504,8 @@ 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,13 +342,19 @@ class CRUDController extends AbstractController
*/ */
protected function buildQueryEntities(string $action, Request $request) protected function buildQueryEntities(string $action, Request $request)
{ {
return $this->getDoctrine()->getManager() $query = $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,6 +8,7 @@ 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;
@ -38,6 +39,8 @@ 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,43 +54,44 @@ 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,6 +3,7 @@
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;
/** /**
@ -20,8 +21,17 @@ 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,4 +41,9 @@ 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

@ -20,11 +20,17 @@ class LocationApiController extends ApiController
$query->expr()->eq('e.createdBy', ':user'), $query->expr()->eq('e.createdBy', ':user'),
$query->expr()->gte('e.createdAt', ':dateBefore') $query->expr()->gte('e.createdAt', ':dateBefore')
), ),
$query->expr()->eq('e.availableForUsers', "'TRUE'") $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([ ->setParameters([
'user' => $this->getUser(), 'user' => $this->getUser(),
'dateBefore' => (new \DateTime())->sub(new \DateInterval('P6M')) 'dateBefore' => (new \DateTime())->sub(new \DateInterval('P6M')),
'emptyString' => '',
]); ]);
} }
} }

View File

@ -0,0 +1,23 @@
<?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;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
/**
* Class LocationTypeApiController
*
* @package Chill\MainBundle\Controller
* @author Champs Libres
*/
class LocationTypeApiController extends ApiController
{
public function customizeQuery(string $action, Request $request, $query): void
{
$query->andWhere(
$query->expr()->andX(
$query->expr()->eq('e.availableForUsers', "'TRUE'"),
$query->expr()->eq('e.active', "'TRUE'"),
)
);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
class LocationTypeController extends CRUDController
{
}

View File

@ -24,26 +24,26 @@ class LoadLanguages extends AbstractFixture implements ContainerAwareInterface,
// Array of ancien languages (to exclude) // Array of ancien languages (to exclude)
private $ancientToExclude = ["ang", "egy", "fro", "goh", "grc", "la", "non", "peo", "pro", "sga", private $ancientToExclude = ["ang", "egy", "fro", "goh", "grc", "la", "non", "peo", "pro", "sga",
"dum", "enm", "frm", "gmh", "mga", "akk", "phn", "zxx", "got", "und"]; "dum", "enm", "frm", "gmh", "mga", "akk", "phn", "zxx", "got", "und"];
/** /**
* *
* @var ContainerInterface * @var ContainerInterface
*/ */
private $container; private $container;
public function setContainer(ContainerInterface $container = null) public function setContainer(ContainerInterface $container = null)
{ {
$this->container = $container; $this->container = $container;
} }
public function getOrder() { public function getOrder() {
return 10; return 10;
} }
public function load(ObjectManager $manager) { public function load(ObjectManager $manager) {
echo "loading languages... \n"; echo "loading languages... \n";
foreach (Intl::getLanguageBundle()->getLanguageNames() as $code => $language) { foreach (Intl::getLanguageBundle()->getLanguageNames() as $code => $language) {
if ( if (
!in_array($code, $this->regionalVersionToInclude) !in_array($code, $this->regionalVersionToInclude)
@ -58,23 +58,24 @@ class LoadLanguages extends AbstractFixture implements ContainerAwareInterface,
$manager->persist($lang); $manager->persist($lang);
} }
} }
$manager->flush(); $manager->flush();
} }
/** /**
* prepare names for languages * Prepare names for languages.
* *
* @param string $languageCode
* @return string[] languages name indexed by available language code * @return string[] languages name indexed by available language code
*/ */
private function prepareName($languageCode) { private function prepareName(string $languageCode): array {
$names = [];
foreach ($this->container->getParameter('chill_main.available_languages') as $lang) { foreach ($this->container->getParameter('chill_main.available_languages') as $lang) {
$names[$lang] = Intl::getLanguageBundle()->getLanguageName($languageCode); $names[$lang] = Intl::getLanguageBundle()->getLanguageName($languageCode);
} }
return $names; return $names;
} }
} }

View File

@ -0,0 +1,69 @@
<?php
namespace Chill\MainBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Persistence\ObjectManager;
use Chill\MainBundle\Entity\LocationType;
/**
* Load location types into database
*
* @author Champs Libres
*/
class LoadLocationType extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface {
/**
*
* @var ContainerInterface
*/
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function getOrder() {
return 52;
}
public function load(ObjectManager $manager): void {
echo "loading some location type... \n";
$arr = [
[
'name' => ['fr' => 'Mairie'],
'address_required' => LocationType::STATUS_OPTIONAL
],
[
'name' => ['fr' => 'Guichet d\'accueil'],
'address_required' => LocationType::STATUS_OPTIONAL
],
[
'name' => ['fr' => 'Domicile de l\'usager'],
'address_required' => LocationType::STATUS_REQUIRED
],
[
'name' => ['fr' => 'Centre d\'aide sociale'],
'address_required' => LocationType::STATUS_OPTIONAL
],
];
foreach ($arr as $a) {
$locationType = (new LocationType())
->setTitle($a['name'])
->setAvailableForUsers(true)
->setActive(true)
->setAddressRequired($a['address_required']);
$manager->persist($locationType);
}
$manager->flush();
}
}

View File

@ -135,7 +135,6 @@ EOF;
85110,ST VINCENT STERLANGES,FR,85276,46.7397220689,-1.08371759277,INSEE 85110,ST VINCENT STERLANGES,FR,85276,46.7397220689,-1.08371759277,INSEE
85110,SIGOURNAIS,FR,85282,46.7140097406,-0.98747730882,INSEE 85110,SIGOURNAIS,FR,85282,46.7140097406,-0.98747730882,INSEE
85110,CHANTONNAY,FR,85051,46.6690167793,-1.04372588019,INSEE 85110,CHANTONNAY,FR,85051,46.6690167793,-1.04372588019,INSEE
85110,CHANTONNAY,FR,85051,46.6690167793,-1.04372588019,INSEE
85110,ST PROUANT,FR,85266,46.7502017421,-0.974504061491,INSEE 85110,ST PROUANT,FR,85266,46.7502017421,-0.974504061491,INSEE
85120,LA CHAPELLE AUX LYS,FR,85053,46.6221916887,-0.642706103195,INSEE 85120,LA CHAPELLE AUX LYS,FR,85053,46.6221916887,-0.642706103195,INSEE
85120,VOUVANT,FR,85305,46.5626835135,-0.764380170382,INSEE 85120,VOUVANT,FR,85305,46.5626835135,-0.764380170382,INSEE
@ -156,11 +155,8 @@ EOF;
85130,ST AUBIN DES ORMEAUX,FR,85198,46.9958175597,-1.04216568722,INSEE 85130,ST AUBIN DES ORMEAUX,FR,85198,46.9958175597,-1.04216568722,INSEE
85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE 85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE
85140,LA MERLATIERE,FR,85142,46.7557703112,-1.29794577,INSEE 85140,LA MERLATIERE,FR,85142,46.7557703112,-1.29794577,INSEE
85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE
85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE
85140,ST MARTIN DES NOYERS,FR,85246,46.7239461187,-1.20379080965,INSEE 85140,ST MARTIN DES NOYERS,FR,85246,46.7239461187,-1.20379080965,INSEE
85140,CHAUCHE,FR,85064,46.8282791899,-1.27090860656,INSEE 85140,CHAUCHE,FR,85064,46.8282791899,-1.27090860656,INSEE
85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE
85150,ST MATHURIN,FR,85250,46.5686332748,-1.70787622288,INSEE 85150,ST MATHURIN,FR,85250,46.5686332748,-1.70787622288,INSEE
85150,MARTINET,FR,85138,46.6620680463,-1.6772013304,INSEE 85150,MARTINET,FR,85138,46.6620680463,-1.6772013304,INSEE
85150,STE FLAIVE DES LOUPS,FR,85211,46.611019489,-1.58031627863,INSEE 85150,STE FLAIVE DES LOUPS,FR,85211,46.611019489,-1.58031627863,INSEE
@ -170,14 +166,12 @@ EOF;
85150,LE GIROUARD,FR,85099,46.5726064909,-1.58872487716,INSEE 85150,LE GIROUARD,FR,85099,46.5726064909,-1.58872487716,INSEE
85150,LANDERONDE,FR,85118,46.6549237031,-1.57351777893,INSEE 85150,LANDERONDE,FR,85118,46.6549237031,-1.57351777893,INSEE
85150,LES ACHARDS,FR,85152,46.6163645636,-1.65038156849,INSEE 85150,LES ACHARDS,FR,85152,46.6163645636,-1.65038156849,INSEE
85150,LES ACHARDS,FR,85152,46.6163645636,-1.65038156849,INSEE
85150,VAIRE,FR,85298,46.6055340621,-1.74863672042,INSEE 85150,VAIRE,FR,85298,46.6055340621,-1.74863672042,INSEE
85160,ST JEAN DE MONTS,FR,85234,46.8021968737,-2.04839789308,INSEE 85160,ST JEAN DE MONTS,FR,85234,46.8021968737,-2.04839789308,INSEE
85170,BELLEVIGNY,FR,85019,46.7756383534,-1.43313700054,INSEE 85170,BELLEVIGNY,FR,85019,46.7756383534,-1.43313700054,INSEE
85170,LE POIRE SUR VIE,FR,85178,46.769919754,-1.50488626452,INSEE 85170,LE POIRE SUR VIE,FR,85178,46.769919754,-1.50488626452,INSEE
85170,BEAUFOU,FR,85015,46.8191122027,-1.52479250801,INSEE 85170,BEAUFOU,FR,85015,46.8191122027,-1.52479250801,INSEE
85170,DOMPIERRE SUR YON,FR,85081,46.7599858068,-1.37275519417,INSEE 85170,DOMPIERRE SUR YON,FR,85081,46.7599858068,-1.37275519417,INSEE
85170,BELLEVIGNY,FR,85019,46.7756383534,-1.43313700054,INSEE
85170,LES LUCS SUR BOULOGNE,FR,85129,46.8527299002,-1.48398928084,INSEE 85170,LES LUCS SUR BOULOGNE,FR,85129,46.8527299002,-1.48398928084,INSEE
85170,ST DENIS LA CHEVASSE,FR,85208,46.8325959261,-1.3830312677,INSEE 85170,ST DENIS LA CHEVASSE,FR,85208,46.8325959261,-1.3830312677,INSEE
85180,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE 85180,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE
@ -191,14 +185,11 @@ EOF;
85200,FONTENAY LE COMTE,FR,85092,46.4563186117,-0.793449510859,INSEE 85200,FONTENAY LE COMTE,FR,85092,46.4563186117,-0.793449510859,INSEE
85200,MERVENT,FR,85143,46.5325327351,-0.748519927998,INSEE 85200,MERVENT,FR,85143,46.5325327351,-0.748519927998,INSEE
85200,DOIX LES FONTAINES,FR,85080,46.3849492327,-0.806840287485,INSEE 85200,DOIX LES FONTAINES,FR,85080,46.3849492327,-0.806840287485,INSEE
85200,FONTENAY LE COMTE,FR,85092,46.4563186117,-0.793449510859,INSEE
85200,LONGEVES,FR,85126,46.4722105292,-0.858917690239,INSEE 85200,LONGEVES,FR,85126,46.4722105292,-0.858917690239,INSEE
85200,ST MARTIN DE FRAIGNEAU,FR,85244,46.4289052087,-0.758948963227,INSEE 85200,ST MARTIN DE FRAIGNEAU,FR,85244,46.4289052087,-0.758948963227,INSEE
85200,SERIGNE,FR,85281,46.5054321828,-0.848819460581,INSEE 85200,SERIGNE,FR,85281,46.5054321828,-0.848819460581,INSEE
85200,BOURNEAU,FR,85033,46.5476882922,-0.813838020265,INSEE 85200,BOURNEAU,FR,85033,46.5476882922,-0.813838020265,INSEE
85200,ST MICHEL LE CLOUCQ,FR,85256,46.4861591475,-0.743056336646,INSEE 85200,ST MICHEL LE CLOUCQ,FR,85256,46.4861591475,-0.743056336646,INSEE
85200,AUCHAY SUR VENDEE,FR,85009,46.4474386161,-0.876574265149,INSEE
85200,DOIX LES FONTAINES,FR,85080,46.3849492327,-0.806840287485,INSEE
85200,MONTREUIL,FR,85148,46.3973419593,-0.840846860992,INSEE 85200,MONTREUIL,FR,85148,46.3973419593,-0.840846860992,INSEE
85200,L ORBRIE,FR,85167,46.4997145725,-0.77427886573,INSEE 85200,L ORBRIE,FR,85167,46.4997145725,-0.77427886573,INSEE
85210,ST JEAN DE BEUGNE,FR,85233,46.5196817523,-1.10826075013,INSEE 85210,ST JEAN DE BEUGNE,FR,85233,46.5196817523,-1.10826075013,INSEE
@ -238,12 +229,9 @@ EOF;
85260,LA COPECHAGNIERE,FR,85072,46.8523980181,-1.34349746898,INSEE 85260,LA COPECHAGNIERE,FR,85072,46.8523980181,-1.34349746898,INSEE
85260,L HERBERGEMENT,FR,85108,46.9166207979,-1.37033557148,INSEE 85260,L HERBERGEMENT,FR,85108,46.9166207979,-1.37033557148,INSEE
85260,MONTREVERD,FR,85197,46.9277672307,-1.4126154924,INSEE 85260,MONTREVERD,FR,85197,46.9277672307,-1.4126154924,INSEE
85260,MONTREVERD,FR,85197,46.9277672307,-1.4126154924,INSEE
85260,MONTREVERD,FR,85197,46.9277672307,-1.4126154924,INSEE
85260,LES BROUZILS,FR,85038,46.8854235235,-1.33186892233,INSEE 85260,LES BROUZILS,FR,85038,46.8854235235,-1.33186892233,INSEE
85270,NOTRE DAME DE RIEZ,FR,85189,46.7532179022,-1.8935292542,INSEE 85270,NOTRE DAME DE RIEZ,FR,85189,46.7532179022,-1.8935292542,INSEE
85270,ST HILAIRE DE RIEZ,FR,85226,46.7432732188,-1.96439228965,INSEE 85270,ST HILAIRE DE RIEZ,FR,85226,46.7432732188,-1.96439228965,INSEE
85270,ST HILAIRE DE RIEZ,FR,85226,46.7432732188,-1.96439228965,INSEE
85280,LA FERRIERE,FR,85089,46.7215872927,-1.33469332327,INSEE 85280,LA FERRIERE,FR,85089,46.7215872927,-1.33469332327,INSEE
85290,MORTAGNE SUR SEVRE,FR,85151,46.9910941319,-0.946500033344,INSEE 85290,MORTAGNE SUR SEVRE,FR,85151,46.9910941319,-0.946500033344,INSEE
85290,ST LAURENT SUR SEVRE,FR,85238,46.9506837971,-0.901123752328,INSEE 85290,ST LAURENT SUR SEVRE,FR,85238,46.9506837971,-0.901123752328,INSEE
@ -254,8 +242,6 @@ EOF;
85300,SALLERTAINE,FR,85280,46.8659054157,-1.94894081389,INSEE 85300,SALLERTAINE,FR,85280,46.8659054157,-1.94894081389,INSEE
85310,LA CHAIZE LE VICOMTE,FR,85046,46.6729533879,-1.29188591019,INSEE 85310,LA CHAIZE LE VICOMTE,FR,85046,46.6729533879,-1.29188591019,INSEE
85310,NESMY,FR,85160,46.5921936479,-1.40947698594,INSEE 85310,NESMY,FR,85160,46.5921936479,-1.40947698594,INSEE
85310,LA CHAIZE LE VICOMTE,FR,85046,46.6729533879,-1.29188591019,INSEE
85310,RIVES DE L YON,FR,85213,46.605637391,-1.3354497172,INSEE
85310,RIVES DE L YON,FR,85213,46.605637391,-1.3354497172,INSEE 85310,RIVES DE L YON,FR,85213,46.605637391,-1.3354497172,INSEE
85310,LE TABLIER,FR,85285,46.5596307281,-1.32788759657,INSEE 85310,LE TABLIER,FR,85285,46.5596307281,-1.32788759657,INSEE
85320,CHATEAU GUIBERT,FR,85061,46.5741109302,-1.25524886228,INSEE 85320,CHATEAU GUIBERT,FR,85061,46.5741109302,-1.25524886228,INSEE
@ -263,11 +249,9 @@ EOF;
85320,ROSNAY,FR,85193,46.5324344973,-1.3007139449,INSEE 85320,ROSNAY,FR,85193,46.5324344973,-1.3007139449,INSEE
85320,BESSAY,FR,85023,46.5397253861,-1.17028433093,INSEE 85320,BESSAY,FR,85023,46.5397253861,-1.17028433093,INSEE
85320,LA BRETONNIERE LA CLAYE,FR,85036,46.4879459421,-1.26773426545,INSEE 85320,LA BRETONNIERE LA CLAYE,FR,85036,46.4879459421,-1.26773426545,INSEE
85320,LA BRETONNIERE LA CLAYE,FR,85036,46.4879459421,-1.26773426545,INSEE
85320,CORPE,FR,85073,46.5050234767,-1.17034425311,INSEE 85320,CORPE,FR,85073,46.5050234767,-1.17034425311,INSEE
85320,MAREUIL SUR LAY DISSAIS,FR,85135,46.5335825488,-1.22688907859,INSEE 85320,MAREUIL SUR LAY DISSAIS,FR,85135,46.5335825488,-1.22688907859,INSEE
85320,PEAULT,FR,85171,46.502029199,-1.22708559855,INSEE 85320,PEAULT,FR,85171,46.502029199,-1.22708559855,INSEE
85320,MAREUIL SUR LAY DISSAIS,FR,85135,46.5335825488,-1.22688907859,INSEE
85320,LA COUTURE,FR,85074,46.523938732,-1.26493227292,INSEE 85320,LA COUTURE,FR,85074,46.523938732,-1.26493227292,INSEE
85320,MOUTIERS SUR LE LAY,FR,85157,46.5651677306,-1.16826489836,INSEE 85320,MOUTIERS SUR LE LAY,FR,85157,46.5651677306,-1.16826489836,INSEE
85320,STE PEXINE,FR,85261,46.5596018797,-1.12406235901,INSEE 85320,STE PEXINE,FR,85261,46.5596018797,-1.12406235901,INSEE
@ -275,7 +259,6 @@ EOF;
85340,L ILE D OLONNE,FR,85112,46.570163703,-1.7737502368,INSEE 85340,L ILE D OLONNE,FR,85112,46.570163703,-1.7737502368,INSEE
85340,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE 85340,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE
85350,L ILE D YEU,FR,85113,46.7093514816,-2.34712702345,INSEE 85350,L ILE D YEU,FR,85113,46.7093514816,-2.34712702345,INSEE
85350,L ILE D YEU,FR,85113,46.7093514816,-2.34712702345,INSEE
85360,LA TRANCHE SUR MER,FR,85294,46.3564601605,-1.43136322126,INSEE 85360,LA TRANCHE SUR MER,FR,85294,46.3564601605,-1.43136322126,INSEE
85370,MOUZEUIL ST MARTIN,FR,85158,46.4591118412,-0.984449849889,INSEE 85370,MOUZEUIL ST MARTIN,FR,85158,46.4591118412,-0.984449849889,INSEE
85370,NALLIERS,FR,85159,46.4658962703,-1.03958611312,INSEE 85370,NALLIERS,FR,85159,46.4658962703,-1.03958611312,INSEE
@ -286,7 +269,6 @@ EOF;
85390,CHAVAGNES LES REDOUX,FR,85066,46.7101499475,-0.915900131393,INSEE 85390,CHAVAGNES LES REDOUX,FR,85066,46.7101499475,-0.915900131393,INSEE
85390,CHEFFOIS,FR,85067,46.6786935635,-0.782949851125,INSEE 85390,CHEFFOIS,FR,85067,46.6786935635,-0.782949851125,INSEE
85390,MOUILLERON ST GERMAIN,FR,85154,46.6612700667,-0.846784201071,INSEE 85390,MOUILLERON ST GERMAIN,FR,85154,46.6612700667,-0.846784201071,INSEE
85390,MOUILLERON ST GERMAIN,FR,85154,46.6612700667,-0.846784201071,INSEE
85400,STE GEMME LA PLAINE,FR,85216,46.4732196212,-1.11103084694,INSEE 85400,STE GEMME LA PLAINE,FR,85216,46.4732196212,-1.11103084694,INSEE
85400,LAIROUX,FR,85117,46.4496842668,-1.27114022202,INSEE 85400,LAIROUX,FR,85117,46.4496842668,-1.27114022202,INSEE
85400,LUCON,FR,85128,46.4510564854,-1.16449285012,INSEE 85400,LUCON,FR,85128,46.4510564854,-1.16449285012,INSEE
@ -294,7 +276,6 @@ EOF;
85400,CHASNAIS,FR,85058,46.4459908481,-1.2385924923,INSEE 85400,CHASNAIS,FR,85058,46.4459908481,-1.2385924923,INSEE
85410,ST LAURENT DE LA SALLE,FR,85237,46.5854041653,-0.922177315485,INSEE 85410,ST LAURENT DE LA SALLE,FR,85237,46.5854041653,-0.922177315485,INSEE
85410,LA CAILLERE ST HILAIRE,FR,85040,46.6293907412,-0.933153931505,INSEE 85410,LA CAILLERE ST HILAIRE,FR,85040,46.6293907412,-0.933153931505,INSEE
85410,LA CAILLERE ST HILAIRE,FR,85040,46.6293907412,-0.933153931505,INSEE
85410,ST CYR DES GATS,FR,85205,46.572397925,-0.86344873853,INSEE 85410,ST CYR DES GATS,FR,85205,46.572397925,-0.86344873853,INSEE
85410,THOUARSAIS BOUILDROUX,FR,85292,46.6062740621,-0.873461023865,INSEE 85410,THOUARSAIS BOUILDROUX,FR,85292,46.6062740621,-0.873461023865,INSEE
85410,CEZAIS,FR,85041,46.5917363748,-0.802618133558,INSEE 85410,CEZAIS,FR,85041,46.5917363748,-0.802618133558,INSEE
@ -310,20 +291,16 @@ EOF;
85420,RIVES D AUTISE,FR,85162,46.424726987,-0.665995249042,INSEE 85420,RIVES D AUTISE,FR,85162,46.424726987,-0.665995249042,INSEE
85430,AUBIGNY LES CLOUZEAUX,FR,85008,46.6028241769,-1.46743549114,INSEE 85430,AUBIGNY LES CLOUZEAUX,FR,85008,46.6028241769,-1.46743549114,INSEE
85430,NIEUL LE DOLENT,FR,85161,46.5676509922,-1.51560194548,INSEE 85430,NIEUL LE DOLENT,FR,85161,46.5676509922,-1.51560194548,INSEE
85430,AUBIGNY LES CLOUZEAUX,FR,85008,46.6028241769,-1.46743549114,INSEE
85430,LA BOISSIERE DES LANDES,FR,85026,46.5581861734,-1.44371985689,INSEE 85430,LA BOISSIERE DES LANDES,FR,85026,46.5581861734,-1.44371985689,INSEE
85440,ST HILAIRE LA FORET,FR,85231,46.4551155186,-1.53048160541,INSEE 85440,ST HILAIRE LA FORET,FR,85231,46.4551155186,-1.53048160541,INSEE
85440,TALMONT ST HILAIRE,FR,85288,46.475786445,-1.62751498166,INSEE 85440,TALMONT ST HILAIRE,FR,85288,46.475786445,-1.62751498166,INSEE
85440,POIROUX,FR,85179,46.5107890457,-1.53929317556,INSEE 85440,POIROUX,FR,85179,46.5107890457,-1.53929317556,INSEE
85440,GROSBREUIL,FR,85103,46.5390090882,-1.6072005484,INSEE 85440,GROSBREUIL,FR,85103,46.5390090882,-1.6072005484,INSEE
85440,AVRILLE,FR,85010,46.4744272125,-1.49524360118,INSEE 85440,AVRILLE,FR,85010,46.4744272125,-1.49524360118,INSEE
85440,TALMONT ST HILAIRE,FR,85288,46.475786445,-1.62751498166,INSEE
85450,CHAMPAGNE LES MARAIS,FR,85049,46.3735020647,-1.13380723653,INSEE 85450,CHAMPAGNE LES MARAIS,FR,85049,46.3735020647,-1.13380723653,INSEE
85450,LA TAILLEE,FR,85286,46.3852513569,-0.941017792066,INSEE 85450,LA TAILLEE,FR,85286,46.3852513569,-0.941017792066,INSEE
85450,CHAILLE LES MARAIS,FR,85042,46.3853555319,-1.01044079362,INSEE 85450,CHAILLE LES MARAIS,FR,85042,46.3853555319,-1.01044079362,INSEE
85450,CHAILLE LES MARAIS,FR,85042,46.3853555319,-1.01044079362,INSEE
85450,VOUILLE LES MARAIS,FR,85304,46.3891941167,-0.968106001439,INSEE 85450,VOUILLE LES MARAIS,FR,85304,46.3891941167,-0.968106001439,INSEE
85450,CHAILLE LES MARAIS,FR,85042,46.3853555319,-1.01044079362,INSEE
85450,MOREILLES,FR,85149,46.4218721314,-1.09404530407,INSEE 85450,MOREILLES,FR,85149,46.4218721314,-1.09404530407,INSEE
85450,PUYRAVAULT,FR,85185,46.3653834101,-1.09115660367,INSEE 85450,PUYRAVAULT,FR,85185,46.3653834101,-1.09115660367,INSEE
85450,STE RADEGONDE DES NOYERS,FR,85267,46.3694246909,-1.06671995264,INSEE 85450,STE RADEGONDE DES NOYERS,FR,85267,46.3694246909,-1.06671995264,INSEE
@ -331,14 +308,10 @@ EOF;
85460,L AIGUILLON SUR MER,FR,85001,46.304138479,-1.2623239198,INSEE 85460,L AIGUILLON SUR MER,FR,85001,46.304138479,-1.2623239198,INSEE
85470,BRETIGNOLLES SUR MER,FR,85035,46.6374826705,-1.86324200464,INSEE 85470,BRETIGNOLLES SUR MER,FR,85035,46.6374826705,-1.86324200464,INSEE
85470,BREM SUR MER,FR,85243,46.6118566989,-1.81003917923,INSEE 85470,BREM SUR MER,FR,85243,46.6118566989,-1.81003917923,INSEE
85470,BREM SUR MER,FR,85243,46.6118566989,-1.81003917923,INSEE
85480,BOURNEZEAU,FR,85034,46.6296975315,-1.14101742229,INSEE 85480,BOURNEZEAU,FR,85034,46.6296975315,-1.14101742229,INSEE
85480,FOUGERE,FR,85093,46.6617881911,-1.23612691916,INSEE 85480,FOUGERE,FR,85093,46.6617881911,-1.23612691916,INSEE
85480,ST HILAIRE LE VOUHIS,FR,85232,46.6859198669,-1.15222590884,INSEE 85480,ST HILAIRE LE VOUHIS,FR,85232,46.6859198669,-1.15222590884,INSEE
85480,THORIGNY,FR,85291,46.6179795025,-1.24888057642,INSEE 85480,THORIGNY,FR,85291,46.6179795025,-1.24888057642,INSEE
85480,BOURNEZEAU,FR,85034,46.6296975315,-1.14101742229,INSEE
85490,BENET,FR,85020,46.368873213,-0.613959918706,INSEE
85490,BENET,FR,85020,46.368873213,-0.613959918706,INSEE
85490,BENET,FR,85020,46.368873213,-0.613959918706,INSEE 85490,BENET,FR,85020,46.368873213,-0.613959918706,INSEE
85500,BEAUREPAIRE,FR,85017,46.9050210355,-1.10144867013,INSEE 85500,BEAUREPAIRE,FR,85017,46.9050210355,-1.10144867013,INSEE
85500,ST PAUL EN PAREDS,FR,85259,46.8303789022,-0.964515191283,INSEE 85500,ST PAUL EN PAREDS,FR,85259,46.8303789022,-0.964515191283,INSEE
@ -356,12 +329,10 @@ EOF;
85540,ST VINCENT SUR GRAON,FR,85277,46.5034756656,-1.382954206,INSEE 85540,ST VINCENT SUR GRAON,FR,85277,46.5034756656,-1.382954206,INSEE
85540,LA JONCHERE,FR,85116,46.4358647401,-1.38517843312,INSEE 85540,LA JONCHERE,FR,85116,46.4358647401,-1.38517843312,INSEE
85540,ST BENOIST SUR MER,FR,85201,46.4266286403,-1.3399129604,INSEE 85540,ST BENOIST SUR MER,FR,85201,46.4266286403,-1.3399129604,INSEE
85540,ST VINCENT SUR GRAON,FR,85277,46.5034756656,-1.382954206,INSEE
85540,CURZON,FR,85077,46.4512376923,-1.30138059216,INSEE 85540,CURZON,FR,85077,46.4512376923,-1.30138059216,INSEE
85540,ST AVAUGOURD DES LANDES,FR,85200,46.5136722903,-1.47528120789,INSEE 85540,ST AVAUGOURD DES LANDES,FR,85200,46.5136722903,-1.47528120789,INSEE
85540,ST CYR EN TALMONDAIS,FR,85206,46.4597334032,-1.33722144355,INSEE 85540,ST CYR EN TALMONDAIS,FR,85206,46.4597334032,-1.33722144355,INSEE
85550,LA BARRE DE MONTS,FR,85012,46.8722784154,-2.09984018879,INSEE 85550,LA BARRE DE MONTS,FR,85012,46.8722784154,-2.09984018879,INSEE
85550,LA BARRE DE MONTS,FR,85012,46.8722784154,-2.09984018879,INSEE
85560,LE BERNARD,FR,85022,46.44832528,-1.43979865314,INSEE 85560,LE BERNARD,FR,85022,46.44832528,-1.43979865314,INSEE
85560,LONGEVILLE SUR MER,FR,85127,46.4091029013,-1.47711855345,INSEE 85560,LONGEVILLE SUR MER,FR,85127,46.4091029013,-1.47711855345,INSEE
85570,POUILLE,FR,85181,46.5022256779,-0.955223380119,INSEE 85570,POUILLE,FR,85181,46.5022256779,-0.955223380119,INSEE
@ -381,11 +352,7 @@ EOF;
85590,ST MARS LA REORTHE,FR,85242,46.8597253005,-0.925777900202,INSEE 85590,ST MARS LA REORTHE,FR,85242,46.8597253005,-0.925777900202,INSEE
85600,LA BOISSIERE DE MONTAIGU,FR,85025,46.9451858636,-1.1916392484,INSEE 85600,LA BOISSIERE DE MONTAIGU,FR,85025,46.9451858636,-1.1916392484,INSEE
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE 85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
85600,TREIZE SEPTIERS,FR,85295,46.9975586143,-1.23193361154,INSEE 85600,TREIZE SEPTIERS,FR,85295,46.9975586143,-1.23193361154,INSEE
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
85610,CUGAND,FR,85076,47.0602388146,-1.25289811103,INSEE 85610,CUGAND,FR,85076,47.0602388146,-1.25289811103,INSEE
85610,LA BERNARDIERE,FR,85021,47.0361828072,-1.27390355206,INSEE 85610,LA BERNARDIERE,FR,85021,47.0361828072,-1.27390355206,INSEE
85620,ROCHESERVIERE,FR,85190,46.9274273036,-1.50132208111,INSEE 85620,ROCHESERVIERE,FR,85190,46.9274273036,-1.50132208111,INSEE
@ -406,11 +373,8 @@ EOF;
85700,MONTOURNAIS,FR,85147,46.7502937556,-0.770013941158,INSEE 85700,MONTOURNAIS,FR,85147,46.7502937556,-0.770013941158,INSEE
85700,POUZAUGES,FR,85182,46.7812581702,-0.828778359084,INSEE 85700,POUZAUGES,FR,85182,46.7812581702,-0.828778359084,INSEE
85700,REAUMUR,FR,85187,46.7145137269,-0.816742537248,INSEE 85700,REAUMUR,FR,85187,46.7145137269,-0.816742537248,INSEE
85700,SEVREMONT,FR,85090,46.8211105864,-0.854584153953,INSEE
85700,MENOMBLET,FR,85141,46.7301338667,-0.728654955878,INSEE 85700,MENOMBLET,FR,85141,46.7301338667,-0.728654955878,INSEE
85700,ST MESMIN,FR,85254,46.8005779435,-0.748892533741,INSEE 85700,ST MESMIN,FR,85254,46.8005779435,-0.748892533741,INSEE
85700,SEVREMONT,FR,85090,46.8211105864,-0.854584153953,INSEE
85700,SEVREMONT,FR,85090,46.8211105864,-0.854584153953,INSEE
85710,BOIS DE CENE,FR,85024,46.9479643351,-1.89668693466,INSEE 85710,BOIS DE CENE,FR,85024,46.9479643351,-1.89668693466,INSEE
85710,CHATEAUNEUF,FR,85062,46.916944435,-1.9261131832,INSEE 85710,CHATEAUNEUF,FR,85062,46.916944435,-1.9261131832,INSEE
85710,LA GARNACHE,FR,85096,46.8977541296,-1.82443040539,INSEE 85710,LA GARNACHE,FR,85096,46.8977541296,-1.82443040539,INSEE
@ -420,10 +384,9 @@ EOF;
85770,VIX,FR,85303,46.3543018169,-0.856628326667,INSEE 85770,VIX,FR,85303,46.3543018169,-0.856628326667,INSEE
85770,LE GUE DE VELLUIRE,FR,85105,46.3675950645,-0.905432724485,INSEE 85770,LE GUE DE VELLUIRE,FR,85105,46.3675950645,-0.905432724485,INSEE
85770,L ILE D ELLE,FR,85111,46.3334258655,-0.919100677098,INSEE 85770,L ILE D ELLE,FR,85111,46.3334258655,-0.919100677098,INSEE
85770,LES VELLUIRE SUR VENDEE,FR,85177,46.4190919441,-0.910475769222,INSEE
85800,ST GILLES CROIX DE VIE,FR,85222,46.6904708814,-1.91946363327,INSEE 85800,ST GILLES CROIX DE VIE,FR,85222,46.6904708814,-1.91946363327,INSEE
85800,LE FENOUILLER,FR,85088,46.7161264566,-1.89206667498,INSEE 85800,LE FENOUILLER,FR,85088,46.7161264566,-1.89206667498,INSEE
85800,GIVRAND,FR,85100,46.6822701061,-1.8787272243,INSEE 85800,GIVRAND,FR,85100,46.6822701061,-1.8787272243,INSEE
EOF; EOF;
} }

View File

@ -20,7 +20,10 @@
namespace Chill\MainBundle\DependencyInjection; namespace Chill\MainBundle\DependencyInjection;
use Chill\MainBundle\Controller\AddressApiController; use Chill\MainBundle\Controller\AddressApiController;
use Chill\MainBundle\Controller\LocationController;
use Chill\MainBundle\Controller\LocationTypeController;
use Chill\MainBundle\Controller\UserController; use Chill\MainBundle\Controller\UserController;
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
use Chill\MainBundle\Doctrine\DQL\STContains; use Chill\MainBundle\Doctrine\DQL\STContains;
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS; use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
@ -44,6 +47,10 @@ use Chill\MainBundle\Doctrine\DQL\Replace;
use Chill\MainBundle\Doctrine\ORM\Hydration\FlatHierarchyEntityHydrator; use Chill\MainBundle\Doctrine\ORM\Hydration\FlatHierarchyEntityHydrator;
use Chill\MainBundle\Doctrine\Type\NativeDateIntervalType; use Chill\MainBundle\Doctrine\Type\NativeDateIntervalType;
use Chill\MainBundle\Doctrine\Type\PointType; use Chill\MainBundle\Doctrine\Type\PointType;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Form\LocationTypeType;
use Chill\MainBundle\Form\LocationFormType;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
@ -90,12 +97,19 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
$configuration = $this->getConfiguration($configs, $container); $configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs); $config = $this->processConfiguration($configuration, $configs);
// replace all config with a main key:
$container->setParameter('chill_main', $config);
// legacy config
$container->setParameter('chill_main.installation_name', $container->setParameter('chill_main.installation_name',
$config['installation_name']); $config['installation_name']);
$container->setParameter('chill_main.available_languages', $container->setParameter('chill_main.available_languages',
$config['available_languages']); $config['available_languages']);
$container->setParameter('chill_main.available_countries',
$config['available_countries']);
$container->setParameter('chill_main.routing.resources', $container->setParameter('chill_main.routing.resources',
$config['routing']['resources']); $config['routing']['resources']);
@ -193,6 +207,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
'OVERLAPSI' => OverlapsI::class, 'OVERLAPSI' => OverlapsI::class,
'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class, 'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class,
'ST_CONTAINS' => STContains::class, 'ST_CONTAINS' => STContains::class,
'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class,
], ],
], ],
'hydrators' => [ 'hydrators' => [
@ -315,7 +330,51 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
'template' => '@ChillMain/User/edit.html.twig' 'template' => '@ChillMain/User/edit.html.twig'
] ]
] ]
] ],
[
'class' => Location::class,
'name' => 'main_location',
'base_path' => '/admin/main/location',
'base_role' => 'ROLE_ADMIN',
'form_class' => LocationFormType::class,
'controller' => LocationController::class,
'actions' => [
'index' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Location/index.html.twig',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Location/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Location/edit.html.twig',
]
]
],
[
'class' => LocationType::class,
'name' => 'main_location_type',
'base_path' => '/admin/main/location-type',
'base_role' => 'ROLE_ADMIN',
'form_class' => LocationTypeType::class,
'controller' => LocationTypeController::class,
'actions' => [
'index' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/LocationType/index.html.twig',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/LocationType/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/LocationType/edit.html.twig',
]
]
],
], ],
'apis' => [ 'apis' => [
[ [
@ -470,6 +529,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
], ],
[ [
'class' => \Chill\MainBundle\Entity\LocationType::class, 'class' => \Chill\MainBundle\Entity\LocationType::class,
'controller' => \Chill\MainBundle\Controller\LocationTypeApiController::class,
'name' => 'location_type', 'name' => 'location_type',
'base_path' => '/api/1.0/main/location-type', 'base_path' => '/api/1.0/main/location-type',
'base_role' => 'ROLE_USER', 'base_role' => 'ROLE_USER',

View File

@ -1,35 +1,32 @@
<?php <?php
/*
*/ declare(strict_types=1);
namespace Chill\MainBundle\DependencyInjection\CompilerPass; namespace Chill\MainBundle\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Chill\MainBundle\Form\PermissionsGroupType; use Chill\MainBundle\Form\PermissionsGroupType;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use LogicException;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ACLFlagsCompilerPass implements CompilerPassInterface class ACLFlagsCompilerPass implements CompilerPassInterface
{ {
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
$permissionGroupType = $container->getDefinition(PermissionsGroupType::class); $permissionGroupType = $container->getDefinition(PermissionsGroupType::class);
foreach($container->findTaggedServiceIds('chill_main.flags') as $id => $tags) { foreach($container->findTaggedServiceIds('chill_main.flags') as $id => $tags) {
$reference = new Reference($id); $reference = new Reference($id);
foreach ($tags as $tag) { foreach ($tags as $tag) {
switch($tag['scope']) { switch($tag['scope']) {
case PermissionsGroupType::FLAG_SCOPE: case PermissionsGroupType::FLAG_SCOPE:
$permissionGroupType->addMethodCall('addFlagProvider', [ $reference ]); $permissionGroupType->addMethodCall('addFlagProvider', [ $reference ]);
break; break;
default: default:
throw new \LogicalException(sprintf( throw new LogicException(sprintf(
"This tag 'scope' is not implemented: %s, on service with id %s", $tag['scope'], $id) "This tag 'scope' is not implemented: %s, on service with id %s", $tag['scope'], $id)
); );
} }

View File

@ -16,23 +16,20 @@ use Symfony\Component\HttpFoundation\Request;
*/ */
class Configuration implements ConfigurationInterface class Configuration implements ConfigurationInterface
{ {
use AddWidgetConfigurationTrait;
/**
*
* @var ContainerBuilder
*/
private $containerBuilder;
use AddWidgetConfigurationTrait;
public function __construct(array $widgetFactories = array(),
private ContainerBuilder $containerBuilder;
public function __construct(
array $widgetFactories,
ContainerBuilder $containerBuilder) ContainerBuilder $containerBuilder)
{ {
$this->setWidgetFactories($widgetFactories); $this->setWidgetFactories($widgetFactories);
$this->containerBuilder = $containerBuilder; $this->containerBuilder = $containerBuilder;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -51,6 +48,10 @@ class Configuration implements ConfigurationInterface
->defaultValue(array('fr')) ->defaultValue(array('fr'))
->prototype('scalar')->end() ->prototype('scalar')->end()
->end() // end of array 'available_languages' ->end() // end of array 'available_languages'
->arrayNode('available_countries')
->defaultValue(array('FR'))
->prototype('scalar')->end()
->end() // end of array 'available_countries'
->arrayNode('routing') ->arrayNode('routing')
->children() ->children()
->arrayNode('resources') ->arrayNode('resources')
@ -97,6 +98,14 @@ class Configuration implements ConfigurationInterface
->end() ->end()
->end() ->end()
->end() ->end()
->arrayNode('acl')
->addDefaultsIfNotSet()
->children()
->booleanNode('form_show_scopes')
->defaultTrue()
->end()
->end()
->end()
->arrayNode('redis') ->arrayNode('redis')
->children() ->children()
->scalarNode('host') ->scalarNode('host')
@ -247,7 +256,7 @@ class Configuration implements ConfigurationInterface
->end() // end of root ->end() // end of root
; ;
return $treeBuilder; return $treeBuilder;
} }
} }

View File

@ -30,27 +30,27 @@ use Chill\MainBundle\DependencyInjection\Widget\HasWidgetFactoriesExtensionInter
/** /**
* Compile the configurations and inject required service into container. * Compile the configurations and inject required service into container.
* *
* The widgets are services tagged with : * The widgets are services tagged with :
* *
* ``` * ```
* { name: chill_widget, alias: my_alias, place: my_place } * { name: chill_widget, alias: my_alias, place: my_place }
* ``` * ```
* *
* Or, if the tag does not exist or if you need to add some config to your * Or, if the tag does not exist or if you need to add some config to your
* service depending on the config, you should use a `WidgetFactory` (see * service depending on the config, you should use a `WidgetFactory` (see
* `WidgetFactoryInterface`. * `WidgetFactoryInterface`.
* *
* To reuse this compiler pass, simple execute the doProcess metho in your * To reuse this compiler pass, simple execute the doProcess metho in your
* compiler. Example : * compiler. Example :
* *
* ``` * ```
* namespace Chill\MainBundle\DependencyInjection\CompilerPass; * namespace Chill\MainBundle\DependencyInjection\CompilerPass;
* *
* use Symfony\Component\DependencyInjection\ContainerBuilder; * use Symfony\Component\DependencyInjection\ContainerBuilder;
* use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass; * use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass;
* class WidgetsCompilerPass extends AbstractWidgetsCompilerPass { * class WidgetsCompilerPass extends AbstractWidgetsCompilerPass {
* *
* public function process(ContainerBuilder $container) * public function process(ContainerBuilder $container)
* { * {
* $this->doProcess($container, 'chill_main', 'chill_main.widgets'); * $this->doProcess($container, 'chill_main', 'chill_main.widgets');
@ -58,58 +58,58 @@ use Chill\MainBundle\DependencyInjection\Widget\HasWidgetFactoriesExtensionInter
* } * }
* ``` * ```
* *
* *
*/ */
abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
{ {
private $widgetServices = array(); private $widgetServices = array();
/** /**
* *
* @var WidgetFactoryInterface[] * @var WidgetFactoryInterface[]
*/ */
private $widgetFactories; private $widgetFactories;
/** /**
* The service which will manage the widgets * The service which will manage the widgets
* *
* @var string * @var string
*/ */
const WIDGET_MANAGER = 'chill.main.twig.widget'; const WIDGET_MANAGER = 'chill.main.twig.widget';
/** /**
* the method wich register the widget into give service. * the method wich register the widget into give service.
*/ */
const WIDGET_MANAGER_METHOD_REGISTER = 'addWidget'; const WIDGET_MANAGER_METHOD_REGISTER = 'addWidget';
/** /**
* the value of the `name` key in service definitions's tag * the value of the `name` key in service definitions's tag
* *
* @var string * @var string
*/ */
const WIDGET_SERVICE_TAG_NAME = 'chill_widget'; const WIDGET_SERVICE_TAG_NAME = 'chill_widget';
/** /**
* the key used to collect the alias in the service definition's tag. * the key used to collect the alias in the service definition's tag.
* the alias must be * the alias must be
* injected into the configuration under 'alias' key. * injected into the configuration under 'alias' key.
* *
* @var string * @var string
*/ */
const WIDGET_SERVICE_TAG_ALIAS = 'alias'; const WIDGET_SERVICE_TAG_ALIAS = 'alias';
/** /**
* the key used to collect the authorized place in the service definition's tag * the key used to collect the authorized place in the service definition's tag
* *
* @var string * @var string
*/ */
const WIDGET_SERVICE_TAG_PLACES = 'place'; const WIDGET_SERVICE_TAG_PLACES = 'place';
/** /**
* the key to use to order widget for a given place * the key to use to order widget for a given place
*/ */
const WIDGET_CONFIG_ORDER = 'order'; const WIDGET_CONFIG_ORDER = 'order';
/** /**
* the key to use to identify widget for a given place * the key to use to identify widget for a given place
*/ */
@ -118,24 +118,25 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
/** /**
* process the configuration and the container to add the widget available * process the configuration and the container to add the widget available
* *
* @param ContainerBuilder $container * @param ContainerBuilder $container
* @param string $extension the extension of your bundle * @param string $extension the extension of your bundle
* @param string $containerWidgetConfigParameterName the key under which we can use the widget configuration * @param string $containerWidgetConfigParameterName the key under which we can use the widget configuration
* @throws \LogicException * @throws \LogicException
* @throws \UnexpectedValueException if the given extension does not implement HasWidgetExtensionInterface * @throws \UnexpectedValueException if the given extension does not implement HasWidgetExtensionInterface
* @throws \InvalidConfigurationException if there are errors in the config
*/ */
public function doProcess(ContainerBuilder $container, $extension, public function doProcess(
$containerWidgetConfigParameterName) ContainerBuilder $container,
{ $extension,
$containerWidgetConfigParameterName
) {
if (!$container->hasDefinition(self::WIDGET_MANAGER)) { if (!$container->hasDefinition(self::WIDGET_MANAGER)) {
throw new \LogicException("the service ".self::WIDGET_MANAGER." should". throw new \LogicException("the service ".self::WIDGET_MANAGER." should".
" be present. It is required by ".self::class); " be present. It is required by ".self::class);
} }
$managerDefinition = $container->getDefinition(self::WIDGET_MANAGER); $managerDefinition = $container->getDefinition(self::WIDGET_MANAGER);
// collect the widget factories // collect the widget factories
/* @var $extensionClass HasWidgetFactoriesExtensionInterface */ /* @var $extensionClass HasWidgetFactoriesExtensionInterface */
$extensionClass = $container->getExtension($extension); $extensionClass = $container->getExtension($extension);
@ -148,19 +149,19 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
HasWidgetFactoriesExtensionInterface::class, HasWidgetFactoriesExtensionInterface::class,
get_class($extensionClass))); get_class($extensionClass)));
} }
$this->widgetFactories = $extensionClass->getWidgetFactories(); $this->widgetFactories = $extensionClass->getWidgetFactories();
// collect the availabled tagged services // collect the availabled tagged services
$this->collectTaggedServices($container); $this->collectTaggedServices($container);
// collect the widgets and their config : // collect the widgets and their config :
$widgetParameters = $container->getParameter($containerWidgetConfigParameterName); $widgetParameters = $container->getParameter($containerWidgetConfigParameterName);
// and add them to the delegated_block // and add them to the delegated_block
foreach($widgetParameters as $place => $widgets) { foreach($widgetParameters as $place => $widgets) {
foreach ($widgets as $param) { foreach ($widgets as $param) {
$alias = $param[self::WIDGET_CONFIG_ALIAS]; $alias = $param[self::WIDGET_CONFIG_ALIAS];
// check that the service exists // check that the service exists
@ -168,43 +169,43 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
throw new InvalidConfigurationException(sprintf("The alias %s". throw new InvalidConfigurationException(sprintf("The alias %s".
" is not defined.", $alias)); " is not defined.", $alias));
} }
// check that the widget is allowed at this place // check that the widget is allowed at this place
if (!$this->isPlaceAllowedForWidget($place, $alias, $container)) { if (!$this->isPlaceAllowedForWidget($place, $alias, $container)) {
throw new \InvalidConfigurationException(sprintf( throw new InvalidConfigurationException(sprintf(
"The widget with alias %s is not allowed at place %s", "The widget with alias %s is not allowed at place %s",
$alias, $alias,
$place $place
)); ));
} }
// get the order, eventually corrected // get the order, eventually corrected
$order = $this->cacheAndGetOrdering($place, $param[self::WIDGET_CONFIG_ORDER]); $order = $this->cacheAndGetOrdering($place, $param[self::WIDGET_CONFIG_ORDER]);
// register the widget with config to the service, using the method // register the widget with config to the service, using the method
// `addWidget` // `addWidget`
if ($this->widgetServices[$alias] instanceof WidgetFactoryInterface) { if ($this->widgetServices[$alias] instanceof WidgetFactoryInterface) {
/* @var $factory WidgetFactoryInterface */ /* @var $factory WidgetFactoryInterface */
$factory = $this->widgetServices[$alias]; $factory = $this->widgetServices[$alias];
// get the config (under the key which equals to widget_alias // get the config (under the key which equals to widget_alias
$config = isset($param[$factory->getWidgetAlias()]) ? $config = isset($param[$factory->getWidgetAlias()]) ?
$param[$factory->getWidgetAlias()] : array(); $param[$factory->getWidgetAlias()] : array();
// register the service into the container // register the service into the container
$serviceId =$this->registerServiceIntoContainer($container, $serviceId =$this->registerServiceIntoContainer($container,
$factory, $place, $order, $config); $factory, $place, $order, $config);
$managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER, $managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER,
array( array(
$place, $place,
$order, $order,
new Reference($serviceId), new Reference($serviceId),
$config $config
)); ));
} else { } else {
$managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER, $managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER,
array( array(
$place, $place,
$order, $order,
new Reference($this->widgetServices[$alias]), new Reference($this->widgetServices[$alias]),
array() // the config is alway an empty array array() // the config is alway an empty array
)); ));
@ -212,10 +213,10 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
} }
} }
} }
/** /**
* register the service into container. * register the service into container.
* *
* @param ContainerBuilder $container * @param ContainerBuilder $container
* @param WidgetFactoryInterface $factory * @param WidgetFactoryInterface $factory
* @param string $place * @param string $place
@ -231,28 +232,28 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
array $config array $config
) { ) {
$serviceId = $factory->getServiceId($container, $place, $order, $config); $serviceId = $factory->getServiceId($container, $place, $order, $config);
$definition = $factory->createDefinition($container, $place, $definition = $factory->createDefinition($container, $place,
$order, $config); $order, $config);
$container->setDefinition($serviceId, $definition); $container->setDefinition($serviceId, $definition);
return $serviceId; return $serviceId;
} }
/** /**
* cache of ordering by place. * cache of ordering by place.
* *
* @internal used by function cacheAndGetOrdering * @internal used by function cacheAndGetOrdering
* @var array * @var array
*/ */
private $cacheOrdering = array(); private $cacheOrdering = array();
/** /**
* check if the ordering has already be used for the given $place and, * check if the ordering has already be used for the given $place and,
* if yes, correct the ordering by incrementation of 1 until the ordering * if yes, correct the ordering by incrementation of 1 until the ordering
* has not be used. * has not be used.
* *
* recursive method. * recursive method.
* *
* @param string $place * @param string $place
* @param float $ordering * @param float $ordering
* @return float * @return float
@ -262,7 +263,7 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
if (!array_key_exists($place, $this->cacheOrdering)) { if (!array_key_exists($place, $this->cacheOrdering)) {
$this->cacheOrdering[$place] = array(); $this->cacheOrdering[$place] = array();
} }
// check if the order exists // check if the order exists
if (array_search($ordering, $this->cacheOrdering[$place])) { if (array_search($ordering, $this->cacheOrdering[$place])) {
// if the order exists, increment of 1 and try again // if the order exists, increment of 1 and try again
@ -270,14 +271,14 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
} else { } else {
// cache the ordering // cache the ordering
$this->cacheOrdering[$place][] = $ordering; $this->cacheOrdering[$place][] = $ordering;
return $ordering; return $ordering;
} }
} }
/** /**
* get the places where the service is allowed * get the places where the service is allowed
* *
* @param Definition $definition * @param Definition $definition
* @return unknown * @return unknown
*/ */
@ -288,7 +289,7 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
->getAllowedPlaces())) { ->getAllowedPlaces())) {
return true; return true;
} }
} else { } else {
$definition = $container->findDefinition($this->widgetServices[$widgetAlias]); $definition = $container->findDefinition($this->widgetServices[$widgetAlias]);
@ -300,17 +301,17 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
} }
} }
} }
return false; return false;
} }
/** /**
* This method collect all service tagged with `self::WIDGET_SERVICE_TAG`, and * This method collect all service tagged with `self::WIDGET_SERVICE_TAG`, and
* add also the widget defined by factories * add also the widget defined by factories
* *
* This method also check that the service is correctly tagged with `alias` and * This method also check that the service is correctly tagged with `alias` and
* `places`, or the factory give a correct alias and more than one place. * `places`, or the factory give a correct alias and more than one place.
* *
* @param ContainerBuilder $container * @param ContainerBuilder $container
* @throws InvalidConfigurationException * @throws InvalidConfigurationException
* @throws InvalidArgumentException * @throws InvalidArgumentException
@ -320,13 +321,13 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
// first, check the service tagged in service definition // first, check the service tagged in service definition
foreach ($container->findTaggedServiceIds(self::WIDGET_SERVICE_TAG_NAME) as $id => $attrs) { foreach ($container->findTaggedServiceIds(self::WIDGET_SERVICE_TAG_NAME) as $id => $attrs) {
foreach ($attrs as $attr) { foreach ($attrs as $attr) {
// check the alias is set // check the alias is set
if (!isset($attr[self::WIDGET_SERVICE_TAG_ALIAS])) { if (!isset($attr[self::WIDGET_SERVICE_TAG_ALIAS])) {
throw new InvalidConfigurationException("you should add an ".self::WIDGET_SERVICE_TAG_ALIAS. throw new InvalidConfigurationException("you should add an ".self::WIDGET_SERVICE_TAG_ALIAS.
" key on the service ".$id); " key on the service ".$id);
} }
// check the place is set // check the place is set
if (!isset($attr[self::WIDGET_SERVICE_TAG_PLACES])) { if (!isset($attr[self::WIDGET_SERVICE_TAG_PLACES])) {
throw new InvalidConfigurationException(sprintf( throw new InvalidConfigurationException(sprintf(
@ -335,54 +336,54 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
$id $id
)); ));
} }
// check the alias does not exists yet // check the alias does not exists yet
if (array_key_exists($attr[self::WIDGET_SERVICE_TAG_ALIAS], $this->widgetServices)) { if (array_key_exists($attr[self::WIDGET_SERVICE_TAG_ALIAS], $this->widgetServices)) {
throw new InvalidArgumentException("a service has already be defined with the ". throw new InvalidArgumentException("a service has already be defined with the ".
self::WIDGET_SERVICE_TAG_ALIAS." ".$attr[self::WIDGET_SERVICE_TAG_ALIAS]); self::WIDGET_SERVICE_TAG_ALIAS." ".$attr[self::WIDGET_SERVICE_TAG_ALIAS]);
} }
// register the service as available // register the service as available
$this->widgetServices[$attr[self::WIDGET_SERVICE_TAG_ALIAS]] = $id; $this->widgetServices[$attr[self::WIDGET_SERVICE_TAG_ALIAS]] = $id;
} }
} }
// add the services defined by factories // add the services defined by factories
foreach($this->widgetFactories as $factory) { foreach($this->widgetFactories as $factory) {
/* @var $factory WidgetFactoryInterface */ /* @var $factory WidgetFactoryInterface */
$alias = $factory->getWidgetAlias(); $alias = $factory->getWidgetAlias();
// check the alias is not empty // check the alias is not empty
if (empty($alias)) { if (empty($alias)) {
throw new \LogicException(sprintf( throw new \LogicException(sprintf(
"the widget factory %s returns an empty alias", "the widget factory %s returns an empty alias",
get_class($factory))); get_class($factory)));
} }
// check the places are not empty // check the places are not empty
if (!is_array($factory->getAllowedPlaces())) { if (!is_array($factory->getAllowedPlaces())) {
throw new \UnexpectedValueException("the method 'getAllowedPlaces' " throw new \UnexpectedValueException("the method 'getAllowedPlaces' "
. "should return a non-empty array. Unexpected value on ". . "should return a non-empty array. Unexpected value on ".
get_class($factory)); get_class($factory));
} }
if (count($factory->getAllowedPlaces()) == 0) { if (count($factory->getAllowedPlaces()) == 0) {
throw new \LengthException("The method 'getAllowedPlaces' should " throw new \LengthException("The method 'getAllowedPlaces' should "
. "return a non-empty array, but returned 0 elements on ". . "return a non-empty array, but returned 0 elements on ".
get_class($factory).'::getAllowedPlaces()'); get_class($factory).'::getAllowedPlaces()');
} }
// check the alias does not exists yet // check the alias does not exists yet
if (array_key_exists($alias, $this->widgetServices)) { if (array_key_exists($alias, $this->widgetServices)) {
throw new InvalidArgumentException("a service has already be defined with the ". throw new InvalidArgumentException("a service has already be defined with the ".
self::WIDGET_SERVICE_TAG_ALIAS." ".$alias); self::WIDGET_SERVICE_TAG_ALIAS." ".$alias);
} }
// register the factory as available // register the factory as available
$this->widgetServices[$factory->getWidgetAlias()] = $factory; $this->widgetServices[$factory->getWidgetAlias()] = $factory;
} }
} }
} }

View File

@ -0,0 +1,35 @@
<?php
/*
*
*
*/
namespace Chill\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Return the length of an array
*/
class JsonbArrayLength extends FunctionNode
{
private $expr1;
public function getSql(SqlWalker $sqlWalker): string
{
return sprintf(
'jsonb_array_length(%s)',
$this->expr1->dispatch($sqlWalker),
);
}
public function parse(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expr1 = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -1,7 +1,7 @@
<?php <?php
/* /*
* *
* *
*/ */
namespace Chill\MainBundle\Doctrine\DQL; namespace Chill\MainBundle\Doctrine\DQL;
@ -11,19 +11,18 @@ use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\SqlWalker;
/** /**
*
* *
* @author Julien Fastré <julien.fastre@champs-libres.coop> @author Julien Fastré <julien.fastre@champs-libres.coop>
*/ */
class JsonbExistsInArray extends FunctionNode class JsonbExistsInArray extends FunctionNode
{ {
private $expr1; private $expr1;
private $expr2; private $expr2;
public function getSql(SqlWalker $sqlWalker): string public function getSql(SqlWalker $sqlWalker): string
{ {
return sprintf( return sprintf(
'jsonb_exists(%s, %s)', '%s ?? %s',
$this->expr1->dispatch($sqlWalker), $this->expr1->dispatch($sqlWalker),
$sqlWalker->walkInputParameter($this->expr2) $sqlWalker->walkInputParameter($this->expr2)
); );

View File

@ -4,13 +4,9 @@ namespace Chill\MainBundle\Doctrine\Model;
use \JsonSerializable; use \JsonSerializable;
/**
* Description of Point
*
*/
class Point implements JsonSerializable { class Point implements JsonSerializable {
private ?float $lat = null; private ?float $lat;
private ?float $lon = null; private ?float $lon;
public static string $SRID = '4326'; public static string $SRID = '4326';
private function __construct(?float $lon, ?float $lat) private function __construct(?float $lon, ?float $lat)
@ -22,6 +18,7 @@ class Point implements JsonSerializable {
public function toGeoJson(): string public function toGeoJson(): string
{ {
$array = $this->toArrayGeoJson(); $array = $this->toArrayGeoJson();
return \json_encode($array); return \json_encode($array);
} }
@ -33,60 +30,53 @@ class Point implements JsonSerializable {
public function toArrayGeoJson(): array public function toArrayGeoJson(): array
{ {
return [ return [
"type" => "Point", 'type' => 'Point',
"coordinates" => [ $this->lon, $this->lat ] 'coordinates' => [$this->lon, $this->lat],
]; ];
} }
/**
*
* @return string
*/
public function toWKT(): string public function toWKT(): string
{ {
return 'SRID='.self::$SRID.';POINT('.$this->lon.' '.$this->lat.')'; return sprintf("SRID=%s;POINT(%s %s)", self::$SRID, $this->lon, $this->lat);
} }
/** public static function fromGeoJson(string $geojson): self
*
* @param type $geojson
* @return Point
*/
public static function fromGeoJson(string $geojson): Point
{ {
$a = json_decode($geojson); $a = json_decode($geojson);
//check if the geojson string is correct
if (NULL === $a or !isset($a->type) or !isset($a->coordinates)){ if (null === $a) {
throw PointException::badJsonString($geojson); throw PointException::badJsonString($geojson);
} }
if ($a->type != 'Point'){ if (null === $a->type || null === $a->coordinates) {
throw PointException::badJsonString($geojson);
}
if ($a->type !== 'Point'){
throw PointException::badGeoType(); throw PointException::badGeoType();
} }
$lat = $a->coordinates[1]; [$lon, $lat] = $a->coordinates;
$lon = $a->coordinates[0];
return Point::fromLonLat($lon, $lat); return Point::fromLonLat($lon, $lat);
} }
public static function fromLonLat(float $lon, float $lat): Point public static function fromLonLat(float $lon, float $lat): self
{ {
if (($lon > -180 && $lon < 180) && ($lat > -90 && $lat < 90)) if (($lon > -180 && $lon < 180) && ($lat > -90 && $lat < 90)) {
{
return new Point($lon, $lat); return new Point($lon, $lat);
} else {
throw PointException::badCoordinates($lon, $lat);
} }
throw PointException::badCoordinates($lon, $lat);
} }
public static function fromArrayGeoJson(array $array): Point public static function fromArrayGeoJson(array $array): self
{ {
if ($array['type'] == 'Point' && if ($array['type'] === 'Point' && isset($array['coordinates'])) {
isset($array['coordinates']))
{
return self::fromLonLat($array['coordinates'][0], $array['coordinates'][1]); return self::fromLonLat($array['coordinates'][0], $array['coordinates'][1]);
} }
throw new \Exception('Unable to build a point from input data.');
} }
public function getLat(): float public function getLat(): float

View File

@ -15,17 +15,17 @@ use Doctrine\DBAL\Types\ConversionException;
class NativeDateIntervalType extends DateIntervalType class NativeDateIntervalType extends DateIntervalType
{ {
const FORMAT = '%rP%YY%MM%DDT%HH%IM%SS'; const FORMAT = '%rP%YY%MM%DDT%HH%IM%SS';
public function getName(): string public function getName(): string
{ {
return \Doctrine\DBAL\Types\Type::DATEINTERVAL; return \Doctrine\DBAL\Types\Types::DATEINTERVAL;
} }
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{ {
return 'INTERVAL'; return 'INTERVAL';
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -41,36 +41,36 @@ class NativeDateIntervalType extends DateIntervalType
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']); throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']);
} }
public function convertToPHPValue($value, AbstractPlatform $platform) public function convertToPHPValue($value, AbstractPlatform $platform)
{ {
if ($value === null || $value instanceof \DateInterval) { if ($value === null || $value instanceof \DateInterval) {
return $value; return $value;
} }
try { try {
$strings = explode(' ', $value); $strings = explode(' ', $value);
if (count($strings) === 0) { if (count($strings) === 0) {
return null; return null;
} }
$intervalSpec = 'P'; $intervalSpec = 'P';
\reset($strings); \reset($strings);
do { do {
$intervalSpec .= $this->convertEntry($strings); $intervalSpec .= $this->convertEntry($strings);
} while (next($strings) !== FALSE); } while (next($strings) !== FALSE);
return new \DateInterval($intervalSpec); return new \DateInterval($intervalSpec);
} catch (\Exception $exception) { } catch (\Exception $exception) {
throw $this->createConversionException($value, $exception); throw $this->createConversionException($value, $exception);
} }
} }
private function convertEntry(&$strings) private function convertEntry(&$strings)
{ {
$current = \current($strings); $current = \current($strings);
if (is_numeric($current)) { if (is_numeric($current)) {
$next = \next($strings); $next = \next($strings);
switch($next) { switch($next) {
@ -79,7 +79,7 @@ class NativeDateIntervalType extends DateIntervalType
$unit = 'Y'; $unit = 'Y';
break; break;
case 'mon': case 'mon':
case 'mons': case 'mons':
$unit = 'M'; $unit = 'M';
break; break;
case 'day': case 'day':
@ -89,20 +89,20 @@ class NativeDateIntervalType extends DateIntervalType
default: default:
throw $this->createConversionException(implode('', $strings)); throw $this->createConversionException(implode('', $strings));
} }
return $current.$unit; return $current.$unit;
} elseif (\preg_match('/([0-9]{2}\:[0-9]{2}:[0-9]{2})/', $current) === 1) { } elseif (\preg_match('/([0-9]{2}\:[0-9]{2}:[0-9]{2})/', $current) === 1) {
$tExploded = explode(':', $current); $tExploded = explode(':', $current);
$intervalSpec = 'T'; $intervalSpec = 'T';
$intervalSpec.= $tExploded[0].'H'; $intervalSpec.= $tExploded[0].'H';
$intervalSpec.= $tExploded[1].'M'; $intervalSpec.= $tExploded[1].'M';
$intervalSpec.= $tExploded[2].'S'; $intervalSpec.= $tExploded[2].'S';
return $intervalSpec; return $intervalSpec;
} }
} }
protected function createConversionException($value, $exception = null) protected function createConversionException($value, $exception = null)
{ {
return ConversionException::conversionFailedFormat($value, $this->getName(), 'xx year xx mons xx days 01:02:03', $exception); return ConversionException::conversionFailedFormat($value, $this->getName(), 'xx year xx mons xx days 01:02:03', $exception);

View File

@ -28,7 +28,7 @@ class Country
/** /**
* @var string * @var string
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
* @groups({"read"}) * @groups({"read"})
* *
*/ */

View File

@ -43,14 +43,14 @@ class Language
/** /**
* @var string array * @var string array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;
/** /**
* Get id * Get id
* *
* @return string * @return string
*/ */
public function getId() public function getId()
{ {
@ -59,7 +59,7 @@ class Language
/** /**
* Set id * Set id
* *
* @param string $id * @param string $id
* @return Language * @return Language
*/ */
@ -78,17 +78,17 @@ class Language
public function setName($name) public function setName($name)
{ {
$this->name = $name; $this->name = $name;
return $this; return $this;
} }
/** /**
* Get name * Get name
* *
* @return string array * @return string array
*/ */
public function getName() public function getName()
{ {
return $this->name; return $this->name;
} }
} }

View File

@ -76,6 +76,12 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
*/ */
private bool $availableForUsers = false; private bool $availableForUsers = false;
/**
* @ORM\Column(type="boolean", nullable=true)
* @Serializer\Groups({"read"})
*/
private bool $active = true;
/** /**
* @ORM\ManyToOne(targetEntity=User::class) * @ORM\ManyToOne(targetEntity=User::class)
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
@ -192,6 +198,18 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
return $this; return $this;
} }
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function getCreatedBy(): ?User public function getCreatedBy(): ?User
{ {
return $this->createdBy; return $this->createdBy;

View File

@ -40,6 +40,12 @@ class LocationType
*/ */
private bool $availableForUsers = true; private bool $availableForUsers = true;
/**
* @ORM\Column(type="boolean", nullable=true)
* @Serializer\Groups({"read"})
*/
private bool $active = true;
/** /**
* @ORM\Column(type="string", length=32, options={"default"="optional"}) * @ORM\Column(type="string", length=32, options={"default"="optional"})
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
@ -70,6 +76,18 @@ class LocationType
return $this; return $this;
} }
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function getAvailableForUsers(): ?bool public function getAvailableForUsers(): ?bool
{ {
return $this->availableForUsers; return $this->availableForUsers;

View File

@ -52,7 +52,7 @@ class Scope
* *
* @var array * @var array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
* @Groups({"read"}) * @Groups({"read"})
*/ */
private $name = []; private $name = [];

View File

@ -116,7 +116,7 @@ class User implements AdvancedUserInterface {
* Array where SAML attributes's data are stored * Array where SAML attributes's data are stored
* @var array * @var array
* *
* @ORM\Column(type="json_array", nullable=true) * @ORM\Column(type="json", nullable=true)
*/ */
private $attributes; private $attributes;

View File

@ -3,10 +3,14 @@
namespace Chill\MainBundle\Entity; namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
/** /**
* @ORM\Entity * @ORM\Entity
* @ORM\Table("chill_main_user_job") * @ORM\Table("chill_main_user_job")
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
* "user_job"=UserJob::class
* })
*/ */
class UserJob class UserJob
{ {
@ -15,12 +19,14 @@ class UserJob
* @ORM\Id * @ORM\Id
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
* @Serializer\Groups({"read"})
*/ */
protected ?int $id = null; protected ?int $id = null;
/** /**
* @var array|string[]A * @var array|string[]A
* @ORM\Column(name="label", type="json") * @ORM\Column(name="label", type="json")
* @Serializer\Groups({"read"})
*/ */
protected array $label = []; protected array $label = [];

View File

@ -0,0 +1,85 @@
<?php
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\LocationType as EntityLocationType;
use Chill\MainBundle\Form\Type\PickAddressType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class LocationFormType extends AbstractType
{
// private TranslatableStringHelper $translatableStringHelper;
// /**
// * @param TranslatableStringHelper $translatableStringHelper
// */
// public function __construct(TranslatableStringHelper $translatableStringHelper)
// {
// $this->translatableStringHelper = $translatableStringHelper;
// }
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('locationType', EntityType::class, [
'class' => EntityLocationType::class,
'choice_attr' => function (EntityLocationType $entity) {
return [
'data-address' => $entity->getAddressRequired(),
'data-contact' => $entity->getContactData(),
];
},
'choice_label' => function (EntityLocationType $entity) {
//return $this->translatableStringHelper->localize($entity->getTitle()); //TODO not working. Cannot pass smthg in the constructor
return $entity->getTitle()['fr'];
},
])
->add('name', TextType::class)
->add('phonenumber1', TextType::class, ['required' => false])
->add('phonenumber2', TextType::class, ['required' => false])
->add('email', TextType::class, ['required' => false])
->add('address', PickAddressType::class, [
'required' => false,
'label' => 'Address',
'use_valid_from' => false,
'use_valid_to' => false,
'mapped' => false,
])
->add('active', ChoiceType::class,
[
'choices' => [
'Yes' => true,
'No' => false
],
'expanded' => true
]);
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Chill\MainBundle\Entity\Location'
));
}
/**
* @return string
*/
public function getBlockPrefix()
{
return 'chill_mainbundle_location';
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
final class LocationTypeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TranslatableStringFormType::class,
[
'label' => 'Name',
])
->add('availableForUsers', ChoiceType::class,
[
'choices' => [
'Yes' => true,
'No' => false
],
'expanded' => true
])
->add('addressRequired', ChoiceType::class,
[
'choices' => [
'optional' => LocationType::STATUS_OPTIONAL,
'required' => LocationType::STATUS_REQUIRED,
'never' => LocationType::STATUS_NEVER,
],
'expanded' => true
])
->add('contactData', ChoiceType::class,
[
'choices' => [
'optional' => LocationType::STATUS_OPTIONAL,
'required' => LocationType::STATUS_REQUIRED,
'never' => LocationType::STATUS_NEVER,
],
'expanded' => true
])
->add('active', ChoiceType::class,
[
'choices' => [
'Yes' => true,
'No' => false
],
'expanded' => true
]);
}
}

View File

@ -54,7 +54,7 @@ class CommentType extends AbstractType
$data = $event->getForm()->getData(); $data = $event->getForm()->getData();
$comment = $event->getData() ?? ['comment' => '']; $comment = $event->getData() ?? ['comment' => ''];
if ($data->getComment() !== $comment['comment']) { if (null !== $data && $data->getComment() !== $comment['comment']) {
$data->setDate(new \DateTime()); $data->setDate(new \DateTime());
$data->setUserId($this->user->getId()); $data->setUserId($this->user->getId());
$event->getForm()->setData($data); $event->getForm()->setData($data);

View File

@ -51,8 +51,10 @@ class CenterTransformer implements DataTransformerInterface
} }
} }
$ids = [];
if ($this->multiple) { if ($this->multiple) {
$ids = \explode(',', $id); $ids = explode(',', $id);
} else { } else {
$ids[] = (int) $id; $ids[] = (int) $id;
} }
@ -68,9 +70,9 @@ class CenterTransformer implements DataTransformerInterface
if ($this->multiple) { if ($this->multiple) {
return new ArrayCollection($centers); return new ArrayCollection($centers);
} else {
return $centers[0];
} }
return $centers[0];
} }
public function transform($center) public function transform($center)

View File

@ -3,9 +3,12 @@
namespace Chill\MainBundle\Form\Type\Listing; namespace Chill\MainBundle\Form\Type\Listing;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper; use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SearchType; use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
final class FilterOrderType extends \Symfony\Component\Form\AbstractType final class FilterOrderType extends \Symfony\Component\Form\AbstractType
@ -29,9 +32,32 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
]); ]);
} }
$checkboxesBuilder = $builder->create('checkboxes', null, [ 'compound' => true ]);
foreach ($helper->getCheckboxes() as $name => $c) {
$choices = \array_combine(
\array_map(function($c, $t) {
if ($t !== NULL) { return $t; }
else { return $c; }
}, $c['choices'], $c['trans']),
$c['choices']
);
$checkboxesBuilder->add($name, ChoiceType::class, [
'choices' => $choices,
'expanded' => true,
'multiple' => true,
]);
}
if (0 < count($helper->getCheckboxes())) {
$builder->add($checkboxesBuilder);
}
foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) { foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) {
switch($key) { switch($key) {
case 'q': case 'q':
case 'checkboxes'.$key:
break; break;
case 'page': case 'page':
$builder->add($key, HiddenType::class, [ $builder->add($key, HiddenType::class, [
@ -47,6 +73,17 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
} }
} }
public function buildView(FormView $view, FormInterface $form, array $options)
{
/** @var FilterOrderHelper $helper */
$helper = $options['helper'];
$view->vars['has_search_box'] = $helper->hasSearchBox();
$view->vars['checkboxes'] = [];
foreach ($helper->getCheckboxes() as $name => $c) {
$view->vars['checkboxes'][$name] = [];
}
}
public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver) public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver)
{ {
$resolver->setRequired('helper') $resolver->setRequired('helper')

View File

@ -23,7 +23,9 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper; use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper;
use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
@ -36,6 +38,7 @@ use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Security;
/** /**
* Allow to pick amongst available scope for the current * Allow to pick amongst available scope for the current
@ -46,14 +49,10 @@ use Symfony\Component\Security\Core\Role\Role;
* - `center`: the center of the entity * - `center`: the center of the entity
* - `role` : the role of the user * - `role` : the role of the user
* *
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/ */
class ScopePickerType extends AbstractType class ScopePickerType extends AbstractType
{ {
/** protected AuthorizationHelperInterface $authorizationHelper;
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/** /**
* @var TokenStorageInterface * @var TokenStorageInterface
@ -70,22 +69,26 @@ class ScopePickerType extends AbstractType
*/ */
protected $translatableStringHelper; protected $translatableStringHelper;
protected Security $security;
public function __construct( public function __construct(
AuthorizationHelper $authorizationHelper, AuthorizationHelperInterface $authorizationHelper,
TokenStorageInterface $tokenStorage, TokenStorageInterface $tokenStorage,
ScopeRepository $scopeRepository, ScopeRepository $scopeRepository,
Security $security,
TranslatableStringHelper $translatableStringHelper TranslatableStringHelper $translatableStringHelper
) { ) {
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->scopeRepository = $scopeRepository; $this->scopeRepository = $scopeRepository;
$this->security = $security;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$query = $this->buildAccessibleScopeQuery($options['center'], $options['role']); $items = $this->authorizationHelper->getReachableScopes($this->security->getUser(),
$items = $query->getQuery()->execute(); $options['role'], $options['center']);
if (1 !== count($items)) { if (1 !== count($items)) {
$builder->add('scope', EntityType::class, [ $builder->add('scope', EntityType::class, [
@ -94,9 +97,7 @@ class ScopePickerType extends AbstractType
'choice_label' => function (Scope $c) { 'choice_label' => function (Scope $c) {
return $this->translatableStringHelper->localize($c->getName()); return $this->translatableStringHelper->localize($c->getName());
}, },
'query_builder' => function () use ($options) { 'choices' => $items,
return $this->buildAccessibleScopeQuery($options['center'], $options['role']);
},
]); ]);
$builder->setDataMapper(new ScopePickerDataMapper()); $builder->setDataMapper(new ScopePickerDataMapper());
} else { } else {
@ -121,19 +122,22 @@ class ScopePickerType extends AbstractType
$resolver $resolver
// create `center` option // create `center` option
->setRequired('center') ->setRequired('center')
->setAllowedTypes('center', [Center::class]) ->setAllowedTypes('center', [Center::class, 'array', 'null'])
// create ``role` option // create ``role` option
->setRequired('role') ->setRequired('role')
->setAllowedTypes('role', ['string', Role::class]); ->setAllowedTypes('role', ['string', Role::class]);
} }
/** /**
* @param Center|array|Center[] $center
* @param string $role
* @return \Doctrine\ORM\QueryBuilder * @return \Doctrine\ORM\QueryBuilder
*/ */
protected function buildAccessibleScopeQuery(Center $center, Role $role) protected function buildAccessibleScopeQuery($center, $role)
{ {
$roles = $this->authorizationHelper->getParentRoles($role); $roles = $this->authorizationHelper->getParentRoles($role);
$roles[] = $role; $roles[] = $role;
$centers = $center instanceof Center ? [$center]: $center;
$qb = $this->scopeRepository->createQueryBuilder('s'); $qb = $this->scopeRepository->createQueryBuilder('s');
$qb $qb
@ -142,8 +146,8 @@ class ScopePickerType extends AbstractType
->join('rs.permissionsGroups', 'pg') ->join('rs.permissionsGroups', 'pg')
->join('pg.groupCenters', 'gc') ->join('pg.groupCenters', 'gc')
// add center constraint // add center constraint
->where($qb->expr()->eq('IDENTITY(gc.center)', ':center')) ->where($qb->expr()->in('IDENTITY(gc.center)', ':centers'))
->setParameter('center', $center->getId()) ->setParameter('centers', \array_map(fn(Center $c) => $c->getId(), $centers))
// role constraints // role constraints
->andWhere($qb->expr()->in('rs.role', ':roles')) ->andWhere($qb->expr()->in('rs.role', ':roles'))
->setParameter('roles', $roles) ->setParameter('roles', $roles)

View File

@ -28,6 +28,7 @@ use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Chill\MainBundle\Form\Type\Select2ChoiceType; use Chill\MainBundle\Form\Type\Select2ChoiceType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/** /**
* Extends choice to allow adding select2 library on widget * Extends choice to allow adding select2 library on widget
@ -37,31 +38,25 @@ use Chill\MainBundle\Templating\TranslatableStringHelper;
*/ */
class Select2CountryType extends AbstractType class Select2CountryType extends AbstractType
{ {
/** private RequestStack $requestStack;
* @var RequestStack
*/
private $requestStack;
/** private ObjectManager $em;
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/** protected TranslatableStringHelper $translatableStringHelper;
* @var ObjectManager
*/ protected ParameterBagInterface $parameterBag;
private $em;
public function __construct( public function __construct(
RequestStack $requestStack, RequestStack $requestStack,
ObjectManager $em, ObjectManager $em,
TranslatableStringHelper $translatableStringHelper TranslatableStringHelper $translatableStringHelper,
ParameterBagInterface $parameterBag
) )
{ {
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
$this->em = $em; $this->em = $em;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
$this->parameterBag = $parameterBag;
} }
public function getBlockPrefix() public function getBlockPrefix()
@ -82,19 +77,29 @@ class Select2CountryType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$locale = $this->requestStack->getCurrentRequest()->getLocale();
$countries = $this->em->getRepository('Chill\MainBundle\Entity\Country')->findAll(); $countries = $this->em->getRepository('Chill\MainBundle\Entity\Country')->findAll();
$choices = array(); $choices = [];
$preferredCountries = $this->parameterBag->get('chill_main.available_countries');
$preferredChoices = [];
foreach ($countries as $c) { foreach ($countries as $c) {
$choices[$c->getId()] = $this->translatableStringHelper->localize($c->getName()); $choices[$c->getId()] = $this->translatableStringHelper->localize($c->getName());
} }
foreach ($preferredCountries as $pc) {
foreach ($countries as $c) {
if ($c->getCountryCode() == $pc) {
$preferredChoices[$c->getId()] = $this->translatableStringHelper->localize($c->getName());
}
}
}
asort($choices, SORT_STRING | SORT_FLAG_CASE); asort($choices, SORT_STRING | SORT_FLAG_CASE);
$resolver->setDefaults(array( $resolver->setDefaults(array(
'class' => 'Chill\MainBundle\Entity\Country', 'class' => 'Chill\MainBundle\Entity\Country',
'choices' => array_combine(array_values($choices),array_keys($choices)) 'choices' => array_combine(array_values($choices),array_keys($choices)),
'preferred_choices' => array_combine(array_values($preferredChoices), array_keys($preferredChoices))
)); ));
} }
} }

View File

@ -28,37 +28,32 @@ use Chill\MainBundle\Form\Type\DataTransformer\MultipleObjectsToIdTransformer;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Chill\MainBundle\Form\Type\Select2ChoiceType; use Chill\MainBundle\Form\Type\Select2ChoiceType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/** /**
* Extends choice to allow adding select2 library on widget for languages (multiple) * Extends choice to allow adding select2 library on widget for languages (multiple)
*/ */
class Select2LanguageType extends AbstractType class Select2LanguageType extends AbstractType
{ {
/** private RequestStack $requestStack;
* @var RequestStack
*/
private $requestStack;
/** private ObjectManager $em;
* @var ObjectManager
*/
private $em;
/** protected TranslatableStringHelper $translatableStringHelper;
*
* @var TranslatableStringHelper protected ParameterBagInterface $parameterBag;
*/
protected $translatableStringHelper;
public function __construct( public function __construct(
RequestStack $requestStack, RequestStack $requestStack,
ObjectManager $em, ObjectManager $em,
TranslatableStringHelper $translatableStringHelper TranslatableStringHelper $translatableStringHelper,
ParameterBagInterface $parameterBag
) )
{ {
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
$this->em = $em; $this->em = $em;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
$this->parameterBag = $parameterBag;
} }
public function getBlockPrefix() public function getBlockPrefix()
@ -79,19 +74,24 @@ class Select2LanguageType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$locale = $this->requestStack->getCurrentRequest()->getLocale();
$languages = $this->em->getRepository('Chill\MainBundle\Entity\Language')->findAll(); $languages = $this->em->getRepository('Chill\MainBundle\Entity\Language')->findAll();
$choices = array(); $preferredLanguages = $this->parameterBag->get('chill_main.available_languages');
$choices = [];
$preferredChoices = [];
foreach ($languages as $l) { foreach ($languages as $l) {
$choices[$l->getId()] = $this->translatableStringHelper->localize($l->getName()); $choices[$l->getId()] = $this->translatableStringHelper->localize($l->getName());
} }
foreach ($preferredLanguages as $l) {
$preferredChoices[$l] = $choices[$l];
}
asort($choices, SORT_STRING | SORT_FLAG_CASE); asort($choices, SORT_STRING | SORT_FLAG_CASE);
$resolver->setDefaults(array( $resolver->setDefaults(array(
'class' => 'Chill\MainBundle\Entity\Language', 'class' => 'Chill\MainBundle\Entity\Language',
'choices' => array_combine(array_values($choices),array_keys($choices)) 'choices' => array_combine(array_values($choices), array_keys($choices)),
'preferred_choices' => array_combine(array_values($preferredChoices), array_keys($preferredChoices))
)); ));
} }
} }

View File

@ -17,6 +17,8 @@
*/ */
namespace Chill\MainBundle\Form\Type; namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
@ -56,14 +58,18 @@ class UserPickerType extends AbstractType
protected UserRepository $userRepository; protected UserRepository $userRepository;
protected UserACLAwareRepositoryInterface $userACLAwareRepository;
public function __construct( public function __construct(
AuthorizationHelper $authorizationHelper, AuthorizationHelper $authorizationHelper,
TokenStorageInterface $tokenStorage, TokenStorageInterface $tokenStorage,
UserRepository $userRepository UserRepository $userRepository,
UserACLAwareRepositoryInterface $userACLAwareRepository
) { ) {
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->userRepository = $userRepository; $this->userRepository = $userRepository;
$this->userACLAwareRepository = $userACLAwareRepository;
} }
@ -72,7 +78,7 @@ class UserPickerType extends AbstractType
$resolver $resolver
// create `center` option // create `center` option
->setRequired('center') ->setRequired('center')
->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class ]) ->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class, 'null', 'array' ])
// create ``role` option // create ``role` option
->setRequired('role') ->setRequired('role')
->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ]) ->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ])
@ -86,17 +92,19 @@ class UserPickerType extends AbstractType
->setDefault('choice_label', function(User $u) { ->setDefault('choice_label', function(User $u) {
return $u->getUsername(); return $u->getUsername();
}) })
->setDefault('scope', null)
->setAllowedTypes('scope', [Scope::class, 'array', 'null'])
->setNormalizer('choices', function(Options $options) { ->setNormalizer('choices', function(Options $options) {
$users = $this->authorizationHelper $users = $this->userACLAwareRepository
->findUsersReaching($options['role'], $options['center']); ->findUsersByReachedACL($options['role'], $options['center'], $options['scope'], true);
if (NULL !== $options['having_permissions_group_flag']) { if (NULL !== $options['having_permissions_group_flag']) {
return $this->userRepository return $this->userRepository
->findUsersHavingFlags($options['having_permissions_group_flag'], $users) ->findUsersHavingFlags($options['having_permissions_group_flag'], $users)
; ;
} }
return $users; return $users;
}) })
; ;

View File

@ -0,0 +1,62 @@
<?php
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\ParentRoleHelper;
use Doctrine\ORM\EntityManagerInterface;
class UserACLAwareRepository implements UserACLAwareRepositoryInterface
{
private ParentRoleHelper $parentRoleHelper;
private EntityManagerInterface $em;
public function __construct(ParentRoleHelper $parentRoleHelper, EntityManagerInterface $em)
{
$this->parentRoleHelper = $parentRoleHelper;
$this->em = $em;
}
public function findUsersByReachedACL(string $role, $center, $scope = null, bool $onlyEnabled = true): array
{
$parents = $this->parentRoleHelper->getParentRoles($role);
$parents[] = $role;
$qb = $this->em->createQueryBuilder();
$qb
->select('u')
->from(User::class, 'u')
->join('u.groupCenters', 'gc')
->join('gc.permissionsGroup', 'pg')
->join('pg.roleScopes', 'rs')
->where($qb->expr()->in('rs.role', $parents))
;
if ($onlyEnabled) {
$qb->andWhere($qb->expr()->eq('u.enabled', "'TRUE'"));
}
if (NULL !== $center) {
$centers = $center instanceof Center ? [$center] : $center;
$qb
->andWhere($qb->expr()->in('gc.center', ':centers'))
->setParameter('centers', $centers)
;
}
if (NULL !== $scope) {
$scopes = $scope instanceof Scope ? [$scope] : $scope;
$qb
->andWhere($qb->expr()->in('rs.scope', ':scopes'))
->setParameter('scopes', $scopes)
;
}
return $qb->getQuery()->getResult();
}
}

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