diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php index 536f414f2..733ff9e27 100644 --- a/DependencyInjection/ChillPersonExtension.php +++ b/DependencyInjection/ChillPersonExtension.php @@ -8,6 +8,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Chill\MainBundle\DependencyInjection\MissingBundleException; +use Chill\PersonBundle\Security\Authorization\PersonVoter; /** * This is the class that loads and manages your bundle configuration @@ -111,7 +112,8 @@ 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'), + 'CHILL_PERSON_SEE' => array(PersonVoter::STATS) ) )); } diff --git a/Export/Aggregator/GenderAggregator.php b/Export/Aggregator/GenderAggregator.php new file mode 100644 index 000000000..bc75c4350 --- /dev/null +++ b/Export/Aggregator/GenderAggregator.php @@ -0,0 +1,92 @@ + + * + * 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 . + */ + +namespace Chill\PersonBundle\Export\Aggregator; + +use Chill\MainBundle\Export\AggregatorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Translation\TranslatorInterface; +use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Export\Declarations; + +/** + * + * + * @author Julien Fastré + */ +class GenderAggregator implements AggregatorInterface +{ + + /** + * + * @var TranslatorInterface + */ + protected $translator; + + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + public function applyOn() + { + return Declarations::PERSON_TYPE; + } + + + public function buildForm(FormBuilderInterface $builder) + { + + } + + public function alterQuery(QueryBuilder $qb, $data) + { + + $qb->addSelect('person.gender as gender'); + + $qb->addGroupBy('gender'); + + } + + public function getTitle() + { + return "Group people by gender"; + } + + public function getQueryKeys($data) + { + return array('gender'); + } + + public function getLabels($key, array $values, $data) + { + return array( + Person::FEMALE_GENDER => $this->translator->trans('woman'), + Person::MALE_GENDER => $this->translator->trans('man'), + '_header' => $this->translator->trans('Gender') + ); + } + + public function addRole() + { + return NULL; + } + +} diff --git a/Export/Aggregator/NationalityAggregator.php b/Export/Aggregator/NationalityAggregator.php new file mode 100644 index 000000000..834921f68 --- /dev/null +++ b/Export/Aggregator/NationalityAggregator.php @@ -0,0 +1,443 @@ + + * + * 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 . + */ + +namespace Chill\PersonBundle\Export\Aggregator; + +use Chill\MainBundle\Export\AggregatorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\EntityRepository; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Security\Core\Role\Role; +use Chill\PersonBundle\Security\Authorization\PersonVoter; + +/** + * + * + * @author Julien Fastré + */ +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) + { + $this->countriesRepository = $countriesRepository; + $this->translatableStringHelper = $translatableStringHelper; + $this->translator = $translator; + } + + const EUROPE_COUNTRY_CODE = array('BE', 'FR'); + + public function applyOn() + { + return 'person'; + } + + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('group_by_level', 'choice', array( + 'choices' => array( + 'Group by continents' => 'continent', + 'Group by country' => 'country' + ), + 'choices_as_values' => true, + 'expanded' => true, + 'multiple' => false + )); + + } + + public function alterQuery(QueryBuilder $qb, $data) + { + // add a clause in select part + if ($data['group_by_level'] === 'country') { + $qb->addSelect('nationality.countryCode as nationality_aggregator'); + } elseif ($data['group_by_level'] === 'continent') { + $clause = 'CASE ' + . 'WHEN nationality.countryCode IN(:europe_country_codes) THEN \'EU\' ' + . 'ELSE \'OC\' ' //this is dummy code ! + . 'END as nationality_aggregator '; + $qb->addSelect($clause); + $qb->setParameter('europe_country_codes', self::EUROPE_COUNTRY_CODE); + } else { + 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'); + foreach($countries as $row) { + $labels[$row['c_countryCode']] = $this->translatableStringHelper->localize($row['c_name']); + } + + return $labels; + + } elseif ($data['group_by_level'] === 'continent') { + + return array( + 'EU' => $this->translator->trans('Europe'), + 'AS' => $this->translator->trans('Asia'), + 'AN' => $this->translator->trans('Antartica'), + 'AF' => $this->translator->trans('Africa'), + 'SA' => $this->translator->trans('South America'), + 'NA' => $this->translator->trans('North America'), + 'OC' => $this->translator->trans('Oceania'), + '' => $this->translator->trans('without data'), + '_header' => $this->translator->trans('Continent') + ); + } + + } + + public static function getCountryData() + { + // this list is extracted by https://en.wikipedia.org/wiki/List_of_sovereign_states_and_dependent_territories_by_continent_%28data_file%29 + // source : + // Wikipedia contributors, "List of sovereign states and dependent territories by continent (data file)," + // Wikipedia, The Free Encyclopedia, https://en.wikipedia.org/w/index.php?title=List_of_sovereign_states_and_dependent_territories_by_continent_(data_file)&oldid=688980440 + // (accessed January 2, 2016). + return << + * + * 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 . + */ + +namespace Chill\PersonBundle\Export; + +/** + * This class declare constants used for the export framework. + * + * + * @author Julien Fastré + */ +abstract class Declarations +{ + CONST PERSON_TYPE = 'person'; +} diff --git a/Export/Export/CountPerson.php b/Export/Export/CountPerson.php new file mode 100644 index 000000000..df7611c25 --- /dev/null +++ b/Export/Export/CountPerson.php @@ -0,0 +1,118 @@ + + * + * 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 . + */ + +namespace Chill\PersonBundle\Export\Export; + +use Chill\MainBundle\Export\ExportInterface; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Form\FormBuilderInterface; +use Doctrine\ORM\Query; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Symfony\Component\Security\Core\Role\Role; +use Chill\PersonBundle\Export\Declarations; +use Chill\MainBundle\Export\FormatterInterface; + +/** + * + * + * @author Julien Fastré + */ +class CountPerson implements ExportInterface +{ + /** + * + */ + public function getType() + { + return Declarations::PERSON_TYPE; + } + + public function getDescription() + { + return "Count persons by various parameters."; + } + + public function getTitle() + { + return "Count persons"; + } + + public function requiredRole() + { + return new Role(PersonVoter::STATS); + } + + /** + * Initiate the query + * + * @param QueryBuilder $qb + * @return QueryBuilder + */ + public function initiateQuery(QueryBuilder $qb, array $requiredModifiers, array $acl, array $data = array()) + { + $centers = array_map(function($el) { return $el['center']; }, $acl); + + $qb->select('COUNT(person.id) AS export_result') + ->from('ChillPersonBundle:Person', 'person') + ->join('person.center', 'center') + ->andWhere('center IN (:authorized_centers)') + ->setParameter('authorized_centers', $centers); + ; + + + return $qb; + } + + public function getResult(QueryBuilder $qb, $data) + { + return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getQueryKeys($data) + { + return array('export_result'); + } + + public function getLabels($key, array $values, $data) + { + if ($key !== 'export_result') { + throw new \LogicException("the key $key is not used by this export"); + } + + $labels = array_combine($values, $values); + $labels['_header'] = 'Number of people'; + + return $labels; + } + + public function getAllowedFormattersTypes() + { + return array(FormatterInterface::TYPE_TABULAR); + } + + public function buildForm(FormBuilderInterface $builder) { + + } + + public function supportsModifiers() + { + return array(Declarations::PERSON_TYPE); + } + +} diff --git a/Export/Filter/GenderFilter.php b/Export/Filter/GenderFilter.php new file mode 100644 index 000000000..14aba4376 --- /dev/null +++ b/Export/Filter/GenderFilter.php @@ -0,0 +1,87 @@ + + * + * 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 . + */ + +namespace Chill\PersonBundle\Export\Filter; + +use Chill\MainBundle\Export\FilterInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\PersonBundle\Entity\Person; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Query\Expr; +use Symfony\Component\Security\Core\Role\Role; + +/** + * + * + * @author Julien Fastré + */ +class GenderFilter implements FilterInterface +{ + + public function applyOn() + { + return 'person'; + } + + /** + * + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_genders', 'choice', array( + 'choices' => array( + Person::FEMALE_GENDER => 'Woman', + Person::MALE_GENDER => 'Man' + ), + 'multiple' => true, + 'expanded' => false + )); + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + $clause = $qb->expr()->in('person.gender', ':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']); + } + + /** + * A title which will be used in the label for the form + * + * @return string + */ + public function getTitle() + { + return 'Filter by person gender'; + } + + public function addRole() + { + return NULL; + } +} diff --git a/Export/Filter/NationalityFilter.php b/Export/Filter/NationalityFilter.php new file mode 100644 index 000000000..136002853 --- /dev/null +++ b/Export/Filter/NationalityFilter.php @@ -0,0 +1,72 @@ + + * + * 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 . + */ + +namespace Chill\PersonBundle\Export\Filter; + +use Symfony\Component\Form\FormBuilderInterface; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Export\FilterInterface; +use Doctrine\ORM\Query\Expr; +use Symfony\Component\Security\Core\Role\Role; + +/** + * + * + * @author Julien Fastré + */ +class NationalityFilter implements FilterInterface +{ + public function applyOn() + { + return 'person'; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('nationalities', 'select2_chill_country', array( + 'placeholder' => 'Choose countries' + )); + + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + $clause = $qb->expr()->in('person.nationality', ':person_nationality'); + + if ($where instanceof Expr\Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('person_nationality', array($data['nationalities'])); + } + + public function getTitle() + { + return "Filter by person's nationality"; + } + + public function addRole() + { + return NULL; + } +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 3bb58b536..819f3e46f 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -70,6 +70,38 @@ services: tags: - { name: form.type, alias: chill_personbundle_person_creation } + chill.person.export.export_count_person: + class: Chill\PersonBundle\Export\Export\CountPerson + tags: + - { name: chill.export, alias: count_person } + + chill.person.export.filter_gender: + class: Chill\PersonBundle\Export\Filter\GenderFilter + tags: + - { name: chill.export_filter, alias: person_gender_filter } + + + chill.person.export.filter_nationality: + class: Chill\PersonBundle\Export\Filter\NationalityFilter + tags: + - { name: chill.export_filter, alias: person_nationality_filter } + + chill.person.export.aggregator_nationality: + class: Chill\PersonBundle\Export\Aggregator\NationalityAggregator + arguments: + - "@chill.main.countries_repository" + - "@chill.main.helper.translatable_string" + - "@translator" + tags: + - { name: chill.export_aggregator, alias: person_nationality_aggregator } + + chill.person.export.aggregator_gender: + class: Chill\PersonBundle\Export\Aggregator\GenderAggregator + arguments: + - "@translator" + tags: + - { name: chill.export_aggregator, alias: person_gender_aggregator } + chill.person.form.type.pick_person: class: Chill\PersonBundle\Form\Type\PickPersonType arguments: @@ -83,4 +115,4 @@ services: class: Chill\PersonBundle\Entity\PersonRepository factory: ['@doctrine.orm.entity_manager', getRepository] arguments: - - 'Chill\PersonBundle\Entity\Person' \ No newline at end of file + - 'Chill\PersonBundle\Entity\Person' diff --git a/Security/Authorization/PersonVoter.php b/Security/Authorization/PersonVoter.php index d795c39cf..916966a8a 100644 --- a/Security/Authorization/PersonVoter.php +++ b/Security/Authorization/PersonVoter.php @@ -34,6 +34,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleInterface const CREATE = 'CHILL_PERSON_CREATE'; const UPDATE = 'CHILL_PERSON_UPDATE'; const SEE = 'CHILL_PERSON_SEE'; + const STATS = 'CHILL_PERSON_STATS'; /** * @@ -48,21 +49,31 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleInterface protected function getSupportedAttributes() { - return array(self::CREATE, self::UPDATE, self::SEE); + return array(self::CREATE, self::UPDATE, self::SEE, self::STATS); } protected function getSupportedClasses() { - return array('Chill\PersonBundle\Entity\Person'); + return array('Chill\PersonBundle\Entity\Person', 'Chill\MainBundle\Entity\Center'); } - protected function isGranted($attribute, $person, $user = null) + protected function isGranted($attribute, $object, $user = null) { if (!$user instanceof User) { return false; } - return $this->helper->userHasAccess($user, $person, $attribute); + if ($attribute === self::STATS and !$object instanceof \Chill\MainBundle\Entity\Center) { + throw new \LogicException("the expected type is \Chill\MainBundle\Entity\Center for " + . "role ".self::STATS." ".get_class($object)." given."); + } + + if ($attribute !== self::STATS and !$object instanceof \Chill\PersonBundle\Entity\Person) { + throw new \LogicException("the expected type is \Chill\PersonBundle\Entity\Person for " + . "role ".$attribute." ".get_class($object)." given."); + } + + return $this->helper->userHasAccess($user, $object, $attribute); }