From 90f579d88a4488817d04815edc1738d218afe980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 18 Jan 2016 21:32:22 +0100 Subject: [PATCH 01/13] show memo in person details + minor fixes The memo is shown only if not empty. minor fixes : - re-add label 'country of birth' on view page - set title to pages --- Resources/translations/messages.fr.yml | 1 + Resources/views/Person/edit.html.twig | 10 ++++++++-- Resources/views/Person/view.html.twig | 17 ++++++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index f5022bd77..0aaccf0d0 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -68,6 +68,7 @@ Reset: 'Remise à zéro' 'Create accompanying period': 'Nouvelle ouverture-fermeture à une autre date' 'Closing motive': 'Motif de clôture' 'Person details': 'Détails de la personne' +'Update details for %name%': 'Modifier détails de %name%' Accompanying period list: Périodes d'accompagnement #timeline diff --git a/Resources/views/Person/edit.html.twig b/Resources/views/Person/edit.html.twig index 5b768f37c..10472a12f 100644 --- a/Resources/views/Person/edit.html.twig +++ b/Resources/views/Person/edit.html.twig @@ -18,14 +18,21 @@ {% set activeRouteKey = '' %} -{% block title %}ChillPersonBundle:Person:see{% endblock %} +{% block title %}{{ 'Update details for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %} {% block personcontent %} + +

{{ 'Update details for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName|capitalize } ) }}

{% form_theme form 'ChillMainBundle:Form:fields.html.twig' %} {{ form_start(form) }} +
+

{{ 'Memo'|trans }}

+{{ form_row(form.memo, {'label' : 'Memo'} ) }} +
+

{{ 'General information'|trans }}

{{ form_row(form.firstName, {'label' : 'First name'}) }} @@ -54,7 +61,6 @@
-{{ form_row(form.memo, {'label' : 'Memo'} ) }} {{ form_rest(form) }} diff --git a/Resources/views/Person/view.html.twig b/Resources/views/Person/view.html.twig index 7f93df1ff..5ae10a9a5 100644 --- a/Resources/views/Person/view.html.twig +++ b/Resources/views/Person/view.html.twig @@ -23,7 +23,8 @@ This view should receive those arguments: - person #} -{% block title %}ChillPersonBundle:Person:see{% endblock %} +{% block title %}{{ 'Person details'|trans|capitalize ~ ' ' ~ person.firstName|capitalize ~ + ' ' ~ person.lastName }}{% endblock %} {# we define variables to include an edit form repeated multiple time across the page @@ -34,8 +35,21 @@ This view should receive those arguments: {% block personcontent %} + +
+ {% if person.memo is not empty %} +
+
+

{{ 'Memo'|trans|upper }}

+ +

+

{{ person.memo|nl2br }}
+

+
+
+ {% endif %}

{{ 'General information'|trans|upper }}

@@ -74,6 +88,7 @@ This view should receive those arguments:
{{ 'Place of birth'|trans }} :
{{ person.placeOfBirth }}
+
{{ 'Country of birth'|trans }} :
{% spaceless %} {% if person.countryOfBirth is not null %} {{ person.countryOfBirth.name|localize_translatable_string }} From d70be5ea85501e1ff313a760687c07c914ea8b49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 18 Jan 2016 22:30:57 +0100 Subject: [PATCH 02/13] add person detection improvement: inversion firstName<->lastName --- Controller/PersonController.php | 4 ++- .../Controller/PersonControllerCreateTest.php | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Controller/PersonController.php b/Controller/PersonController.php index 3c461d1ba..60e9f7eb7 100644 --- a/Controller/PersonController.php +++ b/Controller/PersonController.php @@ -302,7 +302,9 @@ class PersonController extends Controller $dql = 'SELECT p from ChillPersonBundle:Person p WHERE ' . 'LOWER(p.firstName) LIKE LOWER(:firstName)' - . ' OR LOWER(p.lastName) LIKE LOWER(:lastName)'; + . ' OR LOWER(p.lastName) LIKE LOWER(:lastName)' + . ' OR LOWER(p.firstName) LIKE LOWER(:lastName)' + . ' OR LOWER(p.lastName) LIKE LOWER(:firstName)'; $query->setParameter('firstName', $form['firstName']->getData()) ->setParameter('lastName', $form['lastName']->getData()); diff --git a/Tests/Controller/PersonControllerCreateTest.php b/Tests/Controller/PersonControllerCreateTest.php index 83acbbf7b..5f0a33fab 100644 --- a/Tests/Controller/PersonControllerCreateTest.php +++ b/Tests/Controller/PersonControllerCreateTest.php @@ -240,6 +240,31 @@ class PersonControllerCreateTest extends WebTestCase } + public function testReviewExistingDetectionInversedLastNameWithFirstName() + { + $client = $this->getAuthenticatedClient(); + + $crawler = $client->request('GET', '/fr/person/new'); + + //test the page is loaded before continuing + $this->assertTrue($client->getResponse()->isSuccessful()); + + $form = $crawler->selectButton("Ajouter la personne")->form(); + $form = $this->fillAValidCreationForm($form, 'Charline', 'dd'); + $client->submit($form); + + $this->assertContains('Depardieu', $client->getCrawler()->text(), + "check that the page has detected the lastname of a person existing in database"); + + //inversion + $form = $crawler->selectButton("Ajouter la personne")->form(); + $form = $this->fillAValidCreationForm($form, 'dd', 'Charline'); + $client->submit($form); + + $this->assertContains('Depardieu', $client->getCrawler()->text(), + "check that the page has detected the lastname of a person existing in database"); + } + public static function tearDownAfterClass() { static::bootKernel(); From d419be2af180cce70b7529021e069c4bd3a1ea16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 5 Feb 2016 23:05:53 +0100 Subject: [PATCH 03/13] set some fields visibility as configurable Some fields can now be hidden through configuration --- Controller/PersonController.php | 4 +- DependencyInjection/ChillPersonExtension.php | 27 +++ DependencyInjection/Configuration.php | 92 +++++--- Form/PersonType.php | 64 +++++- Resources/config/services.yml | 9 +- Resources/views/Person/edit.html.twig | 19 +- Resources/views/Person/view.html.twig | 18 ++ Resources/views/layout.html.twig | 6 +- .../Controller/PersonControllerUpdateTest.php | 18 ++ ...onControllerUpdateWithHiddenFieldsTest.php | 211 ++++++++++++++++++ Tests/Controller/PersonControllerViewTest.php | 6 + ...rsonControllerViewWithHiddenFieldsTest.php | 107 +++++++++ .../config/config_test_with_hidden_fields.yml | 18 ++ 13 files changed, 551 insertions(+), 48 deletions(-) create mode 100644 Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php create mode 100644 Tests/Controller/PersonControllerViewWithHiddenFieldsTest.php create mode 100644 Tests/Fixtures/App/app/config/config_test_with_hidden_fields.yml diff --git a/Controller/PersonController.php b/Controller/PersonController.php index 60e9f7eb7..655bbad1b 100644 --- a/Controller/PersonController.php +++ b/Controller/PersonController.php @@ -75,7 +75,7 @@ class PersonController extends Controller $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person, 'You are not allowed to edit this person'); - $form = $this->createForm(new PersonType(), $person, + $form = $this->createForm(PersonType::class, $person, array( "action" => $this->generateUrl('chill_person_general_update', array("person_id" => $person_id)), @@ -98,7 +98,7 @@ class PersonController extends Controller $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person, 'You are not allowed to edit this person'); - $form = $this->createForm(new PersonType(), $person, + $form = $this->createForm(PersonType::class, $person, array("cFGroup" => $this->getCFGroup())); if ($request->getMethod() === 'POST') { diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php index 589e2c14e..536f414f2 100644 --- a/DependencyInjection/ChillPersonExtension.php +++ b/DependencyInjection/ChillPersonExtension.php @@ -31,10 +31,25 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac // set configuration for validation $container->setParameter('chill_person.validation.birtdate_not_before', $config['validation']['birthdate_not_after']); + + $this->handlePersonFieldsParameters($container, $config['person_fields']); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); } + + private function handlePersonFieldsParameters(ContainerBuilder $container, $config) + { + if (array_key_exists('enabled', $config)) { + unset($config['enabled']); + } + + $container->setParameter('chill_person.person_fields', $config); + + foreach ($config as $key => $value) { + $container->setParameter('chill_person.person_fields.'.$key, $value); + } + } private function declarePersonAsCustomizable (ContainerBuilder $container) { @@ -66,6 +81,18 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $asseticConfig['bundles'][] = 'ChillPersonBundle'; $container->prependExtensionConfig('assetic', array('bundles' => array('ChillPersonBundle'))); + + //add person_fields parameter as global + $chillPersonConfig = $container->getExtensionConfig($this->getAlias()); + $config = $this->processConfiguration(new Configuration(), $chillPersonConfig); + $twigConfig = array( + 'globals' => array( + 'chill_person' => array( + 'fields' => $config['person_fields'] + ) + ) + ); + $container->prependExtensionConfig('twig', $twigConfig); $this-> declarePersonAsCustomizable($container); diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 833b5498d..5299a22fd 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -27,40 +27,70 @@ class Configuration implements ConfigurationInterface $rootNode ->canBeDisabled() ->children() - ->arrayNode('search') - ->canBeDisabled() + ->arrayNode('search') + ->canBeDisabled() ->children() - ->booleanNode('use_double_metaphone') - ->defaultFalse() - ->end() - ->booleanNode('use_trigrams') - ->defaultFalse() - ->end() - ->end() - ->end() - ->arrayNode('validation') - ->canBeDisabled() - ->children() - ->scalarNode('birthdate_not_after') - ->info($this->validationBirthdateNotAfterInfos) - ->defaultValue('P1D') - ->validate() - ->ifTrue(function($period) { - try { - $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() - ->end() - ->end(); + ->booleanNode('use_double_metaphone') + ->defaultFalse() + ->end() // use_double_metaphone, parent = children for 'search' + ->booleanNode('use_trigrams') + ->defaultFalse() + ->end() // use_trigrams, parent = children of 'search' + ->end() //children for 'search', parent = array node 'search' + ->end() // array 'search', parent = children of root + ->arrayNode('validation') + ->canBeDisabled() + ->children() + ->scalarNode('birthdate_not_after') + ->info($this->validationBirthdateNotAfterInfos) + ->defaultValue('P1D') + ->validate() + ->ifTrue(function($period) { + try { + $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 + ->arrayNode('person_fields') + ->canBeDisabled() + ->children() + ->append($this->addFieldNode('place_of_birth')) + ->append($this->addFieldNode('email')) + ->append($this->addFieldNode('phonenumber')) + ->append($this->addFieldNode('nationality')) + ->append($this->addFieldNode('country_of_birth')) + ->append($this->addFieldNode('marital_status')) + ->append($this->addFieldNode('spoken_languages')) + ->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'); + + $node + ->values(array('hidden', 'visible')) + ->defaultValue('visible') + ->info("If the field $key must be shown") + ->end(); + //var_dump($node); + return $node; + } } diff --git a/Form/PersonType.php b/Form/PersonType.php index 16f50cf6f..aa76a3ba7 100644 --- a/Form/PersonType.php +++ b/Form/PersonType.php @@ -28,6 +28,25 @@ use Chill\PersonBundle\Form\Type\GenderType; class PersonType extends AbstractType { + /** + * array of configuration for person_fields. + * + * Contains whether we should add fields some optional fields (optional per + * instance) + * + * @var string[] + */ + protected $config = array(); + + /** + * + * @param string[] $personFieldsConfiguration configuration of visibility of some fields + */ + public function __construct(array $personFieldsConfiguration) + { + $this->config = $personFieldsConfiguration; + } + /** * @param FormBuilderInterface $builder * @param array $options @@ -38,27 +57,48 @@ class PersonType extends AbstractType ->add('firstName') ->add('lastName') ->add('birthdate', 'date', array('required' => false, 'widget' => 'single_text', 'format' => 'dd-MM-yyyy')) - ->add('placeOfBirth', 'text', array('required' => false)) ->add('gender', new GenderType(), array( 'required' => true )) ->add('memo', 'textarea', array('required' => false)) - ->add('phonenumber', 'textarea', array('required' => false)) - ->add('email', 'textarea', array('required' => false)) - ->add('countryOfBirth', 'select2_chill_country', array( + ; + + if ($this->config['place_of_birth'] === 'visible') { + $builder->add('placeOfBirth', 'text', array('required' => false)); + } + + if ($this->config['phonenumber'] === 'visible') { + $builder->add('phonenumber', 'textarea', array('required' => false)); + } + + if ($this->config['email'] === 'visible') { + $builder->add('email', 'textarea', array('required' => false)); + } + + if ($this->config['country_of_birth'] === 'visible') { + $builder->add('countryOfBirth', 'select2_chill_country', array( 'required' => false - )) - ->add('nationality', 'select2_chill_country', array( + )); + } + + if ($this->config['nationality'] === 'visible') { + $builder->add('nationality', 'select2_chill_country', array( 'required' => false - )) - ->add('spokenLanguages', 'select2_chill_language', array( + )); + } + + if ($this->config['spoken_languages'] === 'visible') { + $builder->add('spokenLanguages', 'select2_chill_language', array( 'required' => false, 'multiple' => true - )) - ->add('maritalStatus', 'select2_chill_marital_status', array( + )); + } + + if ($this->config['marital_status'] === 'visible'){ + $builder->add('maritalStatus', 'select2_chill_marital_status', array( 'required' => false - )) - ; + )); + } if($options['cFGroup']) { $builder diff --git a/Resources/config/services.yml b/Resources/config/services.yml index e934a7c71..b0cf94e1d 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -1,7 +1,14 @@ parameters: # cl_chill_person.example.class: Chill\PersonBundle\Example -services: +services: + chill.person.form.person_creation: + class: Chill\PersonBundle\Form\PersonType + arguments: + - %chill_person.person_fields% + tags: + - { name: form.type } + chill.person.accompanying_period_closing_motive: class: Chill\PersonBundle\Form\Type\ClosingMotiveType scope: request diff --git a/Resources/views/Person/edit.html.twig b/Resources/views/Person/edit.html.twig index 10472a12f..fd3a59854 100644 --- a/Resources/views/Person/edit.html.twig +++ b/Resources/views/Person/edit.html.twig @@ -43,23 +43,40 @@

{{ 'Birth information'|trans }}

{{ form_row(form.birthdate, {'label': 'Date of birth'} ) }} + {%- if form.placeOfBirth is defined -%} {{ form_row(form.placeOfBirth, { 'label' : 'Place of birth'} ) }} + {%- endif -%} + {%- if form.countryOfBirht is defined -%} {{ form_row(form.countryOfBirth, { 'label' : 'Country of birth' } ) }} + {%- endif -%}
+{%- if form.nationality is defined or form.spokenLanguages is defined or form.maritalStatus is defined -%}

{{ 'Administrative information'|trans }}

+ {%- if form.nationality is defined -%} {{ form_row(form.nationality, { 'label' : 'Nationality'|trans} ) }} + {%- endif -%} + {%- if form.spokenLanguages is defined -%} {{ form_row(form.spokenLanguages, {'label' : 'Spoken languages'}) }} + {%- endif -%} + {%- if form.maritalStatus is defined -%} {{ form_row(form.maritalStatus, { 'label' : 'Marital status'} ) }} + {%- endif -%}
+{%- endif -%} +{%- if form.email is defined or form.phonenumber is defined -%}

{{ 'Contact information'|trans }}

+ {%- if form.email is defined -%} {{ form_row(form.email, {'label': 'Email'}) }} + {%- endif -%} + {%- if form.phonenumber is defined -%} {{ form_row(form.phonenumber, {'label': 'Phonenumber'}) }} - + {%- endif -%}
+{%- endif -%} {{ form_rest(form) }} diff --git a/Resources/views/Person/view.html.twig b/Resources/views/Person/view.html.twig index 5ae10a9a5..e86a55857 100644 --- a/Resources/views/Person/view.html.twig +++ b/Resources/views/Person/view.html.twig @@ -86,8 +86,11 @@ This view should receive those arguments: {%- endif -%}
+ {%- if chill_person.fields.place_of_birth == 'visible' -%}
{{ 'Place of birth'|trans }} :
{{ person.placeOfBirth }}
+ {%- endif -%} + {%- if chill_person.fields.country_of_birth == 'visible' -%}
{{ 'Country of birth'|trans }} :
{% spaceless %} {% if person.countryOfBirth is not null %} @@ -96,6 +99,7 @@ This view should receive those arguments: {{ 'Unknown country of birth'|trans }} {% endif %} {% endspaceless %}
+ {%- endif -%} {% if is_granted('CHILL_PERSON_UPDATE', person) %} @@ -106,10 +110,12 @@ This view should receive those arguments:
+ {%- if chill_person.fields.nationality == 'visible' or chill_person.fields.spoken_languages == 'visible'-%}

{{ 'Administrative information'|trans|upper }}

+ {%- if chill_person.fields.nationality == 'visible' -%}
{{ 'Nationality'|trans }} :
@@ -120,6 +126,8 @@ This view should receive those arguments: {% endif %}
+ {%- endif -%} + {%- if chill_person.fields.spoken_languages == 'visible' -%}
{{'Spoken languages'|trans}} :
@@ -132,6 +140,8 @@ This view should receive those arguments: {% endif %}
+ {%- endif -%} + {%- if chill_person.fields.marital_status == 'visible' -%}
{{'Marital status'|trans}} :
@@ -142,24 +152,31 @@ This view should receive those arguments: {% endif %}
+ {%- endif -%} {% if is_granted('CHILL_PERSON_UPDATE', person) %} {{ include(edit_tmp_name, edit_tmp_args) }} {% endif %}
+ {%- endif -%} + {%- if chill_person.fields.email == 'visible' or chill_person.fields.phonenumber == 'visible' -%}

 {{ 'Contact information'|trans|upper }}

+ {%- if chill_person.fields.email == 'visible' -%}
{{ 'Email'|trans }} :
{{ person.email}} 
+ {%- endif -%} + {%- if chill_person.fields.phonenumber == 'visible' -%}
{{ 'Phonenumber'|trans }} :
{{ person.phonenumber}} 
+ {% endif %} {% if is_granted('CHILL_PERSON_UPDATE', person) %} @@ -167,6 +184,7 @@ This view should receive those arguments: {% endif %}
+ {%- endif -%}
{% if cFGroup and (cFGroup.getActiveCustomFields|length > 0) %} diff --git a/Resources/views/layout.html.twig b/Resources/views/layout.html.twig index 78a674a12..9baf42dc0 100644 --- a/Resources/views/layout.html.twig +++ b/Resources/views/layout.html.twig @@ -70,6 +70,7 @@ {{ person.birthdate|localizeddate('long', 'none') }} {% endif %}
+ {%- if chill_person.fields.nationality == 'visible' -%}
{{ 'Nationality'|trans|upper}} : {% if person.nationality is not null %} @@ -78,6 +79,8 @@ {% trans %}Without nationality{% endtrans %} {% endif %}
+ {%- endif -%} + {%- if chill_person.fields.spoken_languages == 'visible' -%}
{{ 'Spoken languages'|trans|upper}} : {% if person.spokenLanguages|length == 0 %} @@ -87,7 +90,8 @@ {{ lang.name|localize_translatable_string }}{% if not loop.last %},{% endif %} {% endfor %} {% endif %} -
+ + {%- endif -%} {% endblock %} diff --git a/Tests/Controller/PersonControllerUpdateTest.php b/Tests/Controller/PersonControllerUpdateTest.php index 9429e4e3e..b7087024d 100644 --- a/Tests/Controller/PersonControllerUpdateTest.php +++ b/Tests/Controller/PersonControllerUpdateTest.php @@ -96,6 +96,24 @@ class PersonControllerUpdateTest extends WebTestCase "The person edit form is accessible"); } + /** + * Test the configurable fields are present + * + * @group configurable_fields + */ + public function testHiddenFielsArePresent() + { + $crawler = $this->client->request('GET', $this->editUrl); + + $configurables = array('placeOfBirth', 'phonenumber', 'email', + 'countryOfBirth', 'nationality', 'spokenLanguages', 'maritalStatus'); + $form = $crawler->selectButton('Submit')->form(); //; + + foreach($configurables as $key) { + $this->assertTrue($form->has('chill_personbundle_person['.$key.']')); + } + } + /** * Test if the edit page of a given person is not accessible for a user * of another center of the person diff --git a/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php b/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php new file mode 100644 index 000000000..7d374a3a7 --- /dev/null +++ b/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php @@ -0,0 +1,211 @@ + + * + * 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\Tests\Controller; + +//ini_set('memory_limit', '-1'); + +use Chill\PersonBundle\Entity\Person; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +/** + * Test the edition of persons + * + * As I am logged in as "center a_social" + * + * @author Julien Fastré + */ +class PersonControllerUpdateWithHiddenFieldsTest extends WebTestCase +{ + /** @var \Doctrine\ORM\EntityManagerInterface The entity manager */ + private $em; + + /** @var Person The person on which the test is executed */ + private $person; + + /** @var string The url using for editing the person's information */ + private $editUrl; + + /** @var string The url using for seeing the person's information */ + private $viewUrl; + + /** + * Prepare client and create a random person + */ + public function setUp() + { + static::bootKernel(array('environment' => 'test_with_hidden_fields')); + + $this->em = static::$kernel->getContainer() + ->get('doctrine.orm.entity_manager'); + + $center = $this->em->getRepository('ChillMainBundle:Center') + ->findOneBy(array('name' => 'Center A')); + + $this->person = (new Person()) + ->setLastName("My Beloved") + ->setFirstName("Jesus") + ->setCenter($center) + ->setGender(Person::MALE_GENDER); + + $this->em->persist($this->person); + $this->em->flush(); + + $this->editUrl = '/en/person/'.$this->person->getId().'/general/edit'; + $this->viewUrl = '/en/person/'.$this->person->getId().'/general'; + + $this->client = static::createClient( + array( + 'environment' => 'test_with_hidden_fields' + ), + array( + 'PHP_AUTH_USER' => 'center a_social', + 'PHP_AUTH_PW' => 'password', + ) + ); + } + + /** + * Reload the person from the db + */ + protected function refreshPerson() + { + $this->person = $this->em->getRepository('ChillPersonBundle:Person') + ->find($this->person->getId()); + } + + /** + * Test the edit page are accessible + */ + public function testEditPageIsSuccessful() + { + $this->client->request('GET', $this->editUrl); + $this->assertTrue($this->client->getResponse()->isSuccessful(), + "The person edit form is accessible"); + } + + /** + * Test the configurable fields are absent + * + * @group configurable_fields + */ + public function testHiddenFielsAreAbsent() + { + $crawler = $this->client->request('GET', $this->editUrl); + + $configurables = array('placeOfBirth', 'phonenumber', 'email', + 'countryOfBirth', 'nationality', 'spokenLanguages', 'maritalStatus'); + $form = $crawler->selectButton('Submit')->form(); //; + + foreach($configurables as $key) { + $this->assertFalse($form->has('chill_personbundle_person['.$key.']')); + } + } + + /** + * Test the edition of a field + * + * Given I fill the field with $value + * And I submit the form + * Then I am redirected to the 'general' page + * And the person is updated in the db + * + * @dataProvider validTextFieldsProvider + * @param string $field + * @param string $value + * @param \Closure $callback + */ + public function testEditTextField($field, $value, \Closure $callback) + { + $crawler = $this->client->request('GET', $this->editUrl); + + $form = $crawler->selectButton('Submit') + ->form(); + //transform countries into value if needed + switch ($field) { + case 'nationality': + case 'countryOfBirth': + if ($value !== NULL) { + $country = $this->em->getRepository('ChillMainBundle:Country') + ->findOneByCountryCode($value); + $transformedValue = $country->getId(); + } else { + $transformedValue = NULL; + } + break; + default: + $transformedValue = $value; + } + + $form->get('chill_personbundle_person['.$field. ']') + ->setValue($transformedValue); + + $this->client->submit($form); + $this->refreshPerson(); + + $this->assertTrue($this->client->getResponse()->isRedirect($this->viewUrl), + 'the page is redirected to general view'); + $this->assertEquals($value, $callback($this->person), + 'the value '.$field.' is updated in db'); + + $crawler = $this->client->followRedirect(); + $this->assertGreaterThan(0, $crawler->filter('.success')->count(), + 'a element .success is shown'); + + if($field == 'birthdate' or $field == 'memo' or $field == 'countryOfBirth' or $field == 'nationality' + or $field == 'gender') { + // we do not perform test on the web page contents. + } else { + $this->assertGreaterThan(0, $crawler->filter('html:contains("'.$value.'")')->count()); + } + } + + /** + * provide valid values to test, with field name and + * a function to find the value back from person entity + * + * @return mixed[] + */ + public function validTextFieldsProvider() + { + return array( + ['firstName', 'random Value', function(Person $person) { return $person->getFirstName(); } ], + ['lastName' , 'random Value', function(Person $person) { return $person->getLastName(); } ], + ['birthdate', '15-12-1980', function(Person $person) { return $person->getBirthdate()->format('d-m-Y'); }], + ['memo', 'jfkdlmq jkfldmsq jkmfdsq', function(Person $person) { return $person->getMemo(); }], + ['birthdate', '', function(Person $person) { return $person->getBirthdate(); }], + ['gender', Person::FEMALE_GENDER, function(Person $person) { return $person->getGender(); }], + ); + } + + public function tearDown() + { + $this->refreshPerson(); + $this->em->remove($this->person); + $this->em->flush(); + } + + private function getVeryLongText() + { + return <<assertGreaterThan(0, $crawler->filter('html:contains("Tested Person")')->count()); $this->assertGreaterThan(0, $crawler->filter('html:contains("Réginald")')->count()); + $this->assertContains('Email addresses', $crawler->text()); + $this->assertContains('Phonenumber', $crawler->text()); + $this->assertContains('Langues parlées', $crawler->text()); + $this->assertContains(/* Etat */ 'civil', $crawler->text()); } /** diff --git a/Tests/Controller/PersonControllerViewWithHiddenFieldsTest.php b/Tests/Controller/PersonControllerViewWithHiddenFieldsTest.php new file mode 100644 index 000000000..2ace5c1da --- /dev/null +++ b/Tests/Controller/PersonControllerViewWithHiddenFieldsTest.php @@ -0,0 +1,107 @@ + + * + * 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\Tests\Controller; + +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Chill\PersonBundle\Entity\Person; + +/** + * @author Julien Fastré + * @author Marc Ducobu + */ +class PersonControllerViewTestWithHiddenFields extends WebTestCase +{ + /** @var \Doctrine\ORM\EntityManagerInterface The entity manager */ + private $em; + + /** @var Person A person used on which to run the test */ + private $person; + + /** @var String The url to view the person details */ + private $viewUrl; + + public function setUp() + { + static::bootKernel(array('environment' => 'test_with_hidden_fields')); + + $this->em = static::$kernel->getContainer() + ->get('doctrine.orm.entity_manager'); + + $center = $this->em->getRepository('ChillMainBundle:Center') + ->findOneBy(array('name' => 'Center A')); + + $this->person = (new Person()) + ->setLastName("Tested Person") + ->setFirstName("Réginald") + ->setCenter($center) + ->setGender(Person::MALE_GENDER); + + $this->em->persist($this->person); + $this->em->flush(); + + $this->viewUrl = '/en/person/'.$this->person->getId().'/general'; + } + + /** + * Test if the view page is accessible + * + * @group configurable_fields + */ + public function testViewPerson() + { + $client = static::createClient( + array('environment' => 'test_with_hidden_fields'), + array( + 'PHP_AUTH_USER' => 'center a_social', + 'PHP_AUTH_PW' => 'password', + 'HTTP_ACCEPT_LANGUAGE' => 'fr' + ) + ); + + $crawler = $client->request('GET', $this->viewUrl); + $response = $client->getResponse(); + + $this->assertTrue($response->isSuccessful()); + + $this->assertGreaterThan(0, $crawler->filter('html:contains("Tested Person")')->count()); + $this->assertGreaterThan(0, $crawler->filter('html:contains("Réginald")')->count()); + $this->assertNotContains('Email addresses', $crawler->text()); + $this->assertNotContains('Phonenumber', $crawler->text()); + $this->assertNotContains('Langues parlées', $crawler->text()); + $this->assertNotContains(/* Etat */ 'civil', $crawler->text()); + } + + /** + * Reload the person from the db + */ + protected function refreshPerson() + { + $this->person = $this->em->getRepository('ChillPersonBundle:Person') + ->find($this->person->getId()); + } + + public function tearDown() + { + $this->refreshPerson(); + $this->em->remove($this->person); + $this->em->flush(); + } + +} diff --git a/Tests/Fixtures/App/app/config/config_test_with_hidden_fields.yml b/Tests/Fixtures/App/app/config/config_test_with_hidden_fields.yml new file mode 100644 index 000000000..e11599661 --- /dev/null +++ b/Tests/Fixtures/App/app/config/config_test_with_hidden_fields.yml @@ -0,0 +1,18 @@ +# config/config_test.yml +imports: + - { resource: config.yml } #here we import a config.yml file, this is not required + +framework: + test: ~ + session: + storage_id: session.storage.filesystem + +chill_person: + person_fields: + nationality: hidden + email: hidden + place_of_birth: hidden + phonenumber: hidden + country_of_birth: hidden + marital_status: hidden + spoken_languages: hidden \ No newline at end of file From 760ec27d7046a51a905e6c1f49032113af3e7db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 8 Feb 2016 21:51:03 +0100 Subject: [PATCH 04/13] update composer.json --- .gitlab-ci.yml | 2 +- composer.json | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea2567ddd..877e63b85 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ services: before_script: - composer config github-oauth.github.com $GITHUB_TOKEN - - composer install --no-interaction --ignore-platform-reqs + - composer install --no-interaction - cp Tests/Fixtures/App/app/config/parameters.gitlab-ci.yml Tests/Fixtures/App/app/config/parameters.yml - php Tests/Fixtures/App/app/console.php --env=test cache:warmup - php Tests/Fixtures/App/app/console.php doctrine:migrations:migrate --env=test --no-interaction diff --git a/composer.json b/composer.json index 747218e89..cdcde1723 100644 --- a/composer.json +++ b/composer.json @@ -16,13 +16,12 @@ } ], "require": { - "php": "~5.5", + "php": "~5.6|~7", "twig/extensions": "~1.0", "symfony/assetic-bundle": "~2.3", "symfony/monolog-bundle": "^2.7", - "symfony/framework-bundle": "~2.7", "symfony/yaml": "~2.7", - "symfony/symfony": "~2.7", + "symfony/symfony": "~2.8", "doctrine/dbal": "~2.5", "doctrine/orm": "~2.4", "doctrine/common": "~2.4", @@ -35,8 +34,6 @@ "doctrine/migrations": "~1.0@dev" }, "require-dev": { - "symfony/dom-crawler": "2.5", - "symfony/security": "~2.5", "symfony/phpunit-bridge": "^2.7", "fzaninotto/faker": "~1" }, From 230945c322899630bfaec32fe8b6cd62ff5c721d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 22 Feb 2016 17:46:32 +0100 Subject: [PATCH 05/13] add macro for person entity rendering easily --- Resources/views/Person/macro.html.twig | 1 + 1 file changed, 1 insertion(+) create mode 100644 Resources/views/Person/macro.html.twig diff --git a/Resources/views/Person/macro.html.twig b/Resources/views/Person/macro.html.twig new file mode 100644 index 000000000..e6773f3ae --- /dev/null +++ b/Resources/views/Person/macro.html.twig @@ -0,0 +1 @@ +{% macro render(p, withLink=false) %}{{ p.firstName }} {{ p.lastName }}{% endmacro %} \ No newline at end of file From afd52294b120bd40bb29b3af8de72a8c20717597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 23 Feb 2016 15:29:03 +0100 Subject: [PATCH 06/13] set deps on doctrine-migrations-bundle to ~1.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cdcde1723..d6448804f 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "chill-project/custom-fields": "dev-master@dev", "doctrine/doctrine-fixtures-bundle": "~2.2", "champs-libres/composer-bundle-migration": "~1.0", - "doctrine/doctrine-migrations-bundle": "dev-master@dev", + "doctrine/doctrine-migrations-bundle": "~1.1", "doctrine/migrations": "~1.0@dev" }, "require-dev": { From 46081fd360dc2b0d4a9d6fd91410e3009550cd19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 4 Mar 2016 17:24:17 +0100 Subject: [PATCH 07/13] fix condition which prevent countryOfBirth to be shown --- Resources/views/Person/edit.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/views/Person/edit.html.twig b/Resources/views/Person/edit.html.twig index fd3a59854..89bc4b44c 100644 --- a/Resources/views/Person/edit.html.twig +++ b/Resources/views/Person/edit.html.twig @@ -46,7 +46,7 @@ {%- if form.placeOfBirth is defined -%} {{ form_row(form.placeOfBirth, { 'label' : 'Place of birth'} ) }} {%- endif -%} - {%- if form.countryOfBirht is defined -%} + {%- if form.countryOfBirth is defined -%} {{ form_row(form.countryOfBirth, { 'label' : 'Country of birth' } ) }} {%- endif -%} From afe6ace33187884fab7f3ff489e84a70cd7f5bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Mar 2016 17:59:01 +0100 Subject: [PATCH 08/13] add a many-to-many relation to addresses --- DataFixtures/ORM/LoadPeople.php | 74 ++++++++++++++++++- DependencyInjection/Configuration.php | 1 + Entity/Person.php | 53 +++++++++++++ Resources/config/doctrine/Person.orm.yml | 5 ++ .../migrations/Version20160310161006.php | 49 ++++++++++++ Resources/translations/messages.fr.yml | 2 + Resources/views/Person/view.html.twig | 15 ++++ 7 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 Resources/migrations/Version20160310161006.php diff --git a/DataFixtures/ORM/LoadPeople.php b/DataFixtures/ORM/LoadPeople.php index fedff5a4d..63b0c53e5 100644 --- a/DataFixtures/ORM/LoadPeople.php +++ b/DataFixtures/ORM/LoadPeople.php @@ -26,6 +26,8 @@ use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\Persistence\ObjectManager; use Chill\PersonBundle\Entity\Person; use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes; +use Chill\MainBundle\Entity\Address; /** * Load people into database @@ -38,6 +40,13 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con use \Symfony\Component\DependencyInjection\ContainerAwareTrait; + protected $faker; + + public function __construct() + { + $this->faker = \Faker\Factory::create('fr_FR'); + } + public function prepare() { //prepare days, month, years @@ -114,12 +123,27 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con $firstName = $this->firstNamesFemale[array_rand($this->firstNamesFemale)]; } + // add an address on 80% of the created people + if (rand(0,100) < 80) { + $address = $this->getRandomAddress(); + // on 30% of those person, add multiple addresses + if (rand(0,10) < 4) { + $address = array( + $address, + $this->getRandomAddress() + ); + } + } else { + $address = null; + } + $person = array( 'FirstName' => $firstName, 'LastName' => $lastName, 'Gender' => $sex, 'Nationality' => (rand(0,100) > 50) ? NULL: 'BE', 'center' => (rand(0,1) == 0) ? 'centerA': 'centerB', + 'Address' => $address, 'maritalStatus' => $this->maritalStatusRef[array_rand($this->maritalStatusRef)] ); @@ -142,10 +166,18 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con 'Email' => "Email d'un ami: roger@tt.com", 'CountryOfBirth' => 'BE', 'Nationality' => 'BE', - 'CFData' => array() + 'CFData' => array(), + 'Address' => null ), $specific); } + /** + * create a new person from array data + * + * @param array $person + * @param ObjectManager $manager + * @throws \Exception + */ private function addAPerson(array $person, ObjectManager $manager) { $p = new Person(); @@ -164,13 +196,51 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con $value = $this->getReference($value); break; } - call_user_func(array($p, 'set'.$key), $value); + + //try to add the data using the setSomething function, + // if not possible, fallback to addSomething function + if (method_exists($p, 'set'.$key)) { + call_user_func(array($p, 'set'.$key), $value); + } elseif (method_exists($p, 'add'.$key)) { + // if we have a "addSomething", we may have multiple items to add + // so, we set the value in an array if it is not an array, and + // will call the function addSomething multiple times + if (!is_array($value)) { + $value = array($value); + } + + foreach($value as $v) { + if ($v !== NULL) { + call_user_func(array($p, 'add'.$key), $v); + } + } + + } } $manager->persist($p); echo "add person'".$p->__toString()."'\n"; } + /** + * Creata a random address + * + * @return Address + */ + private function getRandomAddress() + { + return (new Address()) + ->setStreetAddress1($this->faker->streetAddress) + ->setStreetAddress2( + rand(0,9) > 5 ? $this->faker->streetAddress : '' + ) + ->setPostcode($this->getReference( + LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] + )) + ->setValidFrom($this->faker->dateTimeBetween('-5 years')) + ; + } + private function getCountry($countryCode) { if ($countryCode === NULL) { diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 5299a22fd..8a935b8b1 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -71,6 +71,7 @@ class Configuration implements ConfigurationInterface ->append($this->addFieldNode('country_of_birth')) ->append($this->addFieldNode('marital_status')) ->append($this->addFieldNode('spoken_languages')) + ->append($this->addFieldNode('address')) ->end() //children for 'person_fields', parent = array 'person_fields' ->end() // person_fields, parent = children of root ->end() // children of 'root', parent = root diff --git a/Entity/Person.php b/Entity/Person.php index 9f707790b..fda72bb54 100644 --- a/Entity/Person.php +++ b/Entity/Person.php @@ -27,6 +27,8 @@ use Chill\MainBundle\Entity\Country; use Chill\PersonBundle\Entity\MaritalStatus; use Doctrine\Common\Collections\ArrayCollection; use Chill\MainBundle\Entity\HasCenterInterface; +use Chill\MainBundle\Entity\Address; +use Doctrine\Common\Collections\Criteria; /** * Person @@ -100,9 +102,16 @@ class Person implements HasCenterInterface { /** @var array Array where customfield's data are stored */ private $cFData; + /** + * + * @var \Doctrine\Common\Collections\Collection + */ + private $addresses; + public function __construct(\DateTime $opening = null) { $this->accompanyingPeriods = new ArrayCollection(); $this->spokenLanguages = new ArrayCollection(); + $this->addresses = new ArrayCollection(); if ($opening === null) { $opening = new \DateTime(); @@ -597,6 +606,50 @@ class Person implements HasCenterInterface { return $this->spokenLanguages; } + public function addAddress(Address $address) + { + $this->addresses[] = $address; + + return $this; + } + + public function removeAddress(Address $address) + { + $this->addresses->removeElement($address); + } + + /** + * + * @return \Chill\MainBundle\Entity\Address[]@return Address[] + */ + public function getAddresses() + { + return $this->addresses; + } + + public function getLastAddress(\DateTime $date = null) + { + if ($date === null) { + $date = new \DateTime('now'); + } + + $lastAddress = null; + + foreach ($this->getAddresses() as $address) { + if ($address->getValidFrom() < $date) { + if ($lastAddress === NULL) { + $lastAddress = $address; + } else { + if ($lastAddress->getValidFrom() < $address->getValidFrom()) { + $lastAddress = $address; + } + } + } + } + + return $lastAddress; + } + /** * Validation callback that checks if the accompanying periods are valid * diff --git a/Resources/config/doctrine/Person.orm.yml b/Resources/config/doctrine/Person.orm.yml index 838ab1e8f..60f128a11 100644 --- a/Resources/config/doctrine/Person.orm.yml +++ b/Resources/config/doctrine/Person.orm.yml @@ -72,4 +72,9 @@ Chill\PersonBundle\Entity\Person: inverseJoinColumns: language_id: referencedColumnName: id + addresses: + targetEntity: Chill\MainBundle\Entity\Address + joinTable: + name: chill_person_persons_to_addresses + cascade: [persist, remove, merge, detach] lifecycleCallbacks: { } diff --git a/Resources/migrations/Version20160310161006.php b/Resources/migrations/Version20160310161006.php new file mode 100644 index 000000000..f17ae910c --- /dev/null +++ b/Resources/migrations/Version20160310161006.php @@ -0,0 +1,49 @@ +abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE TABLE chill_person_persons_to_addresses (' + . 'person_id INT NOT NULL, ' + . 'address_id INT NOT NULL, ' + . 'PRIMARY KEY(person_id, address_id))'); + $this->addSql('CREATE INDEX IDX_4655A196217BBB47 ' + . 'ON chill_person_persons_to_addresses (person_id)'); + $this->addSql('CREATE INDEX IDX_4655A196F5B7AF75 ' + . 'ON chill_person_persons_to_addresses (address_id)'); + $this->addSql('ALTER TABLE chill_person_persons_to_addresses ' + . 'ADD CONSTRAINT FK_4655A196217BBB47 ' + . 'FOREIGN KEY (person_id) ' + . 'REFERENCES Person (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_persons_to_addresses ' + . 'ADD CONSTRAINT FK_4655A196F5B7AF75 ' + . 'FOREIGN KEY (address_id) ' + . 'REFERENCES chill_main_address (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP TABLE chill_person_persons_to_addresses'); + + } +} diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 0aaccf0d0..9d7d9f364 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -70,6 +70,8 @@ Reset: 'Remise à zéro' 'Person details': 'Détails de la personne' 'Update details for %name%': 'Modifier détails de %name%' Accompanying period list: Périodes d'accompagnement +Since %date%: Depuis le %date% +No address given: Pas d'adresse renseignée #timeline 'An accompanying period is opened for %person% on %date%': Une période d'accompagnement a été ouverte le %date% pour %person% diff --git a/Resources/views/Person/view.html.twig b/Resources/views/Person/view.html.twig index e86a55857..1f17e7cb6 100644 --- a/Resources/views/Person/view.html.twig +++ b/Resources/views/Person/view.html.twig @@ -16,6 +16,8 @@ #} {% extends "ChillPersonBundle::layout.html.twig" %} +{% import 'ChillMainBundle:Address:macro.html.twig' as address %} + {% set activeRouteKey = 'chill_person_view' %} {# @@ -165,6 +167,19 @@ This view should receive those arguments:

 {{ 'Contact information'|trans|upper }}

+ {%- if chill_person.fields.address == 'visible' -%} +
+
{{ 'Address'|trans }}
+
+ {%- if person.lastAddress is not empty -%} + {{ address._render(person.lastAddress) }} + {%- else -%} + {{ 'No address given'|trans }} + {%- endif -%} +
+
+ {%- endif -%} + {%- if chill_person.fields.email == 'visible' -%}
{{ 'Email'|trans }} :
From 91ed954be825f74c358f094079067e3762f9b345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Mar 2016 21:44:53 +0100 Subject: [PATCH 09/13] allow to update existing address --- Controller/PersonAddressController.php | 151 +++++++++++++++++++++++++ Resources/config/routing.yml | 8 ++ Resources/translations/messages.fr.yml | 4 + Resources/views/Address/edit.html.twig | 36 ++++++ Resources/views/Person/view.html.twig | 3 + 5 files changed, 202 insertions(+) create mode 100644 Controller/PersonAddressController.php create mode 100644 Resources/views/Address/edit.html.twig diff --git a/Controller/PersonAddressController.php b/Controller/PersonAddressController.php new file mode 100644 index 000000000..6dcea9de1 --- /dev/null +++ b/Controller/PersonAddressController.php @@ -0,0 +1,151 @@ +, + * + * 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\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Chill\PersonBundle\Entity\Person; +use Chill\MainBundle\Form\Type\AddressType; +use Chill\MainBundle\Entity\Address; +use Doctrine\Common\Collections\Criteria; +use Symfony\Component\HttpFoundation\Request; + +/** + * Controller for addresses associated with person + * + * @author Julien Fastré + * @author Champs Libres + */ +class PersonAddressController extends Controller +{ + 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 ($form->isSubmitted() && $form->isValid()) { + $this->getDoctrine()->getManager() + ->flush(); + + $this->addFlash('success', $this->get('translator')->trans( + "The address has been successfully updated")); + + return $this->redirectToRoute('chill_person_view', array( + 'person_id' => $person->getId() + )); + } + + return $this->render('ChillPersonBundle:Address:edit.html.twig', array( + 'person' => $person, + 'address' => $address, + 'form' => $form->createView() + )); + } + + /** + * + * @param Person $person + * @param Address $address + * @return \Symfony\Component\Form\Form + */ + protected function createEditForm(Person $person, Address $address) + { + $form = $this->createForm(AddressType::class, $address, array( + 'method' => 'POST', + 'action' => $this->generateUrl('chill_person_address_update', array( + 'person_id' => $person->getId(), + 'address_id' => $address->getId() + )) + )); + + $form->add('submit', 'submit', array( + 'label' => 'Submit' + )); + + return $form; + } + + /** + * + * @param Person $person + * @param int $address_id + * @return Address + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the address id does not exists or is not associated with given person + */ + protected function findAddressById(Person $person, $address_id) + { + // filtering address + $criteria = Criteria::create() + ->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(); + } +} diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index 91b481ea4..d9983e849 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -69,6 +69,14 @@ chill_person_accompanying_period_close: chill_person_accompanying_period_open: path: /{_locale}/person/{person_id}/accompanying-period/open defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:open } + +chill_person_address_edit: + path: /{_locale}/person/{person_id}/address/{address_id}/edit + defaults: { _controller: ChillPersonBundle:PersonAddress:edit } + +chill_person_address_update: + path: /{_locale}/person/{person_id}/address/{address_id}/update + defaults: { _controller: ChillPersonBundle:PersonAddress:update } chill_person_export: path: /{_locale}/person/export/ diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 9d7d9f364..959ec8db5 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -70,8 +70,12 @@ Reset: 'Remise à zéro' 'Person details': 'Détails de la personne' 'Update details for %name%': 'Modifier détails de %name%' Accompanying period list: Périodes d'accompagnement + +#address Since %date%: Depuis le %date% No address given: Pas d'adresse renseignée +The address has been successfully updated: L'adresse a été mise à jour avec succès +Update address for %name%: Mettre à jour une adresse pour %name% #timeline 'An accompanying period is opened for %person% on %date%': Une période d'accompagnement a été ouverte le %date% pour %person% diff --git a/Resources/views/Address/edit.html.twig b/Resources/views/Address/edit.html.twig new file mode 100644 index 000000000..b71134745 --- /dev/null +++ b/Resources/views/Address/edit.html.twig @@ -0,0 +1,36 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * 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 . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = '' %} + +{% block title %}{{ 'Update address for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %} + +{% block personcontent %} + + {{ form_start(form) }} + + {{ form_row(form.streetAddress1) }} + {{ form_row(form.streetAddress2) }} + {{ form_row(form.postCode) }} + {{ form_row(form.validFrom) }} + + {{ form_row(form.submit, { 'attr' : { 'class': 'sc-button bt-edit' } } ) }} + + {{ form_end(form) }} + +{% endblock personcontent %} \ No newline at end of file diff --git a/Resources/views/Person/view.html.twig b/Resources/views/Person/view.html.twig index 1f17e7cb6..a2ea48d39 100644 --- a/Resources/views/Person/view.html.twig +++ b/Resources/views/Person/view.html.twig @@ -173,6 +173,9 @@ This view should receive those arguments:
{%- if person.lastAddress is not empty -%} {{ address._render(person.lastAddress) }} + + {{ 'Edit'|trans }} + {%- else -%} {{ 'No address given'|trans }} {%- endif -%} From 8d50a9435121b93e99bbb73eebc302628f1aa67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Mar 2016 21:47:58 +0100 Subject: [PATCH 10/13] fix dot in class name --- Resources/views/Person/view.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/views/Person/view.html.twig b/Resources/views/Person/view.html.twig index a2ea48d39..97e1577c5 100644 --- a/Resources/views/Person/view.html.twig +++ b/Resources/views/Person/view.html.twig @@ -177,7 +177,7 @@ This view should receive those arguments: {{ 'Edit'|trans }} {%- else -%} - {{ 'No address given'|trans }} + {{ 'No address given'|trans }} {%- endif -%}
From ff5bda12b66df0accfb6167f7eacdc5fc0414cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sun, 13 Mar 2016 22:14:40 +0100 Subject: [PATCH 11/13] add new / create address + tests --- Controller/PersonAddressController.php | 110 +++++++++- Entity/Person.php | 21 +- Resources/config/doctrine/Person.orm.yml | 1 + Resources/config/routing.yml | 13 ++ Resources/translations/messages.fr.yml | 6 + Resources/views/Address/edit.html.twig | 15 +- Resources/views/Address/list.html.twig | 84 ++++++++ Resources/views/Address/new.html.twig | 47 +++++ Resources/views/Person/view.html.twig | 8 +- .../PersonAddressControllerTest.php | 189 ++++++++++++++++++ 10 files changed, 477 insertions(+), 17 deletions(-) create mode 100644 Resources/views/Address/list.html.twig create mode 100644 Resources/views/Address/new.html.twig create mode 100644 Tests/Controller/PersonAddressControllerTest.php diff --git a/Controller/PersonAddressController.php b/Controller/PersonAddressController.php index 6dcea9de1..f76719344 100644 --- a/Controller/PersonAddressController.php +++ b/Controller/PersonAddressController.php @@ -37,6 +37,92 @@ 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); + + if ($form->isSubmitted() && $form->isValid()) { + $address = $form->getData(); + $person->addAddress($address); + + $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() + )); + } + + 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() @@ -91,7 +177,7 @@ class PersonAddressController extends Controller $this->addFlash('success', $this->get('translator')->trans( "The address has been successfully updated")); - return $this->redirectToRoute('chill_person_view', array( + return $this->redirectToRoute('chill_person_address_list', array( 'person_id' => $person->getId() )); } @@ -126,6 +212,28 @@ class PersonAddressController extends Controller return $form; } + /** + * + * @param Person $person + * @param Address $address + * @return \Symfony\Component\Form\Form + */ + protected function createCreateForm(Person $person, Address $address) + { + $form = $this->createForm(AddressType::class, $address, array( + 'method' => 'POST', + 'action' => $this->generateUrl('chill_person_address_create', array( + 'person_id' => $person->getId() + )) + )); + + $form->add('submit', 'submit', array( + 'label' => 'Submit' + )); + + return $form; + } + /** * * @param Person $person diff --git a/Entity/Person.php b/Entity/Person.php index fda72bb54..b357fafb2 100644 --- a/Entity/Person.php +++ b/Entity/Person.php @@ -619,8 +619,10 @@ class Person implements HasCenterInterface { } /** + * By default, the addresses are ordered by date, descending (the most + * recent first) * - * @return \Chill\MainBundle\Entity\Address[]@return Address[] + * @return \Chill\MainBundle\Entity\Address[] */ public function getAddresses() { @@ -633,21 +635,14 @@ class Person implements HasCenterInterface { $date = new \DateTime('now'); } - $lastAddress = null; + $addresses = $this->getAddresses(); - foreach ($this->getAddresses() as $address) { - if ($address->getValidFrom() < $date) { - if ($lastAddress === NULL) { - $lastAddress = $address; - } else { - if ($lastAddress->getValidFrom() < $address->getValidFrom()) { - $lastAddress = $address; - } - } - } + if ($addresses == null) { + + return null; } - return $lastAddress; + return $addresses->first(); } /** diff --git a/Resources/config/doctrine/Person.orm.yml b/Resources/config/doctrine/Person.orm.yml index 60f128a11..e566ddc7e 100644 --- a/Resources/config/doctrine/Person.orm.yml +++ b/Resources/config/doctrine/Person.orm.yml @@ -74,6 +74,7 @@ Chill\PersonBundle\Entity\Person: referencedColumnName: id addresses: targetEntity: Chill\MainBundle\Entity\Address + orderBy: { 'validFrom': 'DESC' } joinTable: name: chill_person_persons_to_addresses cascade: [persist, remove, merge, detach] diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index d9983e849..927eb59e5 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -70,6 +70,19 @@ chill_person_accompanying_period_open: path: /{_locale}/person/{person_id}/accompanying-period/open defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:open } +chill_person_address_list: + path: /{_locale}/person/{person_id}/address/list + defaults: { _controller: ChillPersonBundle:PersonAddress:list } + +chill_person_address_create: + path: /{_locale}/person/{person_id}/address/create + defaults: { _controller: ChillPersonBundle:PersonAddress:create } + methods: [POST] + +chill_person_address_new: + path: /{_locale}/person/{person_id}/address/new + defaults: { _controller: ChillPersonBundle:PersonAddress:new } + chill_person_address_edit: path: /{_locale}/person/{person_id}/address/{address_id}/edit defaults: { _controller: ChillPersonBundle:PersonAddress:edit } diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 959ec8db5..19b1be9ef 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -76,6 +76,12 @@ Since %date%: Depuis le %date% No address given: Pas d'adresse renseignée The address has been successfully updated: L'adresse a été mise à jour avec succès Update address for %name%: Mettre à jour une adresse pour %name% +Addresses'history for %name%: Historique des adresses de %name% +Addresses'history: Historique des adresses +New address for %name% : Nouvelle adresse pour %name% +The new address was created successfully: La nouvelle adresse a été créée +Add an address: Ajouter une adresse +Back to the person details: Retour aux détails de la personne #timeline 'An accompanying period is opened for %person% on %date%': Une période d'accompagnement a été ouverte le %date% pour %person% diff --git a/Resources/views/Address/edit.html.twig b/Resources/views/Address/edit.html.twig index b71134745..8090416aa 100644 --- a/Resources/views/Address/edit.html.twig +++ b/Resources/views/Address/edit.html.twig @@ -18,10 +18,12 @@ {% set activeRouteKey = '' %} -{% block title %}{{ 'Update address for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %} +{% block title 'Update address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) %} {% block personcontent %} +

{{ 'Update address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}

+ {{ form_start(form) }} {{ form_row(form.streetAddress1) }} @@ -29,7 +31,16 @@ {{ form_row(form.postCode) }} {{ form_row(form.validFrom) }} - {{ form_row(form.submit, { 'attr' : { 'class': 'sc-button bt-edit' } } ) }} + {{ form_end(form) }} diff --git a/Resources/views/Address/list.html.twig b/Resources/views/Address/list.html.twig new file mode 100644 index 000000000..cc382514f --- /dev/null +++ b/Resources/views/Address/list.html.twig @@ -0,0 +1,84 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * 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 . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% import 'ChillMainBundle:Address:macro.html.twig' as address_macros %} + +{% set activeRouteKey = '' %} + +{% block title %}{{ 'Addresses\'history for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}{% endblock %} + +{% block personcontent %} + +

{{ 'Addresses\'history for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}

+ + + + + + + + + + + {% if person.addresses|length == 0 %} + + + + {% else %} + {% for address in person.addresses %} + + + + + + + + {% endfor %} + {% endif %} + +
{{ 'Valid from'|trans }}{{ 'Address'|trans }} 
+ {{ 'No address given'|trans }} + + {{ 'Add an address'|trans }} + +
{{ 'Since %date%'|trans( { '%date%' : address.validFrom|localizeddate('long', 'none') } ) }} + {{ address_macros._render(address, { 'with_valid_from' : false } ) }} + + +
+ + + +{% endblock personcontent %} \ No newline at end of file diff --git a/Resources/views/Address/new.html.twig b/Resources/views/Address/new.html.twig new file mode 100644 index 000000000..8e9f9a4ca --- /dev/null +++ b/Resources/views/Address/new.html.twig @@ -0,0 +1,47 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * 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 . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = '' %} + +{% block title %}{{ 'New address for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %} + +{% block personcontent %} + +

{{ 'New address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}

+ + {{ form_start(form) }} + + {{ form_row(form.streetAddress1) }} + {{ form_row(form.streetAddress2) }} + {{ form_row(form.postCode) }} + {{ form_row(form.validFrom) }} + + + + {{ form_end(form) }} + +{% endblock personcontent %} \ No newline at end of file diff --git a/Resources/views/Person/view.html.twig b/Resources/views/Person/view.html.twig index 97e1577c5..7290aedcc 100644 --- a/Resources/views/Person/view.html.twig +++ b/Resources/views/Person/view.html.twig @@ -175,9 +175,15 @@ This view should receive those arguments: {{ address._render(person.lastAddress) }} {{ 'Edit'|trans }} +
+ + {{ 'Addresses\'history'|trans }} {%- else -%} - {{ 'No address given'|trans }} + {{ 'No address given'|trans }} + + {{ 'Add an address'|trans }} + {%- endif -%} diff --git a/Tests/Controller/PersonAddressControllerTest.php b/Tests/Controller/PersonAddressControllerTest.php new file mode 100644 index 000000000..0f23236a8 --- /dev/null +++ b/Tests/Controller/PersonAddressControllerTest.php @@ -0,0 +1,189 @@ + + * + * 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\Tests\Controller; + +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Chill\PersonBundle\Entity\Person; + +/** + * + * + * @author Julien Fastré + */ +class PersonAddressControllerTest extends WebTestCase +{ + /** @var \Doctrine\ORM\EntityManagerInterface The entity manager */ + protected $em; + + /** @var Person The person on which the test is executed */ + protected static $person; + + /** + * + * @var \Chill\MainBundle\Entity\PostalCode + */ + protected $postalCode; + + /** + * + * @var \Symfony\Component\BrowserKit\Client + */ + protected $client; + + public static function setUpBeforeClass() + { + static::bootKernel(); + + $em = static::$kernel->getContainer() + ->get('doctrine.orm.entity_manager'); + + $center = $em->getRepository('ChillMainBundle:Center') + ->findOneBy(array('name' => 'Center A')); + + self::$person = (new Person()) + ->setLastName("Tested person") + ->setFirstName("Test") + ->setCenter($center) + ->setGender(Person::MALE_GENDER); + + $em->persist(self::$person); + $em->flush(); + } + + /** + * Prepare client and create a random person + */ + public function setUp() + { + static::bootKernel(); + + $this->em = static::$kernel->getContainer() + ->get('doctrine.orm.entity_manager'); + + $this->postalCode = $this->em->getRepository('ChillMainBundle:PostalCode') + ->findOneBy(array('code' => 1000)); + + $this->client = static::createClient(array(), array( + 'PHP_AUTH_USER' => 'center a_social', + 'PHP_AUTH_PW' => 'password', + )); + } + + public static function tearDownAfter() + { + $this->refreshPerson(); + $this->em->remove(self::$person); + $this->em->flush(); + } + + /** + * Reload the person from the db + */ + protected function refreshPerson() + { + self::$person = $this->em->getRepository('ChillPersonBundle:Person') + ->find(self::$person->getId()); + } + + public function testEmptyList() + { + $crawler = $this->client->request('GET', '/fr/person/'. + self::$person->getId().'/address/list'); + + $this->assertTrue($this->client->getResponse()->isSuccessful()); + + $this->assertEquals(1, $crawler->filter('td:contains("Pas d\'adresse renseignée")') + ->count(), + "assert that a message say 'no address given'"); + + } + + /** + * @depends testEmptyList + */ + public function testCreateAddress() + { + $crawler = $this->client->request('GET', '/fr/person/'. + self::$person->getId().'/address/new'); + + $this->assertTrue($this->client->getResponse()->isSuccessful()); + + $form = $crawler->selectButton('Envoi')->form(array( + 'address[streetAddress1]' => 'Rue de la Paix, 50', + 'address[streetAddress2]' => $this->postalCode->getId(), + 'address[validFrom]' => '15-01-2016' + )); + + $this->client->submit($form); + + $crawler = $this->client->followRedirect(); + + $this->assertRegexp('|/fr/person/[0-9]{1,}/address/list|', + $this->client->getHistory()->current()->getUri(), + "assert that the current page is on |/fr/person/[0-9]{1,}/address/list|"); + $this->assertEquals(1, $crawler + ->filter('div.flash_message.success') + ->count(), + "Asserting that the response page contains a success flash message"); + $this->assertEquals(1, $crawler + ->filter('td:contains("Rue de la Paix, 50")') + ->count(), + "Asserting that the page contains the new address"); + + } + + /** + * @depends testCreateAddress + */ + public function testUpdateAddress() + { + $this->refreshPerson(); + $address = self::$person->getLastAddress(); + + $crawler = $this->client->request('GET', '/fr/person/'.self::$person->getId() + .'/address/'.$address->getId().'/edit'); + + $this->assertTrue($this->client->getResponse()->isSuccessful()); + + $form = $crawler->selectButton('Envoi')->form(array( + 'address[streetAddress1]' => 'Rue du Trou Normand, 15', + 'address[validFrom]' => '15-01-2015' + )); + + $this->client->submit($form); + + $crawler = $this->client->followRedirect(); + + $this->assertRegexp('|/fr/person/[0-9]{1,}/address/list|', + $this->client->getHistory()->current()->getUri(), + "assert that the current page is on |/fr/person/[0-9]{1,}/address/list|"); + $this->assertGreaterThan(0, $crawler + ->filter('div.flash_message.success') + ->count(), + "Asserting that the response page contains a success flash message"); + $this->assertEquals(1, $crawler + ->filter('td:contains("Rue du Trou Normand")') + ->count(), + "Asserting that the page contains the new address"); + } + + + +} From 40a14761560d0495747ed7fbfc23b02434985cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 14 Mar 2016 22:46:51 +0100 Subject: [PATCH 12/13] adding a rendering block event ref Chill-project/Chill-Main#12 ref Chill-project/Chill-Group#7 --- Resources/views/layout.html.twig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/views/layout.html.twig b/Resources/views/layout.html.twig index 9baf42dc0..3a9b6aae5 100644 --- a/Resources/views/layout.html.twig +++ b/Resources/views/layout.html.twig @@ -108,4 +108,8 @@ 'args' : {'person_id': person.id }, 'activeRouteKey': activeRouteKey }) }} + +
+ {{ chill_delegated_block('person_post_vertical_menu', { 'person': person } ) }} +
{% endblock %} \ No newline at end of file From bc8832b732f07b067b62fc57f4f5a3fa4122f481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 24 Mar 2016 01:36:09 +0100 Subject: [PATCH 13/13] add a 'PickAPersonType' ref Chill-project/Chill-Event#5 fix #9 --- Form/Type/PickPersonType.php | 161 ++++++++++++++++++++++ Resources/config/services.yml | 16 ++- Resources/translations/messages.fr.yml | 3 + Tests/Form/Type/PickPersonTypeTest.php | 181 +++++++++++++++++++++++++ 4 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 Form/Type/PickPersonType.php create mode 100644 Tests/Form/Type/PickPersonTypeTest.php diff --git a/Form/Type/PickPersonType.php b/Form/Type/PickPersonType.php new file mode 100644 index 000000000..b9932c4ed --- /dev/null +++ b/Form/Type/PickPersonType.php @@ -0,0 +1,161 @@ + + * + * 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\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Chill\MainBundle\Entity\Center; +use Chill\PersonBundle\Entity\PersonRepository; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Role\Role; +use Chill\MainBundle\Entity\GroupCenter; +use Chill\PersonBundle\Entity\Person; + +/** + * This type allow to pick a person. + * + * The form is embedded in a select2 input. + * + * The people may be filtered : + * + * - with the `centers` option, only the people associated with the given center(s) + * are seen. May be an instance of `Chill\MainBundle\Entity\Center`, or an array of + * `Chill\MainBundle\Entity\Center`. By default, all the reachable centers as selected. + * - with the `role` option, only the people belonging to the reachable center for the + * given role are displayed. + * + * + * @author Julien Fastré + */ +class PickPersonType extends AbstractType +{ + /** + * @var PersonRepository + */ + protected $personRepository; + + /** + * + * @var \Chill\MainBundle\Entity\User + */ + protected $user; + + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + public function __construct( + PersonRepository $personRepository, + TokenStorageInterface $tokenStorage, + AuthorizationHelper $authorizationHelper + ) + { + $this->personRepository = $personRepository; + $this->user = $tokenStorage->getToken()->getUser(); + $this->authorizationHelper = $authorizationHelper; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $qb = $options['query_builder']; + + if ($options['role'] === NULL) { + $centers = array_map(function (GroupCenter $g) { + + return $g->getCenter(); + }, $this->user->getGroupCenters()->toArray()); + } else { + $centers = $this->authorizationHelper + ->getReachableCenters($this->user, $options['role']); + } + + if ($options['centers'] === NULL) { + // we select all selected centers + $selectedCenters = $centers; + } else { + $selectedCenters = array(); + $options['centers'] = is_array($options['centers']) ? + $options['centers'] : array($options['centers']); + + foreach ($options['centers'] as $c) { + // check that every member of the array is a center + if (!$c instanceof Center) { + throw new \RuntimeException('Every member of the "centers" ' + . 'option must be an instance of '.Center::class); + } + if (!in_array($c->getId(), array_map( + function(Center $c) { return $c->getId();}, + $centers))) { + throw new AccessDeniedException('The given center is not reachable'); + } + $selectedCenters[] = $c; + } + } + + + $qb + ->orderBy('p.firstName', 'ASC') + ->orderBy('p.lastName', 'ASC') + ->where($qb->expr()->in('p.center', ':centers')) + ->setParameter('centers', $selectedCenters) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + // add the possibles options for this type + $resolver->setDefined('centers') + ->addAllowedTypes('centers', array('array', Center::class, 'null')) + ->setDefault('centers', null) + ->setDefined('role') + ->addAllowedTypes('role', array(Role::class, 'null')) + ->setDefault('role', null) + ; + + // add the default options + $resolver->setDefaults(array( + 'class' => Person::class, + 'choice_label' => function(Person $p) { + return $p->getFirstname().' '.$p->getLastname(); + }, + 'placeholder' => 'Pick a person', + 'choice_attr' => function(Person $p) { + return array( + 'data-center' => $p->getCenter()->getId() + ); + }, + 'attr' => array('class' => 'select2 '), + 'query_builder' => $this->personRepository->createQueryBuilder('p') + )); + } + + public function getParent() + { + return \Symfony\Bridge\Doctrine\Form\Type\EntityType::class; + } + +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index b0cf94e1d..3bb58b536 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -69,4 +69,18 @@ services: - "@chill.main.form.data_transformer.center_transformer" tags: - { name: form.type, alias: chill_personbundle_person_creation } - \ No newline at end of file + + chill.person.form.type.pick_person: + class: Chill\PersonBundle\Form\Type\PickPersonType + arguments: + - "@chill.person.repository.person" + - "@security.token_storage" + - "@chill.main.security.authorization.helper" + tags: + - { name: form.type } + + chill.person.repository.person: + class: Chill\PersonBundle\Entity\PersonRepository + factory: ['@doctrine.orm.entity_manager', getRepository] + arguments: + - 'Chill\PersonBundle\Entity\Person' \ No newline at end of file diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 19b1be9ef..8836cb641 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -71,6 +71,9 @@ Reset: 'Remise à zéro' 'Update details for %name%': 'Modifier détails de %name%' Accompanying period list: Périodes d'accompagnement +# pickAPersonType +Pick a person: Choisir une personne + #address Since %date%: Depuis le %date% No address given: Pas d'adresse renseignée diff --git a/Tests/Form/Type/PickPersonTypeTest.php b/Tests/Form/Type/PickPersonTypeTest.php new file mode 100644 index 000000000..0baf96bfc --- /dev/null +++ b/Tests/Form/Type/PickPersonTypeTest.php @@ -0,0 +1,181 @@ + + * + * 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\Tests\Form\Type; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Chill\PersonBundle\Form\Type\PickPersonType; + +/** + * + * + * @author Julien Fastré + */ +class PickPersonTypeTest extends KernelTestCase +{ + /** + * + * @var \Chill\MainBundle\Entity\User + */ + protected $user; + + /** + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * + * @var \Symfony\Component\Form\FormFactoryInterface + */ + protected $formFactory; + + public function setUp() + { + self::bootKernel(); + + $this->container = self::$kernel->getContainer(); + + $this->user = $this->container->get('doctrine.orm.entity_manager') + ->getRepository('ChillMainBundle:User') + ->findOneBy(array('username' => 'multi_center')); + + $this->formFactory = $this->container->get('form.factory'); + + $token = (new UsernamePasswordToken($this->user, 'password', 'firewall')); + $this->container->get('security.token_storage') + ->setToken($token); + } + + public function testWithoutOption() + { + $form = $this->formFactory + ->createBuilder(PickPersonType::class, null, array()) + ->getForm(); + + $this->assertInstanceOf(\Symfony\Component\Form\FormInterface::class, + $form); + + // transform into a view to have data-center attr + $view = $form->createView(); + + $centerIds = array(); + + /* @var $centerIds \Symfony\Component\Form\ChoiceList\View\ChoiceView */ + foreach($view->vars['choices'] as $choice) { + $centerIds[] = $choice->attr['data-center']; + } + + $this->assertEquals(2, count(array_unique($centerIds)), + "test that the form contains people from 2 centers"); + } + + /** + * Test the form with an option 'centers' with an unique center + * entity (not in an array) + */ + public function testWithOptionCenter() + { + $center = $this->container->get('doctrine.orm.entity_manager') + ->getRepository('ChillMainBundle:Center') + ->findOneBy(array('name' => 'Center A')) + ; + + $form = $this->formFactory + ->createBuilder(PickPersonType::class, null, array( + 'centers' => $center + )) + ->getForm(); + + // transform into a view to have data-center attr + $view = $form->createView(); + + /* @var $centerIds \Symfony\Component\Form\ChoiceList\View\ChoiceView */ + foreach($view->vars['choices'] as $choice) { + $centerIds[] = $choice->attr['data-center']; + } + + $this->assertEquals(1, count(array_unique($centerIds)), + "test that the form contains people from only one centers"); + + $this->assertEquals($center->getId(), array_unique($centerIds)[0]); + + } + + /** + * Test the form with multiple centers + */ + public function testWithOptionCenters() + { + $centers = $this->container->get('doctrine.orm.entity_manager') + ->getRepository('ChillMainBundle:Center') + ->findAll() + ; + + $form = $this->formFactory + ->createBuilder(PickPersonType::class, null, array( + 'centers' => $centers + )) + ->getForm(); + + // transform into a view to have data-center attr + $view = $form->createView(); + + /* @var $centerIds \Symfony\Component\Form\ChoiceList\View\ChoiceView */ + foreach($view->vars['choices'] as $choice) { + $centerIds[] = $choice->attr['data-center']; + } + + $this->assertEquals(2, count(array_unique($centerIds)), + "test that the form contains people from only one centers"); + + } + + /** + * test with an invalid center type in the option 'centers' (in an array) + * + * @expectedException \RuntimeException + */ + public function testWithInvalidOptionCenters() + { + + $form = $this->formFactory + ->createBuilder(PickPersonType::class, null, array( + 'centers' => array('string') + )) + ->getForm(); + } + + public function testWithOptionRoleInvalid() + { + $form = $this->formFactory + ->createBuilder(PickPersonType::class, null, array( + 'role' => new \Symfony\Component\Security\Core\Role\Role('INVALID') + )) + ->getForm(); + + // transform into a view to have data-center attr + $view = $form->createView(); + + $this->assertEquals(0, count($view->vars['choices'])); + } + +}