...
 
Commits (118)
......@@ -10,26 +10,20 @@
- php Resources/test/Fixtures/App/app/console doctrine:fixtures:load --env=test --no-interaction
stages:
- test
- deploy
- test
- build-doc
- deploy-doc
test:php-7:
image: chill/ci-image:php-7
stage: test
<<: *test_definition
script: php vendor/bin/phpunit
test:php-7.1:
image: chill/ci-image:php-7.1
test:php-7.2:
image: chill/ci-image:php-7.2
stage: test
<<: *test_definition
script: php vendor/bin/phpunit
script: APP_ENV=test php vendor/bin/phpunit
deploy-packagist:
stage: deploy
image: chill/ci-image:php-7
image: chill/ci-image:php-7.2
before_script:
# test that PACKAGIST USERNAME and PACKAGIST_TOKEN variable are set
- if [ -z ${PACKAGIST_USERNAME+x} ]; then echo "Please set PACKAGIST_USERNAME variable"; exit -1; fi
......@@ -42,7 +36,7 @@ deploy-packagist:
api-doc-build:
stage: build-doc
environment: api-doc
image: chill/ci-image:php-7
image: chill/ci-image:php-7.2
before_script:
- mkdir api-doc
script: apigen generate --destination api-doc/$CI_BUILD_REF_NAME/$CI_PROJECT_NAME
......
Version 1.5.1
=============
- Improve import of person to allow multiple centers by file ;
- Launch an event on person import ;
- Allow person to have a `null` gender ;
- Allow filters and aggregator to handle null gender ;
- remove inexistant `person.css` file
- fix bug in accompanying person validation
Version 1.5.2
==============
- Add an column with fullname canonical (lowercase and unaccent) to persons entity ;
- Add a trigram index on fullname canonical ;
- Add a "similar person matcher", which allow to detect person with similar names when adding a person ;
- Add a research of persons by fuzzy name, returning result with a similarity of 0.15 ;
Thanks to @matla :-)
Version 1.5.3
=============
- add filtering on accompanying period
- fix problems in gender filter
Version 1.5.4
=============
- add filenumber in person header
Version 1.5.5
=============
- Fix bug in accompanying period filter
Branche master
==============
- Update address validation
......@@ -5,6 +5,7 @@ namespace Chill\PersonBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Chill\PersonBundle\Widget\PersonListWidgetFactory;
use Chill\PersonBundle\DependencyInjection\CompilerPass\AccompanyingPeriodTimelineCompilerPass;
class ChillPersonBundle extends Bundle
{
......@@ -14,5 +15,7 @@ class ChillPersonBundle extends Bundle
$container->getExtension('chill_main')
->addWidgetFactory(new PersonListWidgetFactory());
$container->addCompilerPass(new AccompanyingPeriodTimelineCompilerPass());
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -3,7 +3,7 @@
/*
* Chill is a software for social workers
*
* Copyright (C) 2016, Champs Libres Cooperative SCRLFS,
* Copyright (C) 2016, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
......@@ -23,6 +23,7 @@
namespace Chill\PersonBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Form\Type\AddressType;
use Chill\MainBundle\Entity\Address;
......@@ -37,97 +38,97 @@ use Symfony\Component\HttpFoundation\Request;
*/
class PersonAddressController extends Controller
{
public function listAction($person_id)
{
$person = $this->getDoctrine()->getManager()
->getRepository('ChillPersonBundle:Person')
->find($person_id);
if ($person === null) {
throw $this->createNotFoundException("Person with id $person_id not"
. " found ");
}
$this->denyAccessUnlessGranted(
'CHILL_PERSON_SEE',
$person,
"You are not allowed to edit this person."
);
return $this->render('ChillPersonBundle:Address:list.html.twig', array(
'person' => $person
));
}
public function newAction($person_id)
{
$person = $this->getDoctrine()->getManager()
->getRepository('ChillPersonBundle:Person')
->find($person_id);
if ($person === null) {
throw $this->createNotFoundException("Person with id $person_id not"
. " found ");
}
$this->denyAccessUnlessGranted(
'CHILL_PERSON_UPDATE',
$person,
"You are not allowed to edit this person."
);
$address = new Address();
$form = $this->createCreateForm($person, $address);
return $this->render('ChillPersonBundle:Address:new.html.twig', array(
'person' => $person,
'form' => $form->createView()
));
}
public function createAction($person_id, Request $request)
{
$person = $this->getDoctrine()->getManager()
->getRepository('ChillPersonBundle:Person')
->find($person_id);
if ($person === null) {
throw $this->createNotFoundException("Person with id $person_id not"
. " found ");
}
$this->denyAccessUnlessGranted(
'CHILL_PERSON_UPDATE',
$person,
"You are not allowed to edit this person."
);
$address = new Address();
$form = $this->createCreateForm($person, $address);
$form->handleRequest($request);
$person->addAddress($address);
if ($form->isSubmitted()) {
if ($form->isSubmitted() && $form->isValid()) {
$validatePersonErrors = $this->validatePerson($person);
if (count($validatePersonErrors) !== 0) {
foreach ($validatePersonErrors as $error) {
$this->addFlash('error', $error->getMessage());
}
} elseif ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->flush();
$this->addFlash(
'success',
$this->get('translator')->trans('The new address was created successfully')
);
return $this->redirectToRoute('chill_person_address_list', array(
'person_id' => $person->getId()
));
......@@ -136,66 +137,66 @@ class PersonAddressController extends Controller
->trans('Error! Address not created!'));
}
}
return $this->render('ChillPersonBundle:Address:new.html.twig', array(
'person' => $person,
'form' => $form->createView()
));
}
public function editAction($person_id, $address_id)
{
$person = $this->getDoctrine()->getManager()
->getRepository('ChillPersonBundle:Person')
->find($person_id);
if ($person === null) {
throw $this->createNotFoundException("Person with id $person_id not"
. " found ");
}
$this->denyAccessUnlessGranted(
'CHILL_PERSON_UPDATE',
$person,
"You are not allowed to edit this person."
);
$address = $this->findAddressById($person, $address_id);
$form = $this->createEditForm($person, $address);
return $this->render('ChillPersonBundle:Address:edit.html.twig', array(
'person' => $person,
'address' => $address,
'form' => $form->createView()
));
}
public function updateAction($person_id, $address_id, Request $request)
{
$person = $this->getDoctrine()->getManager()
->getRepository('ChillPersonBundle:Person')
->find($person_id);
if ($person === null) {
throw $this->createNotFoundException("Person with id $person_id not"
. " found ");
}
$this->denyAccessUnlessGranted(
'CHILL_PERSON_UPDATE',
$person,
"You are not allowed to edit this person."
);
$address = $this->findAddressById($person, $address_id);
$form = $this->createEditForm($person, $address);
$form->handleRequest($request);
if ($request->getMethod() === 'POST') {
if ($form->isSubmitted() and $form->isValid()) {
$validatePersonErrors = $this->validatePerson($person);
if (count($validatePersonErrors) !== 0) {
foreach ($validatePersonErrors as $error) {
$this->addFlash('error', $error->getMessage());
......@@ -203,11 +204,11 @@ class PersonAddressController extends Controller
} elseif ($form->isValid()) {
$this->getDoctrine()->getManager()
->flush();
$this->addFlash('success', $this->get('translator')->trans(
"The address has been successfully updated"
));
return $this->redirectToRoute('chill_person_address_list', array(
'person_id' => $person->getId()
));
......@@ -216,14 +217,14 @@ class PersonAddressController extends Controller
->trans('Error when updating the period'));
}
}
return $this->render('ChillPersonBundle:Address:edit.html.twig', array(
'person' => $person,
'address' => $address,
'form' => $form->createView()
));
}
/**
* @param Person $person
* @param Address $address
......@@ -238,16 +239,16 @@ class PersonAddressController extends Controller
'address_id' => $address->getId()
))
));
$form->add('submit', 'submit', array(
$form->add('submit', SubmitType::class, array(
'label' => 'Submit'
));
return $form;
}
/**
*
*
* @param Person $person
* @param Address $address
* @return \Symfony\Component\Form\Form
......@@ -260,16 +261,16 @@ class PersonAddressController extends Controller
'person_id' => $person->getId()
))
));
$form->add('submit', 'submit', array(
$form->add('submit', SubmitType::class, array(
'label' => 'Submit'
));
return $form;
}
/**
*
*
* @param Person $person
* @param int $address_id
* @return Address
......@@ -282,15 +283,15 @@ class PersonAddressController extends Controller
->where(Criteria::expr()->eq('id', $address_id))
->setMaxResults(1);
$addresses = $person->getAddresses()->matching($criteria);
if (count($addresses) === 0) {
throw $this->createNotFoundException("Address with id $address_id "
. "matching person $person_id not found ");
}
return $addresses->first();
}
/**
* @param Chill\PersonBundle\Entity\Person $person
* @return \Symfony\Component\Validator\ConstraintViolationListInterface
......@@ -298,14 +299,14 @@ class PersonAddressController extends Controller
private function validatePerson(Person $person)
{
$errors = $this->get('validator')
->validate($person, array('Default'));
->validate($person, null, array('Default'));
$errors_addresses_consistent = $this->get('validator')
->validate($person, array('addresses_consistent'));
->validate($person, null, array('addresses_consistent'));
foreach($errors_addresses_consistent as $error) {
$errors->add($error);
}
return $errors;
}
}
This diff is collapsed.
......@@ -19,6 +19,8 @@
namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
......@@ -30,7 +32,23 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
*/
class TimelinePersonController extends Controller
{
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* TimelinePersonController constructor.
*
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
public function personAction(Request $request, $person_id)
{
$person = $this->getDoctrine()
......@@ -52,7 +70,10 @@ class TimelinePersonController extends Controller
);
$paginator = $paginatorFactory->create($nbItems);
$event = new PrivacyEvent($person, array('action' => 'timeline'));
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
return $this->render('ChillPersonBundle:Timeline:index.html.twig', array
(
'timeline' => $timelineBuilder->getTimelineHTML(
......
......@@ -26,6 +26,7 @@ use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Chill\MainBundle\DependencyInjection\MissingBundleException;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
/**
......@@ -57,6 +58,16 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$loader->load('services.yml');
$loader->load('services/widgets.yml');
$loader->load('services/exports.yml');
$loader->load('services/fixtures.yml');
$loader->load('services/controller.yml');
$loader->load('services/search.yml');
$loader->load('services/menu.yml');
$loader->load('services/privacyEvent.yml');
$loader->load('services/command.yml');
if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
$loader->load('services/exports_accompanying_period.yml');
}
}
private function handlePersonFieldsParameters(ContainerBuilder $container, $config)
......@@ -68,7 +79,14 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$container->setParameter('chill_person.person_fields', $config);
foreach ($config as $key => $value) {
$container->setParameter('chill_person.person_fields.'.$key, $value);
switch($key) {
case 'accompanying_period':
$container->setParameter('chill_person.accompanying_period', $value);
break;
default:
$container->setParameter('chill_person.person_fields.'.$key, $value);
break;
}
}
}
......@@ -160,7 +178,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$container->prependExtensionConfig('security', array(
'role_hierarchy' => array(
'CHILL_PERSON_UPDATE' => array('CHILL_PERSON_SEE'),
'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE')
'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE'),
PersonVoter::LISTS => [ ChillExportVoter::EXPORT ],
PersonVoter::STATS => [ ChillExportVoter::EXPORT ]
)
));
}
......
<?php
/*
* Copyright (C) 2018 Champs-Libres <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\PersonBundle\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Remove services which add AccompanyingPeriod to timeline if
* accompanying_periods are set to `hidden`
*
*/
class AccompanyingPeriodTimelineCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
// remove services when accompanying period are hidden
if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
return;
}
$definitions = [
'chill.person.timeline.accompanying_period_opening',
'chill.person.timeline.accompanying_period_closing'
];
foreach($definitions as $definition) {
$container
->removeDefinition($definition)
;
}
$definition = $container->getDefinition('chill.main.timeline_builder');
// we have to remove all methods call, and re-add them if not linked
// to this service
$calls = $definition->getMethodCalls();
foreach($calls as list($method, $arguments)) {
if ($method !== 'addProvider') {
continue;
}
$definition->removeMethodCall('addProvider');
if (FALSE === \in_array($arguments[1], $definitions)) {
$definition->addMethodCall($method, $arguments);
}
}
}
}
......@@ -12,7 +12,7 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
*/
class Configuration implements ConfigurationInterface
{
private $validationBirthdateNotAfterInfos = "The period before today during which"
. " any birthdate is not allowed. The birthdate is expressed as ISO8601 : "
. "https://en.wikipedia.org/wiki/ISO_8601#Durations";
......@@ -23,7 +23,7 @@ class Configuration implements ConfigurationInterface
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('cl_chill_person');
$rootNode
->canBeDisabled()
->children()
......@@ -50,14 +50,14 @@ class Configuration implements ConfigurationInterface
$interval = new \DateInterval($period);
} catch (\Exception $ex) {
return true;
}
}
return false;
})
->thenInvalid('Invalid period for birthdate validation : "%s" '
. 'The parameter should match duration as defined by ISO8601 : '
. 'https://en.wikipedia.org/wiki/ISO_8601#Durations')
->end() // birthdate_not_after, parent = children of validation
->end() // children for 'validation', parent = validation
->end() //validation, parent = children of root
->end() // children of root, parent = root
......@@ -67,31 +67,44 @@ class Configuration implements ConfigurationInterface
->append($this->addFieldNode('place_of_birth'))
->append($this->addFieldNode('email'))
->append($this->addFieldNode('phonenumber'))
->append($this->addFieldNode('mobilenumber'))
->append($this->addFieldNode('contact_info'))
->append($this->addFieldNode('nationality'))
->append($this->addFieldNode('country_of_birth'))
->append($this->addFieldNode('marital_status'))
->append($this->addFieldNode('spoken_languages'))
->append($this->addFieldNode('address'))
->append($this->addFieldNode('accompanying_period'))
->append($this->addFieldNode('memo'))
->end() //children for 'person_fields', parent = array 'person_fields'
->end() // person_fields, parent = children of root
->end() // children of 'root', parent = root
;
return $treeBuilder;
}
private function addFieldNode($key)
{
$tree = new TreeBuilder();
$node = $tree->root($key, 'enum');
switch($key) {
case 'accompanying_period':
$info = "If the accompanying periods are shown";
break;
default:
$info = "If the field $key must be shown";
break;
}
$node
->values(array('hidden', 'visible'))
->defaultValue('visible')
->info("If the field $key must be shown")
->info($info)
->end();
return $node;
}
}
......@@ -23,7 +23,7 @@
namespace Chill\PersonBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\ExecutionContextInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* AccompanyingPeriod
......@@ -224,9 +224,9 @@ class AccompanyingPeriod
}
if (! $this->isClosingAfterOpening()) {
$context->addViolationAt('dateClosing',
'The date of closing is before the date of opening',
array(), null);
$context->buildViolation('The date of closing is before the date of opening')
->atPath('dateClosing')
->addViolation();
}
}
......
This diff is collapsed.
<?php
/*
* Copyright (C) 2019 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\PersonBundle\Export;
use Doctrine\ORM\QueryBuilder;
/**
*
*
*/
class AbstractAccompanyingPeriodExportElement
{
/**
* Return true if "accompanying_period" alias is present in the query alises.
*
* @param QueryBuilder $query
* @return bool
*/
protected function havingAccompanyingPeriodInJoin(QueryBuilder $query): bool
{
$joins = $query->getDQLPart('join') ?? [];
return (\in_array('accompanying_period', $query->getAllAliases()));
}
/**
* Add the accompanying period alias to the query
*
* @param QueryBuilder $query
* @return void
* @throws \LogicException if the "person" alias is not present and attaching accompanying period is not possible
*/
protected function addJoinAccompanyingPeriod(QueryBuilder $query): void
{
if (FALSE === $this->havingAccompanyingPeriodInJoin($query)) {
if (FALSE === \in_array('person', $query->getAllAliases())) {
throw new \LogicException("the alias 'person' does not exists in "
. "query builder");
}
$query->join('person.accompanyingPeriods', 'accompanying_period');
}
}
}
......@@ -30,14 +30,15 @@ use Symfony\Component\Security\Core\Role\Role;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
/**
*
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class CountryOfBirthAggregator implements AggregatorInterface,
class CountryOfBirthAggregator implements AggregatorInterface,
ExportElementValidatedInterface
{
/**
......@@ -45,19 +46,19 @@ class CountryOfBirthAggregator implements AggregatorInterface,
* @var EntityRepository
*/
protected $countriesRepository;
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/**
*
* @var TranslatorInterface
*/
protected $translator;
public function __construct(EntityRepository $countriesRepository,
TranslatableStringHelper $translatableStringHelper,
TranslatorInterface $translator)
......@@ -66,16 +67,16 @@ class CountryOfBirthAggregator implements AggregatorInterface,
$this->translatableStringHelper = $translatableStringHelper;
$this->translator = $translator;
}
public function applyOn()
{
return 'person';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('group_by_level', 'choice', array(
$builder->add('group_by_level', ChoiceType::class, array(
'choices' => array(
'Group by continents' => 'continent',
'Group by country' => 'country'
......@@ -84,9 +85,9 @@ class CountryOfBirthAggregator implements AggregatorInterface,
'expanded' => true,
'multiple' => false
));
}
public function validateForm($data, ExecutionContextInterface $context)
{
if ($data['group_by_level'] === null) {
......@@ -94,7 +95,7 @@ class CountryOfBirthAggregator implements AggregatorInterface,
->addViolation();
}
}
public function alterQuery(QueryBuilder $qb, $data)
{
// add a clause in select part
......@@ -109,10 +110,10 @@ class CountryOfBirthAggregator implements AggregatorInterface,
. 'WHEN countryOfBirth.countryCode IN(:cob_south_america_codes) THEN \'SA\' '
. 'WHEN countryOfBirth.countryCode IN(:cob_oceania_codes) THEN \'OC\' '
. 'WHEN countryOfBirth.countryCode IN(:cob_antartica_codes) THEN \'AN\' '
. 'ELSE \'\' '
. 'ELSE \'\' '
. 'END as country_of_birth_aggregator ';
$qb->addSelect($clause);
$params =
$params =
array(
'cob_africa_codes' => CountriesInfo::getCountriesCodeByContinent('AF'),
'cob_asia_codes' => CountriesInfo::getCountriesCodeByContinent('AS'),
......@@ -129,47 +130,47 @@ class CountryOfBirthAggregator implements AggregatorInterface,
throw new \LogicException("The group_by_level '".$data['group_by_level']
." is not known.");
}
$qb->leftJoin('person.countryOfBirth', 'countryOfBirth');
// add group by
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('country_of_birth_aggregator');
} else {
$qb->groupBy('country_of_birth_aggregator');
}
}
public function getTitle()
{
return "Group people by country of birth";
}
public function getQueryKeys($data)
{
return array('country_of_birth_aggregator');
}
public function addRole()
{
return NULL;
}
public function getLabels($key, array $values, $data)
{
if ($data['group_by_level'] === 'country') {
$qb = $this->countriesRepository->createQueryBuilder('c');
$countries = $qb
->andWhere($qb->expr()->in('c.countryCode', ':countries'))
->setParameter('countries', $values)
->getQuery()
->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR);
// initialize array and add blank key for null values
$labels[''] = $this->translator->trans('without data');
$labels['_header'] = $this->translator->trans('Country of birth');
......@@ -177,9 +178,9 @@ class CountryOfBirthAggregator implements AggregatorInterface,
$labels[$row['c_countryCode']] = $this->translatableStringHelper->localize($row['c_name']);
}
} elseif ($data['group_by_level'] === 'continent') {
$labels = array(
'EU' => $this->translator->trans('Europe'),
'AS' => $this->translator->trans('Asia'),
......@@ -192,11 +193,11 @@ class CountryOfBirthAggregator implements AggregatorInterface,
'_header' => $this->translator->trans('Continent of birth')
);
}
return function($value) use ($labels) {
return $labels[$value];
};
}
}
}
......@@ -83,6 +83,10 @@ class GenderAggregator implements AggregatorInterface
return $this->translator->trans('woman');
case Person::MALE_GENDER :
return $this->translator->trans('man');
case Person::BOTH_GENDER:
return $this->translator->trans('both');
case null:
return $this->translator->trans('Not given');
case '_header' :
return $this->translator->trans('Gender');
default:
......
......@@ -30,9 +30,10 @@ use Symfony\Component\Security\Core\Role\Role;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
/**
*
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
......@@ -44,19 +45,19 @@ class NationalityAggregator implements AggregatorInterface,
* @var EntityRepository
*/
protected $countriesRepository;
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/**
*
* @var TranslatorInterface
*/
protected $translator;
public function __construct(EntityRepository $countriesRepository,
TranslatableStringHelper $translatableStringHelper,
TranslatorInterface $translator)
......@@ -65,16 +66,16 @@ class NationalityAggregator implements AggregatorInterface,
$this->translatableStringHelper = $translatableStringHelper;
$this->translator = $translator;
}
public function applyOn()
{
return 'person';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('group_by_level', 'choice', array(
$builder->add('group_by_level', ChoiceType::class, array(
'choices' => array(
'Group by continents' => 'continent',
'Group by country' => 'country'
......@@ -83,7 +84,7 @@ class NationalityAggregator implements AggregatorInterface,
'expanded' => true,
'multiple' => false
));
}
public function validateForm($data, ExecutionContextInterface $context)
......@@ -93,7 +94,7 @@ class NationalityAggregator implements AggregatorInterface,
->addViolation();
}
}
public function alterQuery(QueryBuilder $qb, $data)
{
// add a clause in select part
......@@ -108,10 +109,10 @@ class NationalityAggregator implements AggregatorInterface,
. 'WHEN nationality.countryCode IN(:south_america_codes) THEN \'SA\' '
. 'WHEN nationality.countryCode IN(:oceania_codes) THEN \'OC\' '
. 'WHEN nationality.countryCode IN(:antartica_codes) THEN \'AN\' '
. 'ELSE \'\' '
. 'ELSE \'\' '
. 'END as nationality_aggregator ';
$qb->addSelect($clause);
$params =
$params =
array(
'africa_codes' => CountriesInfo::getCountriesCodeByContinent('AF'),
'asia_codes' => CountriesInfo::getCountriesCodeByContinent('AS'),
......@@ -128,47 +129,47 @@ class NationalityAggregator implements AggregatorInterface,
throw new \LogicException("The group_by_level '".$data['group_by_level']
." is not known.");
}
$qb->leftJoin('person.nationality', 'nationality');
// add group by
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('nationality_aggregator');
} else {
$qb->groupBy('nationality_aggregator');
}
}
public function getTitle()
{
return "Group people by nationality";
}
public function getQueryKeys($data)
{
return array('nationality_aggregator');
}
public function addRole()
{
return NULL;
}
public function getLabels($key, array $values, $data)
{
if ($data['group_by_level'] === 'country') {
$qb = $this->countriesRepository->createQueryBuilder('c');
$countries = $qb
->andWhere($qb->expr()->in('c.countryCode', ':countries'))
->setParameter('countries', $values)
->getQuery()
->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR);
// initialize array and add blank key for null values
$labels[''] = $this->translator->trans('without data');
$labels['_header'] = $this->translator->trans('Nationality');
......@@ -176,9 +177,9 @@ class NationalityAggregator implements AggregatorInterface,
$labels[$row['c_countryCode']] = $this->translatableStringHelper->localize($row['c_name']);
}
} elseif ($data['group_by_level'] === 'continent') {
$labels = array(
'EU' => $this->translator->trans('Europe'),
'AS' => $this->translator->trans('Asia'),
......@@ -191,11 +192,11 @@ class NationalityAggregator implements AggregatorInterface,
'_header' => $this->translator->trans('Continent')
);
}
return function($value) use ($labels) {
return $labels[$value];
};
}
}
}
This diff is collapsed.
<?php
/*
* Copyright (C) 2019 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\PersonBundle\Export\Filter;
use Chill\MainBundle\Export\FilterInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Form\Type\ChillDateType;
use Doctrine\DBAL\Types\Type;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
/**
*
*
*/
class AccompanyingPeriodClosingFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
{
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$this->addJoinAccompanyingPeriod($qb);
$clause = $qb->expr()->andX(
$qb->expr()->lte('accompanying_period.closingDate', ':date_to'),
$qb->expr()->gte('accompanying_period.closingDate', ':date_from'));
$qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Type::DATE);
$qb->setParameter('date_to', $data['date_to'], Type::DATE);
}
public function applyOn(): string
{
return 'person';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', ChillDateType::class, array(
'label' => "Having an accompanying period closed after this date",
'data' => new \DateTime("-1 month"),
));
$builder->add('date_to', ChillDateType::class, array(
'label' => "Having an accompanying period closed before this date",
'data' => new \DateTime(),
));
}
public function describeAction($data, $format = 'string')
{
return [
"Filtered by accompanying period: persons having an accompanying period"
. " closed between the %date_from% and %date_to%",
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y')
]
];
}
public function getTitle(): string
{
return "Filter by accompanying period: closed between two dates";
}
}
<?php
/*
* Copyright (C) 2019 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\PersonBundle\Export\Filter;
use Chill\MainBundle\Export\FilterInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Form\Type\ChillDateType;
use Doctrine\DBAL\Types\Type;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
/**
*
*
*/
class AccompanyingPeriodFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
{
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$this->addJoinAccompanyingPeriod($qb);
$clause = $qb->expr()->andX();
$clause->add(
$qb->expr()->lte('accompanying_period.openingDate', ':date_to')
);
$clause->add(
$qb->expr()->orX(
$qb->expr()->gte('accompanying_period.closingDate', ':date_from'),
$qb->expr()->isNull('accompanying_period.closingDate')
)
);
$qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Type::DATE);
$qb->setParameter('date_to', $data['date_to'], Type::DATE);
}
public function applyOn(): string
{
return 'person';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', ChillDateType::class, array(
'label' => "Having an accompanying period opened after this date",
'data' => new \DateTime("-1 month"),
));
$builder->add('date_to', ChillDateType::class, array(
'label' => "Having an accompanying period ending before this date, or "
. "still opened at this date",
'data' => new \DateTime(),
));
}
public function describeAction($data, $format = 'string')
{
return [
"Filtered by accompanying period: persons having an accompanying period"
. " opened after the %date_from% and closed before the %date_to% (or still opened "
. "at the %date_to%)",
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y')
]
];
}
public function getTitle(): string
{
return "Filter by accompanying period: active period";
}
}
<?php
/*
* Copyright (C) 2019 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\PersonBundle\Export\Filter;
use Chill\MainBundle\Export\FilterInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Form\Type\ChillDateType;
use Doctrine\DBAL\Types\Type;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
/**
*
*
*/
class AccompanyingPeriodOpeningFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
{
public function addRole()
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$this->addJoinAccompanyingPeriod($qb);
$clause = $qb->expr()->andX(
$qb->expr()->lte('accompanying_period.openingDate', ':date_to'),
$qb->expr()->gte('accompanying_period.openingDate', ':date_from'));
$qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Type::DATE);
$qb->setParameter('date_to', $data['date_to'], Type::DATE);
}
public function applyOn(): string
{
return 'person';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', ChillDateType::class, array(
'label' => "Having an accompanying period opened after this date",
'data' => new \DateTime("-1 month"),
));
$builder->add('date_to', ChillDateType::class, array(
'label' => "Having an accompanying period opened before this date",
'data' => new \DateTime(),
));
}
public function describeAction($data, $format = 'string')
{
return [
"Filtered by accompanying period: persons having an accompanying period"
. " opened between the %date_from% and %date_to%",
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y')
]
];
}
public function getTitle(): string
{
return "Filter by accompanying period: starting between two dates";
}
}
......@@ -27,82 +27,114 @@ use Doctrine\ORM\Query\Expr;
use Symfony\Component\Security\Core\Role\Role;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Translation\TranslatorInterface;
/**
*
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class GenderFilter implements FilterInterface,
ExportElementValidatedInterface
{
/**
*
* @var TranslatorInterface
*/
protected $translator;
public function applyOn()
function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function applyOn()
{
return 'person';
}
/**
*
*
*/
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_genders', 'choice', array(
$builder->add('accepted_genders', ChoiceType::class, array(
'choices' => array(
Person::FEMALE_GENDER => 'Woman',
Person::MALE_GENDER => 'Man'
'Woman' => Person::FEMALE_GENDER,
'Man' => Person::MALE_GENDER,
'Both' => Person::BOTH_GENDER,
'Not given' => 'null'
),
'multiple' => false,
'expanded' => false
'choices_as_values' => true,
'multiple' => true,
'expanded' => true
));
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (count($data['accepted_genders']) === 0) {
if (!is_array($data['accepted_genders']) || count($data['accepted_genders']) === 0 ) {
$context->buildViolation("You should select an option")
->addViolation();
}
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('person.gender', ':person_gender');
$isIn = $qb->expr()->in('person.gender', ':person_gender');
if (!\in_array('null', $data['accepted_genders'])) {
$clause = $isIn;
} else {
$clause = $qb->expr()->orX($isIn, $qb->expr()->isNull('person.gender'));
}
if ($where instanceof Expr\Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('person_gender', $data['accepted_genders']);