diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php index 31d68701c..29fe2d828 100644 --- a/DependencyInjection/ChillPersonExtension.php +++ b/DependencyInjection/ChillPersonExtension.php @@ -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; /** @@ -60,6 +61,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/fixtures.yml'); $loader->load('services/controller.yml'); $loader->load('services/search.yml'); + $loader->load('services/menu.yml'); } private function handlePersonFieldsParameters(ContainerBuilder $container, $config) @@ -163,7 +165,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 ] ) )); } diff --git a/Entity/Person.php b/Entity/Person.php index 0c8fb9151..f6a404504 100644 --- a/Entity/Person.php +++ b/Entity/Person.php @@ -60,6 +60,7 @@ class Person implements HasCenterInterface { const MALE_GENDER = 'man'; const FEMALE_GENDER = 'woman'; + const BOTH_GENDER = 'both'; /** @var \Chill\PersonBundle\Entity\MaritalStatus The marital status of the person */ private $maritalStatus; @@ -778,11 +779,11 @@ class Person implements HasCenterInterface { public function isAddressesValid(ExecutionContextInterface $context) { if ($this->hasTwoAdressWithSameValidFromDate()) { - $context->addViolationAt( - 'addresses', - 'Two addresses has the same validFrom date', - array() - ); + $context + ->buildViolation('Two addresses has the same validFrom date') + ->atPath('addresses') + ->addViolation() + ; } } diff --git a/Export/Export/ListPerson.php b/Export/Export/ListPerson.php index 558cd4e6d..58359d939 100644 --- a/Export/Export/ListPerson.php +++ b/Export/Export/ListPerson.php @@ -21,6 +21,7 @@ use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\CustomFieldsBundle\Service\CustomFieldProvider; use Symfony\Component\Form\Extension\Core\Type\DateType; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice; /** * Render a list of peoples @@ -61,7 +62,9 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface 'address_valid_from', 'address_postcode_label', 'address_postcode_code', 'address_country_name', 'address_country_code' ); - + + private $slugs = []; + public function __construct( EntityManagerInterface $em, TranslatorInterface $translator, @@ -264,25 +267,54 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface }; } else { - // for fields which are custom fields - /* @var $cf CustomField */ - $cf = $this->entityManager - ->getRepository(CustomField::class) - ->findOneBy(array('slug' => $this->DQLToSlug($key))); - - return function($value) use ($cf) { - if ($value === '_header') { - return $this->translatableStringHelper->localize($cf->getName()); - } - - return $this->customFieldProvider - ->getCustomFieldByType($cf->getType()) - ->render(json_decode($value, true), $cf, 'csv'); - }; + return $this->getLabelForCustomField($key, $values, $data); } } } + + private function getLabelForCustomField($key, array $values, $data) + { + // for fields which are custom fields + /* @var $cf CustomField */ + $cf = $this->entityManager + ->getRepository(CustomField::class) + ->findOneBy(array('slug' => $this->DQLToSlug($key))); + $cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType()); + $defaultFunction = function($value) use ($cf) { + if ($value === '_header') { + return $this->translatableStringHelper->localize($cf->getName()); + } + + return $this->customFieldProvider + ->getCustomFieldByType($cf->getType()) + ->render(json_decode($value, true), $cf, 'csv'); + }; + + if ($cfType instanceof CustomFieldChoice and $cfType->isMultiple($cf)) { + return function($value) use ($cf, $cfType, $key) { + $slugChoice = $this->extractInfosFromSlug($key)['additionnalInfos']['choiceSlug']; + $decoded = \json_decode($value, true); + + if ($value === '_header') { + + $label = $cfType->getChoices($cf)[$slugChoice]; + + return $this->translatableStringHelper->localize($cf->getName()) + .' | '.$label; + } + + if ($slugChoice === '_other' and $cfType->isChecked($cf, $choiceSlug, $decoded)) { + return $cfType->extractOtherValue($cf, $decoded); + } else { + return $cfType->isChecked($cf, $slugChoice, $decoded); + } + }; + + } else { + return $defaultFunction; + } + } /** * {@inheritDoc} @@ -297,29 +329,46 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface foreach ($data['fields'] as $key) { if (in_array($key, $this->fields)) { $fields[] = $key; - } else { - // this should be a slug from custom field, we have to clean it - $fields[] = $this->slugToDQL($key); } } - - return $fields; + + // add the key from slugs and return + return \array_merge($fields, \array_keys($this->slugs)); } /** * clean a slug to be usable by DQL - * - * @param string $slugsanitize + * + * @param string $slugsanitize + * @param string $type the type of the customfield, if required (currently only for choices) * @return string */ - private function slugToDQL($slug) + private function slugToDQL($slug, $type = "default", array $additionalInfos = []) { - return "cf____".\str_replace("-", "____", $slug); + $uid = 'slug_'.\uniqid(); + + $this->slugs[$uid] = [ + 'slug' => $slug, + 'type' => $type, + 'additionnalInfos' => $additionalInfos + ]; + + return $uid; } private function DQLToSlug($cleanedSlug) + { + return $this->slugs[$cleanedSlug]['slug']; + } + + /** + * + * @param type $cleanedSlug + * @return an array with keys = 'slug', 'type', 'additionnalInfo' + */ + private function extractInfosFromSlug($slug) { - return \str_replace("____", "-", \substr($cleanedSlug, 6)); + return $this->slugs[$slug]; } /** @@ -395,13 +444,21 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface } foreach ($this->getCustomFields() as $cf) { - if (in_array($cf->getSlug(), $data['fields'])) { + $cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType()); + if ($cfType instanceof CustomFieldChoice and $cfType->isMultiple($cf)) { + foreach($cfType->getChoices($cf) as $choiceSlug => $label) { + $slug = $this->slugToDQL($cf->getSlug(), 'choice', [ 'choiceSlug' => $choiceSlug ]); + $qb->addSelect( + sprintf('GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s', + $slug, $slug)); + $qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug()); + } + } else { $slug = $this->slugToDQL($cf->getSlug()); $qb->addSelect( sprintf('GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s', $slug, $slug)); $qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug()); - //$qb->setParameter(sprintf('name%s', $slug), $cf->getSlug()); } } diff --git a/Export/Filter/GenderFilter.php b/Export/Filter/GenderFilter.php index ab551b1f3..dc8697de0 100644 --- a/Export/Filter/GenderFilter.php +++ b/Export/Filter/GenderFilter.php @@ -61,7 +61,7 @@ class GenderFilter implements FilterInterface, public function validateForm($data, ExecutionContextInterface $context) { - if (count($data['accepted_genders']) === 0) { + if ($data['accepted_genders'] === null) { $context->buildViolation("You should select an option") ->addViolation(); } diff --git a/Form/DataTransformer/PersonToIdTransformer.php b/Form/DataTransformer/PersonToIdTransformer.php index fa1b9916e..bfcac0d7c 100644 --- a/Form/DataTransformer/PersonToIdTransformer.php +++ b/Form/DataTransformer/PersonToIdTransformer.php @@ -25,7 +25,7 @@ class PersonToIdTransformer implements DataTransformerInterface /** * Transforms an object (issue) to a string (id). * - * @param Issue|null $issue + * @param Person|null $issue * @return string */ public function transform($issue) @@ -42,7 +42,7 @@ class PersonToIdTransformer implements DataTransformerInterface * * @param string $id * - * @return Issue|null + * @return Person|null * * @throws TransformationFailedException if object (issue) is not found. */ diff --git a/Form/Type/GenderType.php b/Form/Type/GenderType.php index 10aac7c08..ad778d24a 100644 --- a/Form/Type/GenderType.php +++ b/Form/Type/GenderType.php @@ -23,7 +23,8 @@ class GenderType extends AbstractType { $a = array( Person::MALE_GENDER => Person::MALE_GENDER, - Person::FEMALE_GENDER => Person::FEMALE_GENDER + Person::FEMALE_GENDER => Person::FEMALE_GENDER, + Person::BOTH_GENDER => Person::BOTH_GENDER ); $resolver->setDefaults(array( diff --git a/Menu/SectionMenuBuilder.php b/Menu/SectionMenuBuilder.php new file mode 100644 index 000000000..9a4380144 --- /dev/null +++ b/Menu/SectionMenuBuilder.php @@ -0,0 +1,61 @@ + + * + * 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\Menu; + +use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Knp\Menu\MenuItem; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Chill\PersonBundle\Security\Authorization\PersonVoter; + +/** + * + * + * @author Julien Fastré + */ +class SectionMenuBuilder implements LocalMenuBuilderInterface +{ + /** + * + * @var AuthorizationCheckerInterface + */ + protected $authorizationChecker; + + public function __construct(AuthorizationCheckerInterface $authorizationChecker) + { + $this->authorizationChecker = $authorizationChecker; + } + + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + if ($this->authorizationChecker->isGranted(PersonVoter::CREATE)) { + $menu->addChild('Add a person', [ + 'route' => 'chill_person_new' + ]) + ->setExtras([ + 'order' => 10, + 'icons' => [ 'plus' ] + ]); + } + } + + public static function getMenuIds(): array + { + return [ 'section' ]; + } +} diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index 6792ca184..56244cae3 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -18,12 +18,6 @@ chill_person_general_update: chill_person_new: path: /{_locale}/person/new defaults: {_controller: ChillPersonBundle:Person:new } - options: - menus: - section: - order: 10 - label: Add a person - icons: [plus] chill_person_review: path: /{_locale}/person/review diff --git a/Resources/config/services/menu.yml b/Resources/config/services/menu.yml new file mode 100644 index 000000000..09b24d86b --- /dev/null +++ b/Resources/config/services/menu.yml @@ -0,0 +1,6 @@ +services: + Chill\PersonBundle\Menu\SectionMenuBuilder: + arguments: + $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' + tags: + - { name: 'chill.menu_builder' } diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 59ab7e5bf..65b7cd632 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -51,6 +51,8 @@ man: Homme woman: Femme Man: Homme Woman: Femme +both: Indéterminé +Both: Indéterminé Divorced: Divorcé(e) Separated: Séparé(e) Widow: Veuf(ve) @@ -141,6 +143,7 @@ Add an address: Ajouter une adresse Back to the person details: Retour aux détails de la personne #timeline +Timeline: Historique Closing the accompanying period: Fermeture de la période d'accompagnement Opening the accompanying period: Ouverture d'une période d'accompagnement diff --git a/Resources/views/Person/create_review.html.twig b/Resources/views/Person/create_review.html.twig index eed4d57b2..22e51ae27 100644 --- a/Resources/views/Person/create_review.html.twig +++ b/Resources/views/Person/create_review.html.twig @@ -51,7 +51,7 @@ {% endspaceless %} - {{ person.birthdate|localizeddate('long', 'none', app.request.locale) }} + {% if person.birthdate is not null %}{{ person.birthdate|localizeddate('long', 'none', app.request.locale) }}{% else %} {% endif %} {% if person.nationality is not null %}{{ person.nationality.name|localize_translatable_string }}{% else %}{{ 'Without nationality'|trans }}{% endif %} diff --git a/Resources/views/Person/list.html.twig b/Resources/views/Person/list.html.twig index 4d7288d9a..31b80f477 100644 --- a/Resources/views/Person/list.html.twig +++ b/Resources/views/Person/list.html.twig @@ -64,11 +64,13 @@