diff --git a/.gitignore b/.gitignore index c497e28d7..aeec2c4b5 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ docs/build/ .phpunit.result.cache ###< phpunit/phpunit ### +/.php-cs-fixer.cache +/.idea/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 295eb3e0e..f76ec4b98 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,48 +1,87 @@ --- -image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 # Select what we should cache between builds cache: - paths: - - tests/app/vendor/ - - .composer - -before_script: - # add extensions to postgres - - PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -h db -c "CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm;" - # Install and run Composer - - mkdir -p $COMPOSER_HOME - - curl -sS https://getcomposer.org/installer | php - - php -d memory_limit=2G composer.phar install - - php tests/app/bin/console doctrine:migrations:migrate -n - - php -d memory_limit=2G tests/app/bin/console doctrine:fixtures:load -n - - echo "before_script finished" + paths: + - tests/app/vendor/ + - .cache # Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service # See http://docs.gitlab.com/ee/ci/services/README.html for examples. services: - - name: postgis/postgis:12-3.1-alpine - alias: db - - name: redis - alias: redis + - name: postgis/postgis:12-3.1-alpine + alias: db + - name: redis + alias: redis # Set any variables we need variables: - # Configure postgres environment variables (https://hub.docker.com/r/_/postgres/) - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - # fetch the chill-app using git submodules - GIT_SUBMODULE_STRATEGY: recursive - REDIS_HOST: redis - REDIS_PORT: 6379 - REDIS_URL: redis://redis:6379 - # change vendor dir to make the app install into tests/apps - COMPOSER_VENDOR_DIR: tests/app/vendor - # cache some composer data - COMPOSER_HOME: .composer + GIT_DEPTH: 1 + # Configure postgres environment variables (https://hub.docker.com/r/_/postgres/) + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + # fetch the chill-app using git submodules + GIT_SUBMODULE_STRATEGY: recursive + REDIS_HOST: redis + REDIS_PORT: 6379 + REDIS_URL: redis://redis:6379 + # change vendor dir to make the app install into tests/apps + COMPOSER_VENDOR_DIR: tests/app/vendor +stages: + - Composer install + - Tests -# Run our tests -test: - script: - - php -d memory_limit=3G bin/phpunit --colors=never +build: + stage: Composer install + image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + before_script: + - curl -sS https://getcomposer.org/installer | php + - php -d memory_limit=2G composer.phar config -g cache-dir "$(pwd)/.cache" + script: + - php -d memory_limit=2G composer.phar install --optimize-autoloader --no-ansi --no-interaction --no-progress + cache: + paths: + - .cache/ + artifacts: + expire_in: 30 min + paths: + - bin + - tests/app/vendor/ + +code_style: + stage: Tests + image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + script: + - bin/grumphp run --tasks=phpcsfixer + artifacts: + expire_in: 30 min + paths: + - bin + - tests/app/vendor/ + +sa_tests: + stage: Tests + image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + script: + - bin/grumphp run --tasks=phpstan + artifacts: + expire_in: 30 min + paths: + - bin + - tests/app/vendor/ + +unit_tests: + stage: Tests + image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 + script: + - php tests/app/bin/console doctrine:migrations:migrate -n + - php -d memory_limit=2G tests/app/bin/console cache:clear --env=dev + - php -d memory_limit=3G tests/app/bin/console doctrine:fixtures:load -n + - php -d memory_limit=2G tests/app/bin/console cache:clear --env=test + - php -d memory_limit=3G bin/phpunit --colors=never + artifacts: + expire_in: 30 min + paths: + - bin + - tests/app/vendor/ diff --git a/.gitlab/merge_request_templates/Default merge request.md b/.gitlab/merge_request_templates/Default merge request.md new file mode 100644 index 000000000..5d62f91eb --- /dev/null +++ b/.gitlab/merge_request_templates/Default merge request.md @@ -0,0 +1,24 @@ + +# Description of changes + + + + +# Issues related + + + +* ... +* ... + +# Tests + + + diff --git a/.php_cs.dist.php b/.php_cs.dist.php new file mode 100644 index 000000000..4ad5af914 --- /dev/null +++ b/.php_cs.dist.php @@ -0,0 +1,79 @@ +getFinder() + ->ignoreDotFiles(false) + ->notPath('tests/app') + ->name(['.php_cs.dist.php']); + +$rules = $config->getRules(); + +$riskyRules = [ + 'ternary_to_elvis_operator' => false, + 'php_unit_mock_short_will_return' => false, + 'php_unit_set_up_tear_down_visibility' => false, + 'php_unit_construct' => false, + 'php_unit_dedicate_assert' => false, + 'php_unit_expectation' => false, + 'php_unit_mock' => false, + 'php_unit_namespaced' => false, + 'php_unit_no_expectation_annotation' => false, + 'php_unit_test_case_static_method_calls' => false, + 'php_unit_test_annotation' => false, + // 'final_internal_class' => false, + // 'strict_param' => false, + // 'declare_strict_types' => false, + // 'strict_comparison' => false, + // 'no_unreachable_default_argument_value' => false, + // 'ereg_to_preg' => false, + // 'ordered_interfaces' => false, + // 'error_suppression' => false, + // 'non_printable_character' => false, + // 'ordered_traits' => false, + // 'no_useless_sprintf' => false, + // 'dir_constant' => false, + // 'no_alias_functions' => false, + // 'implode_call' => false, + // 'combine_nested_dirname' => false, + // 'pow_to_exponentiation' => false, + // 'comment_to_phpdoc' => false, + // 'no_unset_on_property' => false, + // 'native_constant_invocation' => false, + // 'function_to_constant' => false, + // 'is_null' => false, + // 'native_function_invocation' => false, + // 'no_trailing_whitespace_in_string' => false, + // 'array_push' => false, + // 'fopen_flag_order' => false, + // 'fopen_flags' => false, + // 'logical_operators' => false, + // 'modernize_types_casting' => false, + // 'no_homoglyph_names' => false, + // 'no_unneeded_final_method' => false, + // 'random_api_migration' => false, + // 'static_lambda' => false, + // 'set_type_to_cast' => false, + // 'string_line_ending' => false, + // 'psr_autoloading' => false, +]; + +$rules = array_merge( + $rules, + $riskyRules +); + +$rules['header_comment']['header'] = trim(file_get_contents(__DIR__ . '/resource/header.txt')); + +return $config->setRules($rules); diff --git a/CHANGELOG.md b/CHANGELOG.md index 34047e541..209bf858e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,162 @@ and this project adheres to ## Unreleased +* [main] change address format in case the country is France, in Address render box and address normalizer +* [person] add validator for accompanying period with a test on social issues (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/76) +* [activity] fix visibility for location +* [origin] fix origin: use correctly the translatable strings + * /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');` +* [person] redirect bug fixed. +* [action] add an unrelated issue within action creation. +* [origin] fix origin: use correctly the translatable strings + * /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');` +* [main] change order of civilities in civility fixtures (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] set min attr in the minimum of children field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] add marital status date in person view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] show number of children + allow set number of children to null (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] show acceptSMS option (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [person] add death information in person render box in twig and vue render boxes (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191) +* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262) + +## Test releases + +### test release 2021-12-06 + +* [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316) +* [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316) +* [person search] fix bug when using birthdate after and birthdate before +* [person search] increase pertinence when lastname begins with search pattern +* [activity/actions] Améliore la cohérence du design entre + * la page résumé d'un parcours (liste d'actions récentes et liste d'activités récentes) + * la page liste des actions + * la page liste des activités (contexte personne / contexte parcours) +* [household] field to edit wheter person is titulaire of household or not removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/322) +* [activity] create work if a work with same social action is not associated to the activity +* [visgraph] improve and fix bugs on vis-network relationship graph +* [bugfix] posting of birth- and deathdate through api fixed. +* [suggestions] improve suggestions lists +* [badge-entity] design coherency between badge-person and 3 kinds of badge-thirdparty +* [AddPersons] suggestions row are clickable, not only checkbox +* [activity] improve show/new/edit templates, fix SEE and SEE_DETAILS acl +* [activity][calendar] concerned groups items are clickable with on-the-fly modal, create specific badge for TMS column + +### Test release 2021-11-19 - bis + +* [household] do not allow to create two addresses on the same date +* [activity] handle case when there is no social action associated to social issue +* [activity] layout for issues / actions +* [activity][bugfix] in edit mode, the form will now load the social action list + + +### Test release 2021-11-29 + +* [person] suggest entities (person | thirdparty) when creating/editing the accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/119) +* [activity] add custom validation on the Activity class, based on what is required from the ActivityType (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/188) +* [main] translate multiselect messages when selecting/creating address +* [main] set the coordinates of the city when creating a new address OR choosing "pas d'adresse complète" +* Use the user.label in accompanying course banner, instead of username; +* fix: show validation message when closing accompanying course; +* [thirdparty] link from modal to thirdparty detail page fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/228) +* [assets] new asset to style suggestions lists (with add/remove item link) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/258) +* [accompanyingCourseWorkEdit] improves hyphenation and line breaks for long badges +* [acompanyingCourse] improve Resume page + * complete all needed informations, + * actions and activities are clickables, + * better placement with js masonry blocks on top of content area, + * https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/101 + * https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/295 +* [activity/calendar] on show page, concerned groups of persons table adapt itself to isVisibles options +* [activity] remove the "plus" button in activity list +* [activity] check ACL on activity list in person context +* [list for accompanying course in person] filter list using ACL +* [validation] toasts are displayed for errors when modifying accompanying course (generalization required). +* [period] only the user can enable confidentiality +* add an endpoint for checking permissions. See https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/232 +* [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties +* [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties ## Test releases +### Test release 2021-11-22 + +* [activity] delete admin_user_show in twig template because this route is not defined and should be defined +* [activity] suggest requestor, user and ressources for adding persons|user|3rdparty +* [calendar] suggest persons, professionals and invites for adding persons|3rdparty|user +* [activity] take into account the restrictions on person|thirdparties|users visibilities defined in ActivityType +* [main] Add currentLocation to the User entity + add a page for selecting this location + add in the user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/133) +* [activity] add user current location as default location for a new activity (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/133) +* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167) +* remove "search by phone configuration option": search by phone is now executed by default +* remplacer le classement par ordre alphabétique par un classement par ordre de pertinence, qui tient compte: + * de la présence d'une string avec le nom de la ville; + * de la similarité; + * du fait que la recherche commence par une partie du mot recherché +* ajouter la recherche par numéro de téléphone directement dans la barre de recherche et dans le formulaire recherche avancée; +* ajouter la recherche par date de naissance directement dans la barre de recherche; +* ajouter la recherche par ville dans la recherche avancée +* ajouter un lien vers le ménage dans les résultats de recherche +* ajouter l'id du parcours dans les résultats de recherche +* ajouter le demandeur dans les résultats de recherche +* ajout d'un bouton "recherche avancée" sur la page d'accueil +* [person] create an accompanying course: add client-side validation if no origin (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/210) +* [person] fix bounds for computing current person address: the new address appears immediatly +* [docgen] create a normalizer and serializer for normalization on doc format +* [person normalization] the key center is now "centers" and is an array. Empty array if no center +* [accompanyingCourse] Ability to close accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/296) +* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167) +* [list result] show all courses, except ones with period closed +* [accompanyingCourse] improve banner with small carousel to display slide social-issues or slide associated persons (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/69) + +### Test release 2021-11-15 + +* [main] fix adding multiple AddresseDeRelais (combine PickAddressType with ChillCollection) +* [person]: do not suggest the current household of the person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/51) +* [person]: display other phone numbers in view + add message in case no others phone numbers (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/184) +* unnecessary whitespace removed from person banner after person-id + double parentheses removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/290) +* [person]: delete accompanying period work, including related objects (cascade) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/36) +* [address]: Display of incomplete address adjusted. +* [household]: improve relationship graph + * add form to create/edit/delete relationship link, + * improve graph refresh mechanism + * add feature to export canvas as image (png) +* [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern; +* [person] do not ask for center any more on person creation +* [3party] do not ask for center any more on 3party creation + +## Test releases + +### Test release 2021-11-08 + +* [person]: Display the name of a user when searching after a User (TMS) +* [person]: Add civility to the person +* [person]: Various improvements on the edit person form +* [person]: Set available_languages and available_countries as parameters for use in the edit person form +* [activity] Bugfix: documents can now be added to an activity. +* [tasks] improve tasks with filter order +* [tasks] refactor singleControllerTasks: limit the number of conditions from the context +* [validations] validation of accompanying period added: no duplicate participations or resources (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/60). +* [renderbox] If gender of person is not defined, no icon is displayed instead of neuter-icon (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/129). +* [confidential information] module added to blur confidential information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/248). +* refactor `AuthorizationHelper` and `UserACLAwareRepository` to fix constructor, and separate logic for parent role helper into `ParentRoleHelper` +* [main]: filter location and locationType in backend: exclude NULL names, only active and availableToUsers +* [activity]: perform client-side validation & show/hide fields in the "new location" modal +* [person]: normalize person with CenterResolverDispatcher and handle case where center is null or multiple in PersonRenderBox +* [docstore] voter for PersonDocument and AccompanyingCourseDocument on the 2.0 way (using VoterHelperFactory) +* [docstore] add authorization check inside controller and menu +* [activity]: fix inheritance for role `ACTIVITY FULL` and add missing acl in menu +* [person] show current address in search results +* [person] show alt names in search results +* [admin]: links to activity admin section added again. +* [household]: endDate field deleted from household edit form. +* [household]: View accompanying periods of current and old household members. +* [tasks]: different layout for task list / my tasks, and fix link to tasks in alert or in warning +* [admin]: links to activity admin section added again. +* [household]: household addresses ordered by ValidFrom date and by id to show the last created address on top. +* [socialWorkAction]: display of social issue and parent issues + banner context added. +* [DBAL dependencies] Upgrade to DBAL 3.1 + ### Test release 2021-10-27 * [person]: delete double actions buttons on search person page @@ -33,7 +184,10 @@ and this project adheres to * [3party]: fix address creation * [household members editor] finalisation of editor * [AccompanyingCourse banner]: replace translation referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/70) -* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location. +* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location. +* [household]: add relationship page with dynamic data visualisation graph + +## Test releases ### Test release 2021-10-11 @@ -49,7 +203,7 @@ and this project adheres to * fast creation buttons * add ordering for types -* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities; +* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities; * Improve badges behaviour with small screens; * [ThirdParty]: @@ -100,7 +254,7 @@ and this project adheres to ## Test released -scalarNode('key')->isRequired()->cannotBeEmpty() - ->info('the key stored in database') - ->example('student') - ->end() - ->arrayNode('labels')->isRequired()->requiresAtLeastOneElement() - ->arrayPrototype() - ->children() - ->scalarNode('lang')->isRequired()->cannotBeEmpty() - ->example('fr') - ->end() - ->scalarNode('label')->isRequired()->cannotBeEmpty() - ->example('Étudiant') - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() - ->arrayNode('familial_situations')->isRequired() - ->info("the list of familial situations. If empty, the field will not be shown") - ->arrayPrototype() - ->children() -->scalarNode('key')->isRequired()->cannotBeEmpty() - ->info('the key stored in database') - ->example('half_time_keeping') - ->end() - ->arrayNode('labels')->isRequired()->requiresAtLeastOneElement() - ->arrayPrototype() - ->children() - ->scalarNode('lang')->isRequired()->cannotBeEmpty() - ->example('fr') - ->end() - ->scalarNode('label')->isRequired()->cannotBeEmpty() - ->example('En garde alternée') - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() - ; + ->arrayNode('links')->isRequired()->requiresAtLeastOneElement() + ->arrayPrototype() + ->children() + ->scalarNode('key')->isRequired()->cannotBeEmpty() + ->info('the key stored in database') + ->example('grandson') + ->end() + ->arrayNode('labels')->isRequired()->requiresAtLeastOneElement() + ->arrayPrototype() + ->children() + ->scalarNode('lang')->isRequired()->cannotBeEmpty() + ->example('fr') + ->end() + ->scalarNode('label')->isRequired()->cannotBeEmpty() + ->example('Petit-fils') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('professionnal_situations')->isRequired() + ->info('the list of professional situations. If empty, the field will not be shown') + ->arrayPrototype() + ->children() + ->scalarNode('key')->isRequired()->cannotBeEmpty() + ->info('the key stored in database') + ->example('student') + ->end() + ->arrayNode('labels')->isRequired()->requiresAtLeastOneElement() + ->arrayPrototype() + ->children() + ->scalarNode('lang')->isRequired()->cannotBeEmpty() + ->example('fr') + ->end() + ->scalarNode('label')->isRequired()->cannotBeEmpty() + ->example('Étudiant') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('familial_situations')->isRequired() + ->info('the list of familial situations. If empty, the field will not be shown') + ->arrayPrototype() + ->children() + ->scalarNode('key')->isRequired()->cannotBeEmpty() + ->info('the key stored in database') + ->example('half_time_keeping') + ->end() + ->arrayNode('labels')->isRequired()->requiresAtLeastOneElement() + ->arrayPrototype() + ->children() + ->scalarNode('lang')->isRequired()->cannotBeEmpty() + ->example('fr') + ->end() + ->scalarNode('label')->isRequired()->cannotBeEmpty() + ->example('En garde alternée') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end(); return $treeBuilder; } diff --git a/src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php b/src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php index 36da186d0..ac8743af2 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php +++ b/src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php @@ -1,53 +1,62 @@ + * @ORM\MappedSuperclass */ abstract class AbstractFamilyMember implements HasCenterInterface { - const FAMILIAL_SITUATION = [ - 'a_charge'=> 'A charge', - 'garde_alternee'=> 'Garde alternée', - 'droit_de_visite'=> 'Droit de visite et d\'hébergement', - 'non_a_charge' => 'Non à charge', - ]; + public const FAMILIAL_SITUATION = [ + 'a_charge' => 'A charge', + 'garde_alternee' => 'Garde alternée', + 'droit_de_visite' => 'Droit de visite et d\'hébergement', + 'non_a_charge' => 'Non à charge', + ]; -/** - * - * @var Person - * @ORM\ManyToOne( - * targetEntity="\Chill\PersonBundle\Entity\Person" - * ) - */ - private $person; - /** + * @var date_immutable|null * - * @var MaritalStatus - * @ORM\ManyToOne( - * targetEntity="\Chill\PersonBundle\Entity\MaritalStatus" + * @ORM\Column(name="birthdate", type="date_immutable", nullable=true) + * @Assert\Date + */ + private $birthdate; + + /** + * @var DateTimeImmutable|null + * + * @ORM\Column(name="endDate", type="datetime_immutable", nullable=true) + * @Assert\Date + * @Assert\GreaterThan( + * propertyPath="startDate", + * message="The membership's end date should be after the start date" * ) */ - private $maritalStatus; + private $endDate; /** * @var string - * - * @ORM\Column(name="lastname", type="string", length=255) + * @ORM\Column(name="familial_situation", type="string", length=200, nullable=true) */ - private $lastname = ''; + private $familialSituation; /** * @var string @@ -64,12 +73,34 @@ abstract class AbstractFamilyMember implements HasCenterInterface private $gender = ''; /** - * @var date_immutable|null + * @var string * - * @ORM\Column(name="birthdate", type="date_immutable", nullable=true) - * @Assert\Date() + * @ORM\Column(name="lastname", type="string", length=255) */ - private $birthdate; + private $lastname = ''; + + /** + * @var string + * + * @ORM\Column(name="link", type="string", length=255) + */ + private $link = ''; + + /** + * @var MaritalStatus + * @ORM\ManyToOne( + * targetEntity="\Chill\PersonBundle\Entity\MaritalStatus" + * ) + */ + private $maritalStatus; + + /** + * @var Person + * @ORM\ManyToOne( + * targetEntity="\Chill\PersonBundle\Entity\Person" + * ) + */ + private $person; /** * @var string @@ -79,57 +110,67 @@ abstract class AbstractFamilyMember implements HasCenterInterface private $professionnalSituation = ''; /** - * @var string - * - * @ORM\Column(name="link", type="string", length=255) - */ - private $link = ''; - - /** - * - * @var string - * @ORM\Column(name="familial_situation", type="string", length=200, nullable=true) - */ - private $familialSituation; - - /** - * @var \DateTimeImmutable + * @var DateTimeImmutable * * @ORM\Column(name="startDate", type="datetime_immutable") - * @Assert\NotNull() - * @Assert\Date() + * @Assert\NotNull + * @Assert\Date */ private $startDate; - /** - * @var \DateTimeImmutable|null - * - * @ORM\Column(name="endDate", type="datetime_immutable", nullable=true) - * @Assert\Date() - * @Assert\GreaterThan( - * propertyPath="startDate", - * message="The membership's end date should be after the start date" - * ) - */ - private $endDate; - public function __construct() { - $this->setStartDate(new \DateTimeImmutable('now')); + $this->setStartDate(new DateTimeImmutable('now')); } - - /** - * Set lastname. - * - * @param string $lastname - * - * @return FamilyMember - */ - public function setLastname($lastname) - { - $this->lastname = (string) $lastname; - return $this; + /** + * Get birthdate. + * + * @return date_immutable|null + */ + public function getBirthdate() + { + return $this->birthdate; + } + + public function getCenter(): \Chill\MainBundle\Entity\Center + { + return $this->getPerson()->getCenter(); + } + + /** + * Get endDate. + * + * @return DateTimeImmutable|null + */ + public function getEndDate() + { + return $this->endDate; + } + + public function getFamilialSituation() + { + return $this->familialSituation; + } + + /** + * Get firstname. + * + * @return string + */ + public function getFirstname() + { + return $this->firstname; + } + + /** + * Get gender. + * + * @return string + */ + public function getGender() + { + return $this->gender; } /** @@ -142,6 +183,91 @@ abstract class AbstractFamilyMember implements HasCenterInterface return $this->lastname; } + /** + * Get link. + * + * @return string + */ + public function getLink() + { + return $this->link; + } + + /** + * @return MaritalStatus + */ + public function getMaritalStatus() + { + return $this->maritalStatus; + } + + /** + * @return Person + */ + public function getPerson() + { + return $this->person; + } + + /** + * Get professionnalSituation. + * + * @return string + */ + public function getProfessionnalSituation() + { + return $this->professionnalSituation; + } + + /** + * Get startDate. + * + * @return DateTimeImmutable + */ + public function getStartDate() + { + return $this->startDate; + } + + /** + * Set birthdate. + * + * @return FamilyMember + */ + public function setBirthdate(?DateTimeInterface $birthdate = null) + { + if ($birthdate instanceof DateTime) { + $this->birthdate = DateTimeImmutable::createFromMutable($birthdate); + } elseif (null === $birthdate || $birthdate instanceof DateTimeImmutable) { + $this->birthdate = $birthdate; + } + + return $this; + } + + /** + * Set endDate. + * + * @return FamilyMember + */ + public function setEndDate(?DateTimeInterface $endDate = null) + { + if ($endDate instanceof DateTime) { + $this->endDate = DateTimeImmutable::createFromMutable($endDate); + } elseif ($endDate instanceof DateTimeImmutable || null === $endDate) { + $this->endDate = $endDate; + } + + return $this; + } + + public function setFamilialSituation($familialSituation) + { + $this->familialSituation = $familialSituation; + + return $this; + } + /** * Set firstname. * @@ -156,16 +282,6 @@ abstract class AbstractFamilyMember implements HasCenterInterface return $this; } - /** - * Get firstname. - * - * @return string - */ - public function getFirstname() - { - return $this->firstname; - } - /** * Set gender. * @@ -181,67 +297,19 @@ abstract class AbstractFamilyMember implements HasCenterInterface } /** - * Get gender. + * Set lastname. * - * @return string - */ - public function getGender() - { - return $this->gender; - } - - /** - * Set birthdate. - * - * @param \DateTimeInterface|null $birthdate + * @param string $lastname * * @return FamilyMember */ - public function setBirthdate(\DateTimeInterface $birthdate = null) + public function setLastname($lastname) { - if ($birthdate instanceof \DateTime) { - $this->birthdate = \DateTimeImmutable::createFromMutable($birthdate); - } elseif ($birthdate === null || $birthdate instanceof \DateTimeImmutable) { - $this->birthdate = $birthdate; - } + $this->lastname = (string) $lastname; return $this; } - /** - * Get birthdate. - * - * @return date_immutable|null - */ - public function getBirthdate() - { - return $this->birthdate; - } - - /** - * Set professionnalSituation. - * - * @param string $professionnalSituation - * - * @return FamilyMember - */ - public function setProfessionnalSituation($professionnalSituation) - { - $this->professionnalSituation = (string) $professionnalSituation; - - return $this; - } - - /** - * Get professionnalSituation. - * - * @return string - */ - public function getProfessionnalSituation() - { - return $this->professionnalSituation; - } - /** * Set link. * @@ -257,127 +325,56 @@ abstract class AbstractFamilyMember implements HasCenterInterface } /** - * Get link. + * @param MaritalStatus $maritalStatus * - * @return string + * @return $this */ - public function getLink() + public function setMaritalStatus(?MaritalStatus $maritalStatus = null) { - return $this->link; - } - - public function getFamilialSituation() - { - return $this->familialSituation; - } - - public function setFamilialSituation($familialSituation) - { - $this->familialSituation = $familialSituation; - - return $this; - } - - /** - * Set startDate. - * - * @param \DateTimeImmutable $startDate - * - * @return FamilyMember - */ - public function setStartDate(\DateTimeInterface $startDate) - { - if ($startDate instanceof \DateTime) { - $this->startDate = \DateTimeImmutable::createFromMutable($startDate); - } elseif (NULL === $startDate || $startDate instanceof \DateTimeImmutable) { - $this->startDate = $startDate; - } + $this->maritalStatus = $maritalStatus; return $this; } /** - * Get startDate. - * - * @return \DateTimeImmutable - */ - public function getStartDate() - { - return $this->startDate; - } - - /** - * Set endDate. - * - * @param \DateTimeInterface|null $endDate - * - * @return FamilyMember - */ - public function setEndDate(\DateTimeInterface $endDate = null) - { - if ($endDate instanceof \DateTime) { - $this->endDate = \DateTimeImmutable::createFromMutable($endDate); - } elseif ($endDate instanceof \DateTimeImmutable || $endDate === null) { - $this->endDate = $endDate; - } - - return $this; - } - - /** - * Get endDate. - * - * @return \DateTimeImmutable|null - */ - public function getEndDate() - { - return $this->endDate; - } - - /** - * - * @return Person - */ - public function getPerson() - { - return $this->person; - } - - /** - * - * @return MaritalStatus - */ - public function getMaritalStatus() - { - return $this->maritalStatus; - } - - /** - * - * @param Person $person * @return $this */ public function setPerson(Person $person) { $this->person = $person; - + return $this; } /** - * - * @param MaritalStatus $maritalStatus - * @return $this + * Set professionnalSituation. + * + * @param string $professionnalSituation + * + * @return FamilyMember */ - public function setMaritalStatus(MaritalStatus $maritalStatus = null) + public function setProfessionnalSituation($professionnalSituation) { - $this->maritalStatus = $maritalStatus; - + $this->professionnalSituation = (string) $professionnalSituation; + return $this; } - public function getCenter(): \Chill\MainBundle\Entity\Center + /** + * Set startDate. + * + * @param DateTimeImmutable $startDate + * + * @return FamilyMember + */ + public function setStartDate(DateTimeInterface $startDate) { - return $this->getPerson()->getCenter(); + if ($startDate instanceof DateTime) { + $this->startDate = DateTimeImmutable::createFromMutable($startDate); + } elseif (null === $startDate || $startDate instanceof DateTimeImmutable) { + $this->startDate = $startDate; + } + + return $this; } } diff --git a/src/Bundle/ChillFamilyMembersBundle/Entity/FamilyMember.php b/src/Bundle/ChillFamilyMembersBundle/Entity/FamilyMember.php index ca2d4af92..9e679454b 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Entity/FamilyMember.php +++ b/src/Bundle/ChillFamilyMembersBundle/Entity/FamilyMember.php @@ -1,11 +1,20 @@ configRepository = $configRepository; $this->translatableStringHelper = $translatableStringHelper; } - - /** - * {@inheritdoc} - */ public function buildForm(FormBuilderInterface $builder, array $options) { - $builder ->add('lastname', TextType::class, [ - 'label' => 'Last name' + 'label' => 'Last name', ]) ->add('firstname', TextType::class, [ - 'label' => 'First name' + 'label' => 'First name', ]) ->add('gender', GenderType::class) ->add('birthdate', ChillDateType::class, [ - 'required' => false + 'required' => false, ]) ->add('link', ChoiceType::class, [ 'choices' => $this->buildChoices($this->configRepository->getLinksLabels()), 'placeholder' => 'Choose a link', - 'label' => 'Relationship' + 'label' => 'Relationship', ]) ->add('maritalStatus', Select2MaritalStatusType::class, [ - 'required' => false - ]) - ; - + 'required' => false, + ]); + if ($this->configRepository->hasProfessionalSituation()) { $builder - ->add('professionnalSituation', ChoiceType::class, [ - 'required' => false, - 'choices' => $this->buildChoices( - $this->configRepository->getProfessionalSituationsLabels() - ) - ]); + ->add('professionnalSituation', ChoiceType::class, [ + 'required' => false, + 'choices' => $this->buildChoices( + $this->configRepository->getProfessionalSituationsLabels() + ), + ]); } - + if ($this->configRepository->hasProfessionalSituation()) { $builder - ->add('familialSituation', ChoiceType::class, [ - 'required' => false, - 'choices' => $this->buildChoices( - $this->configRepository->getFamilialSituationsLabels() - ) - ]); + ->add('familialSituation', ChoiceType::class, [ + 'required' => false, + 'choices' => $this->buildChoices( + $this->configRepository->getFamilialSituationsLabels() + ), + ]); } - + if ($options['show_start_date']) { $builder ->add('startDate', ChillDateType::class, [ - 'label' => 'Arrival date in the family' + 'label' => 'Arrival date in the family', ]); } - + if ($options['show_end_date']) { $builder ->add('endDate', ChillDateType::class, [ 'required' => false, - 'label' => 'Departure date of the family' + 'label' => 'Departure date of the family', ]); } + } - }/** - * {@inheritdoc} - */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( + $resolver->setDefaults([ 'data_class' => FamilyMember::class, 'show_start_date' => true, 'show_end_date' => true, - )); - + ]); + $resolver ->setAllowedTypes('show_start_date', 'boolean') - ->setAllowedTypes('show_end_date', 'boolean') - ; - } - - private function buildChoices($els) - { - $links = $this->configRepository - ->getLinksLabels(); - $choices = []; - - // rewrite labels to filter in language - foreach ($els as $key => $labels) { - $choices[$this->translatableStringHelper->localize($labels)] = $key; - } - - return $choices; + ->setAllowedTypes('show_end_date', 'boolean'); } - /** - * {@inheritdoc} - */ public function getBlockPrefix() { return 'chill_amli_familymembersbundle_familymember'; } + private function buildChoices($els) + { + $links = $this->configRepository + ->getLinksLabels(); + $choices = []; + // rewrite labels to filter in language + foreach ($els as $key => $labels) { + $choices[$this->translatableStringHelper->localize($labels)] = $key; + } + + return $choices; + } } diff --git a/src/Bundle/ChillFamilyMembersBundle/Form/FamilyMembersType.php b/src/Bundle/ChillFamilyMembersBundle/Form/FamilyMembersType.php index b8561b633..9ea1f2fa9 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Form/FamilyMembersType.php +++ b/src/Bundle/ChillFamilyMembersBundle/Form/FamilyMembersType.php @@ -1,18 +1,20 @@ + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\AMLI\FamilyMembersBundle\Form; + +use Chill\MainBundle\Form\Type\ChillCollectionType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + class FamilyMembersType extends AbstractType { public function configureOptions(OptionsResolver $resolver) @@ -21,15 +23,15 @@ class FamilyMembersType extends AbstractType 'entry_type' => FamilyMemberType::class, 'entry_options' => [ 'show_start_date' => true, - 'show_end_date' => true + 'show_end_date' => true, ], 'allow_add' => true, 'allow_delete' => true, 'button_add_label' => 'Ajouter un membre', - 'button_remove_label' => 'Enlever ce membre' + 'button_remove_label' => 'Enlever ce membre', ]); } - + public function getParent() { return ChillCollectionType::class; diff --git a/src/Bundle/ChillFamilyMembersBundle/Menu/UserMenuBuilder.php b/src/Bundle/ChillFamilyMembersBundle/Menu/UserMenuBuilder.php index fb226cf5f..17976b6ef 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Menu/UserMenuBuilder.php +++ b/src/Bundle/ChillFamilyMembersBundle/Menu/UserMenuBuilder.php @@ -1,61 +1,61 @@ - */ class UserMenuBuilder implements LocalMenuBuilderInterface { /** - * * @var AuthorizationCheckerInterface */ protected $authorizationChecker; - + /** - * * @var TranslatorInterface */ protected $translator; - + public function __construct( - AuthorizationCheckerInterface $authorizationChecker, + AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator ) { $this->authorizationChecker = $authorizationChecker; $this->translator = $translator; } - public function buildMenu($menuId, MenuItem $menu, array $parameters) { - /* @var $person \Chill\PersonBundle\Entity\Person */ + /** @var \Chill\PersonBundle\Entity\Person $person */ $person = $parameters['person']; - + if ($this->authorizationChecker->isGranted(FamilyMemberVoter::SHOW, $person)) { $menu->addChild( - $this->translator->trans('Family memberships'), [ + $this->translator->trans('Family memberships'), + [ 'route' => 'chill_family_members_family_members_index', - 'routeParameters' => [ 'id' => $person->getId() ], - ]) - ->setExtra('order', 450) - ; + 'routeParameters' => ['id' => $person->getId()], + ] + ) + ->setExtra('order', 450); } } public static function getMenuIds(): array { - return [ 'person' ]; + return ['person']; } } diff --git a/src/Bundle/ChillFamilyMembersBundle/Repository/FamilyMemberRepository.php b/src/Bundle/ChillFamilyMembersBundle/Repository/FamilyMemberRepository.php index 618128c96..571e94161 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Repository/FamilyMemberRepository.php +++ b/src/Bundle/ChillFamilyMembersBundle/Repository/FamilyMemberRepository.php @@ -1,17 +1,39 @@ findBy([ 'person' => $person ]); + parent::__construct($registry, FamilyMember::class); + } + + /** + * @return FamilyMember[] + */ + public function findByPerson(Person $person): array + { + return $this->findBy(['person' => $person]); } } diff --git a/src/Bundle/ChillFamilyMembersBundle/Resources/migrations/Version20180522142023.php b/src/Bundle/ChillFamilyMembersBundle/Resources/migrations/Version20180522142023.php index 90ac49a32..47cf5df6a 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Resources/migrations/Version20180522142023.php +++ b/src/Bundle/ChillFamilyMembersBundle/Resources/migrations/Version20180522142023.php @@ -1,4 +1,13 @@ -abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP SCHEMA chill_family CASCADE'); + } + + public function up(Schema $schema): void { $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); @@ -25,12 +41,4 @@ final class Version20180522142023 extends AbstractMigration $this->addSql('ALTER TABLE chill_family.family_member ADD CONSTRAINT FK_A61F4A49217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_family.family_member ADD CONSTRAINT FK_A61F4A49D7D03CE3 FOREIGN KEY (maritalStatus_id) REFERENCES chill_person_marital_status (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); } - - public function down(Schema $schema) : void - { - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); - - $this->addSql('DROP SCHEMA chill_family CASCADE'); - - } } diff --git a/src/Bundle/ChillFamilyMembersBundle/Security/Voter/FamilyMemberVoter.php b/src/Bundle/ChillFamilyMembersBundle/Security/Voter/FamilyMemberVoter.php index 5e218657c..73c320720 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Security/Voter/FamilyMemberVoter.php +++ b/src/Bundle/ChillFamilyMembersBundle/Security/Voter/FamilyMemberVoter.php @@ -1,79 +1,82 @@ + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\AMLI\FamilyMembersBundle\Security\Voter; + +use Chill\AMLI\FamilyMembersBundle\Entity\FamilyMember; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Security\Authorization\AbstractChillVoter; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; +use Chill\PersonBundle\Entity\Person; +use Symfony\Component\Security\Core\Role\Role; +use function in_array; + class FamilyMemberVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface { - const CREATE = 'CHILL_FAMILY_MEMBERS_FAMILY_MEMBERS_CREATE'; - const DELETE = 'CHILL_FAMILY_MEMBERS_FAMILY_MEMBERS_DELETE'; - const UPDATE = 'CHILL_FAMILY_MEMBERS_FAMILY_MEMBERS_UPDATE'; - const SHOW = 'CHILL_FAMILY_MEMBERS_FAMILY_MEMBERS_SHOW'; - - const ROLES = [ + public const CREATE = 'CHILL_FAMILY_MEMBERS_FAMILY_MEMBERS_CREATE'; + + public const DELETE = 'CHILL_FAMILY_MEMBERS_FAMILY_MEMBERS_DELETE'; + + public const ROLES = [ self::CREATE, self::DELETE, self::SHOW, - self::UPDATE + self::UPDATE, ]; - + + public const SHOW = 'CHILL_FAMILY_MEMBERS_FAMILY_MEMBERS_SHOW'; + + public const UPDATE = 'CHILL_FAMILY_MEMBERS_FAMILY_MEMBERS_UPDATE'; + /** - * * @var AuthorizationHelper */ protected $authorizationHelper; - + public function __construct(AuthorizationHelper $authorizationHelper) { $this->authorizationHelper = $authorizationHelper; } - - protected function supports($attribute, $subject) - { - return (\in_array($attribute, self::ROLES) && $subject instanceof FamilyMember) - or - ($subject instanceof Person && \in_array($attribute, [ self::SHOW, self::CREATE ])); - } - - protected function voteOnAttribute($attribute, $subject, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token) - { - $user = $token->getUser(); - - if (FALSE === $user instanceof User) { - return false; - } - - return $this->authorizationHelper - ->userHasAccess($user, $subject, new Role($attribute)); - } - - public function getRoles() - { - return self::ROLES; - } - - public function getRolesWithHierarchy(): array - { - return [ 'Family Members' => self::ROLES ]; - } - - public function getRolesWithoutScope() + public function getRoles(): array { return self::ROLES; } + public function getRolesWithHierarchy(): array + { + return ['Family Members' => self::ROLES]; + } + + public function getRolesWithoutScope(): array + { + return self::ROLES; + } + + protected function supports($attribute, $subject) + { + return (in_array($attribute, self::ROLES, true) && $subject instanceof FamilyMember) + || ($subject instanceof Person && in_array($attribute, [self::SHOW, self::CREATE], true)); + } + + protected function voteOnAttribute($attribute, $subject, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token) + { + $user = $token->getUser(); + + if (false === $user instanceof User) { + return false; + } + + return $this->authorizationHelper + ->userHasAccess($user, $subject, new Role($attribute)); + } } diff --git a/src/Bundle/ChillFamilyMembersBundle/Templating/Twig.php b/src/Bundle/ChillFamilyMembersBundle/Templating/Twig.php index e02d7e024..c2c060b79 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Templating/Twig.php +++ b/src/Bundle/ChillFamilyMembersBundle/Templating/Twig.php @@ -1,102 +1,99 @@ - */ class Twig extends AbstractExtension { /** - * * @var ConfigRepository */ protected $configRepository; - + /** - * * @var TranslatableStringHelper */ protected $translatableStringHelper; - + public function __construct( - ConfigRepository $configRepository, + ConfigRepository $configRepository, TranslatableStringHelper $translatableStringHelper ) { $this->configRepository = $configRepository; $this->translatableStringHelper = $translatableStringHelper; } - + public function displayFamilialSituation($situation) + { + if (null === $situation) { + return null; + } + + return $this->translatableStringHelper->localize( + $this->configRepository->getFamilialSituationsLabels()[$situation] + ); + } + + public function displayLink($link) + { + if (null === $link) { + return null; + } + + return $this->translatableStringHelper->localize( + $this->configRepository->getLinksLabels()[$link] + ); + } + + public function displayProfessionalSituation($situation) + { + if (null === $situation) { + return null; + } + + return $this->translatableStringHelper->localize( + $this->configRepository->getProfessionalSituationsLabels()[$situation] + ); + } + public function getFilters() { return [ - new TwigFilter('chill_family_member_link_display', [ $this, 'displayLink' ], [ 'is_safe' => [ 'html' ]]), - new TwigFilter('chill_family_member_professional_situation_display', [ $this, 'displayProfessionalSituation' ], [ 'is_safe' => [ 'html' ]]), - new TwigFilter('chill_family_member_familial_situation_display', [ $this, 'displayFamilialSituation' ], [ 'is_safe' => [ 'html' ]]), - + new TwigFilter('chill_family_member_link_display', [$this, 'displayLink'], ['is_safe' => ['html']]), + new TwigFilter('chill_family_member_professional_situation_display', [$this, 'displayProfessionalSituation'], ['is_safe' => ['html']]), + new TwigFilter('chill_family_member_familial_situation_display', [$this, 'displayFamilialSituation'], ['is_safe' => ['html']]), ]; } - + public function getFunctions() { return [ - new TwigFunction('chill_family_members_has_professionnal_situation', [ $this, 'hasProfessionnalSituation' ]), - new TwigFunction('chill_family_members_has_familial_situation', [ $this, 'hasFamilialSituation' ]), - + new TwigFunction('chill_family_members_has_professionnal_situation', [$this, 'hasProfessionnalSituation']), + new TwigFunction('chill_family_members_has_familial_situation', [$this, 'hasFamilialSituation']), ]; } - - public function displayLink($link) - { - if (NULL === $link) { - return null; - } - - return $this->translatableStringHelper->localize( - $this->configRepository->getLinksLabels()[$link] - ); - } - - public function displayProfessionalSituation($situation) - { - if (NULL === $situation) { - return null; - } - - return $this->translatableStringHelper->localize( - $this->configRepository->getProfessionalSituationsLabels()[$situation] - ); - } - - public function hasProfessionnalSituation() - { - return $this->configRepository->hasProfessionalSituation(); - } - - public function displayFamilialSituation($situation) - { - if (NULL === $situation) { - return null; - } - - return $this->translatableStringHelper->localize( - $this->configRepository->getFamilialSituationsLabels()[$situation] - ); - } - + public function hasFamilialSituation() { return $this->configRepository->hasFamilialSituation(); } + + public function hasProfessionnalSituation() + { + return $this->configRepository->hasProfessionalSituation(); + } } diff --git a/src/Bundle/ChillFamilyMembersBundle/Tests/Controller/FamilyMemberControllerTest.php b/src/Bundle/ChillFamilyMembersBundle/Tests/Controller/FamilyMemberControllerTest.php index 01e8d52df..7a10a6158 100644 --- a/src/Bundle/ChillFamilyMembersBundle/Tests/Controller/FamilyMemberControllerTest.php +++ b/src/Bundle/ChillFamilyMembersBundle/Tests/Controller/FamilyMemberControllerTest.php @@ -1,30 +1,42 @@ request('GET', '/edit'); + } + public function testIndex() { - $client = static::createClient(); + $client = self::createClient(); $crawler = $client->request('GET', '/index'); } public function testNew() { - $client = static::createClient(); + $client = self::createClient(); $crawler = $client->request('GET', '/new'); } - - public function testEdit() - { - $client = static::createClient(); - - $crawler = $client->request('GET', '/edit'); - } - } diff --git a/src/Bundle/ChillMainBundle/CRUD/CompilerPass/CRUDControllerCompilerPass.php b/src/Bundle/ChillMainBundle/CRUD/CompilerPass/CRUDControllerCompilerPass.php index 11b3c34fd..86ed8a4fa 100644 --- a/src/Bundle/ChillMainBundle/CRUD/CompilerPass/CRUDControllerCompilerPass.php +++ b/src/Bundle/ChillMainBundle/CRUD/CompilerPass/CRUDControllerCompilerPass.php @@ -1,33 +1,21 @@ - * - * 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\MainBundle\CRUD\CompilerPass; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Reference; -use Chill\MainBundle\Routing\MenuComposer; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Alias; /** - * + * Chill is a software for social workers * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\CRUD\CompilerPass; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; + class CRUDControllerCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) @@ -45,15 +33,15 @@ class CRUDControllerCompilerPass implements CompilerPassInterface } /** - * Add a controller for each definition, and add a methodCall to inject crud configuration to controller + * Add a controller for each definition, and add a methodCall to inject crud configuration to controller. */ private function configureCrudController(ContainerBuilder $container, array $crudEntry, string $apiOrCrud): void { $controllerClass = $crudEntry['controller']; - $controllerServiceName = 'cs'.$apiOrCrud.'_'.$crudEntry['name'].'_controller'; + $controllerServiceName = 'cs' . $apiOrCrud . '_' . $crudEntry['name'] . '_controller'; // create config parameter in container - $param = 'chill_main_'.$apiOrCrud.'_config_'.$crudEntry['name']; + $param = 'chill_main_' . $apiOrCrud . '_config_' . $crudEntry['name']; $container->setParameter($param, $crudEntry); if ($container->hasDefinition($controllerClass)) { @@ -63,8 +51,7 @@ class CRUDControllerCompilerPass implements CompilerPassInterface // add the "addMethodCall" $container->getDefinition($controllerClass) - ->addMethodCall('setCrudConfig', ['%'.$param.'%']); - + ->addMethodCall('setCrudConfig', ['%' . $param . '%']); } else { $controller = new Definition($controllerClass); @@ -72,10 +59,9 @@ class CRUDControllerCompilerPass implements CompilerPassInterface $controller->setAutoconfigured(true); $controller->setPublic(true); - $controller->addMethodCall('setCrudConfig', ['%'.$param.'%']); + $controller->addMethodCall('setCrudConfig', ['%' . $param . '%']); $container->setDefinition($controllerServiceName, $controller); } } - } diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php index a1d28483d..cd458e222 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php @@ -1,265 +1,41 @@ getDoctrine() - ->getRepository($this->getEntityClass()) - ->find($id); - - if (NULL === $e) { - throw $this->createNotFoundException(sprintf("The object %s for id %s is not found", $this->getEntityClass(), $id)); - } - - return $e; - } - - /** - * Create an entity. - * - * @param string $action - * @param Request $request - * @return object - */ - protected function createEntity(string $action, Request $request): object - { - $type = $this->getEntityClass(); - - return new $type; - } - - /** - * Count the number of entities - * - * By default, count all entities. You can customize the query by - * using the method `customizeQuery`. - * - * @param string $action - * @param Request $request - * @return int - */ - protected function countEntities(string $action, Request $request, $_format): int - { - return $this->buildQueryEntities($action, $request) - ->select('COUNT(e)') - ->getQuery() - ->getSingleScalarResult() - ; - } - - /** - * Query the entity. - * - * By default, get all entities. You can customize the query by using the - * method `customizeQuery`. - * - * The method `orderEntity` is called internally to order entities. - * - * It returns, by default, a query builder. - * - */ - protected function queryEntities(string $action, Request $request, string $_format, PaginatorInterface $paginator) - { - $query = $this->buildQueryEntities($action, $request) - ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) - ->setMaxResults($paginator->getItemsPerPage()); - - // allow to order queries and return the new query - return $this->orderQuery($action, $query, $request, $paginator, $_format); - } - - /** - * Add ordering fields in the query build by self::queryEntities - * - */ - protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format) - { - return $query; - } - - /** - * Build the base query for listing all entities. - * - * This method is used internally by `countEntities` `queryEntities` - * - * This base query does not contains any `WHERE` or `SELECT` clauses. You - * can add some by using the method `customizeQuery`. - * - * The alias for the entity is "e". - * - * @param string $action - * @param Request $request - * @return QueryBuilder - */ - protected function buildQueryEntities(string $action, Request $request) - { - $qb = $this->getDoctrine()->getManager() - ->createQueryBuilder() - ->select('e') - ->from($this->getEntityClass(), 'e') - ; - - $this->customizeQuery($action, $request, $qb); - - return $qb; - } - - protected function customizeQuery(string $action, Request $request, $query): void {} - - /** - * Get the result of the query - */ - protected function getQueryResult(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query) - { - return $query->getQuery()->getResult(); - } - - protected function onPreIndex(string $action, Request $request, string $_format): ?Response - { - return null; - } - - /** - * method used by indexAction - */ - protected function onPreIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator): ?Response - { - return null; - } - - /** - * method used by indexAction - */ - protected function onPostIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query): ?Response - { - return null; - } - - /** - * method used by indexAction - */ - protected function onPostIndexFetchQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $entities): ?Response - { - return null; - } - - - /** - * Get the complete FQDN of the class - * - * @return string the complete fqdn of the class - */ - protected function getEntityClass(): string - { - return $this->crudConfig['class']; - } - - /** - * called on post fetch entity - */ - protected function onPostFetchEntity(string $action, Request $request, $entity, $_format): ?Response - { - return null; - } - - /** - * Called on post check ACL - */ - protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response - { - return null; - } - - /** - * check the acl. Called by every action. - * - * By default, check the role given by `getRoleFor` for the value given in - * $entity. - * - * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException - * if not accessible. - * - * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException - */ - protected function checkACL(string $action, Request $request, string $_format, $entity = null) - { - $this->denyAccessUnlessGranted($this->getRoleFor($action, $request, $entity, $_format), $entity); - } - - /** - * - * @return string the crud name - */ - protected function getCrudName(): string - { - return $this->crudConfig['name']; - } - - protected function getActionConfig(string $action) - { - return $this->crudConfig['actions'][$action]; - } - - /** - * Set the crud configuration - * - * Used by the container to inject configuration for this crud. - */ - public function setCrudConfig(array $config): void - { - $this->crudConfig = $config; - } - - /** - * @return PaginatorFactory - */ - protected function getPaginatorFactory(): PaginatorFactory - { - return $this->container->get('chill_main.paginator_factory'); - } - - protected function getValidator(): ValidatorInterface - { - return $this->get('validator'); - } - - /** - * @return array - */ public static function getSubscribedServices(): array { - return \array_merge( + return array_merge( parent::getSubscribedServices(), [ 'chill_main.paginator_factory' => PaginatorFactory::class, @@ -273,4 +49,233 @@ class AbstractCRUDController extends AbstractController ] ); } + + /** + * Set the crud configuration. + * + * Used by the container to inject configuration for this crud. + */ + public function setCrudConfig(array $config): void + { + $this->crudConfig = $config; + } + + /** + * Build the base query for listing all entities. + * + * This method is used internally by `countEntities` `queryEntities` + * + * This base query does not contains any `WHERE` or `SELECT` clauses. You + * can add some by using the method `customizeQuery`. + * + * The alias for the entity is "e". + * + * @return QueryBuilder + */ + protected function buildQueryEntities(string $action, Request $request) + { + $qb = $this->getDoctrine()->getManager() + ->createQueryBuilder() + ->select('e') + ->from($this->getEntityClass(), 'e'); + + $this->customizeQuery($action, $request, $qb); + + return $qb; + } + + /** + * check the acl. Called by every action. + * + * By default, check the role given by `getRoleFor` for the value given in + * $entity. + * + * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException + * if not accessible. + * + * @param mixed|null $entity + * + * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException + */ + protected function checkACL(string $action, Request $request, string $_format, $entity = null) + { + // @TODO: Implements abstract getRoleFor method or do it in the interface. + $this->denyAccessUnlessGranted($this->getRoleFor($action, $request, $entity, $_format), $entity); + } + + /** + * Count the number of entities. + * + * By default, count all entities. You can customize the query by + * using the method `customizeQuery`. + * + * @param mixed $_format + */ + protected function countEntities(string $action, Request $request, $_format): int + { + return $this->buildQueryEntities($action, $request) + ->select('COUNT(e)') + ->getQuery() + ->getSingleScalarResult(); + } + + protected function createEntity(string $action, Request $request): object + { + $class = $this->getEntityClass(); + + return new $class(); + } + + protected function customizeQuery(string $action, Request $request, $query): void + { + } + + protected function getActionConfig(string $action) + { + return $this->crudConfig['actions'][$action]; + } + + /** + * @return string The crud name. + */ + protected function getCrudName(): string + { + return $this->crudConfig['name']; + } + + /** + * get the instance of the entity with the given id. + * + * @throw Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the object is not found + * + * @param mixed $action + */ + protected function getEntity($action, string $id, Request $request): object + { + $e = $this + ->getDoctrine() + ->getRepository($this->getEntityClass()) + ->find($id); + + if (null === $e) { + throw $this->createNotFoundException(sprintf('The object %s for id %s is not found', $this->getEntityClass(), $id)); + } + + return $e; + } + + /** + * Get the FQDN of the class. + * + * @return class-string The FQDN of the class + */ + protected function getEntityClass(): string + { + return $this->crudConfig['class']; + } + + protected function getPaginatorFactory(): PaginatorFactory + { + return $this->container->get('chill_main.paginator_factory'); + } + + /** + * Get the result of the query. + * + * @param mixed $query + */ + protected function getQueryResult(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query) + { + return $query->getQuery()->getResult(); + } + + protected function getValidator(): ValidatorInterface + { + return $this->get('validator'); + } + + /** + * Called on post check ACL. + * + * @param mixed $entity + */ + protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response + { + return null; + } + + /** + * Called on post fetch entity. + * + * @param mixed $entity + * @param mixed $_format + */ + protected function onPostFetchEntity(string $action, Request $request, $entity, $_format): ?Response + { + return null; + } + + /** + * Method used by indexAction. + * + * @param mixed $query + */ + protected function onPostIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query): ?Response + { + return null; + } + + /** + * Method used by indexAction. + * + * @param mixed $entities + */ + protected function onPostIndexFetchQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $entities): ?Response + { + return null; + } + + protected function onPreIndex(string $action, Request $request, string $_format): ?Response + { + return null; + } + + /** + * Method used by indexAction. + */ + protected function onPreIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator): ?Response + { + return null; + } + + /** + * Add ordering fields in the query build by self::queryEntities. + * + * @param mixed $query + * @param mixed $_format + */ + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format) + { + return $query; + } + + /** + * Query the entity. + * + * By default, get all entities. You can customize the query by using the + * method `customizeQuery`. + * + * The method `orderEntity` is called internally to order entities. + * + * It returns, by default, a query builder. + */ + protected function queryEntities(string $action, Request $request, string $_format, PaginatorInterface $paginator) + { + $query = $this->buildQueryEntities($action, $request) + ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) + ->setMaxResults($paginator->getItemsPerPage()); + + // allow to order queries and return the new query + return $this->orderQuery($action, $query, $request, $paginator, $_format); + } } diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php index 359ff0384..15d8dbcc3 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php @@ -1,78 +1,38 @@ getEntity($action, $id, $request, $_format); - - $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format); - - if ($postFetch instanceof Response) { - return $postFetch; - } - - $response = $this->checkACL($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - $response = $this->onPostCheckACL($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - $response = $this->onBeforeSerialize($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - if ($_format === 'json') { - $context = $this->getContextForSerialization($action, $request, $_format, $entity); - - return $this->json($entity, Response::HTTP_OK, [], $context); - } else { - throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This format is not implemented"); - } - } - - public function onBeforeSerialize(string $action, Request $request, $_format, $entity): ?Response - { - return null; - } - - /** - * Base method for handling api action + * Base method for handling api action. + * + * @param mixed $id + * @param mixed $_format * * @return void */ @@ -82,163 +42,45 @@ class ApiController extends AbstractCRUDController case Request::METHOD_GET: case Request::METHOD_HEAD: return $this->entityGet('_entity', $request, $id, $_format); + case Request::METHOD_PUT: case Request::METHOD_PATCH: return $this->entityPut('_entity', $request, $id, $_format); + case Request::METHOD_POST: - return $this->entityPostAction('_entity', $request, $id, $_format); + return $this->entityPostAction('_entity', $request, $id); + case Request::METHOD_DELETE: return $this->entityDelete('_entity', $request, $id, $_format); + default: - throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented"); - } - } - public function entityPost(Request $request, $_format): Response - { - switch($request->getMethod()) { - case Request::METHOD_POST: - return $this->entityPostAction('_entity', $request, $_format); - default: - throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented"); + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This method is not implemented'); } } - protected function entityPostAction($action, Request $request, string $_format): Response - { - $entity = $this->createEntity($action, $request); - - try { - $entity = $this->deserialize($action, $request, $_format, $entity); - } catch (NotEncodableValueException $e) { - throw new BadRequestException("invalid json", 400, $e); - } - - $errors = $this->validate($action, $request, $_format, $entity); - - $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors); - if ($response instanceof Response) { - return $response; - } - - if ($errors->count() > 0) { - $response = $this->json($errors); - $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); - - return $response; - } - - $response = $this->checkACL($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - $response = $this->onPostCheckACL($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - $this->getDoctrine()->getManager()->persist($entity); - $this->getDoctrine()->getManager()->flush(); - - $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors); - if ($response instanceof Response) { - return $response; - } - $response = $this->onBeforeSerialize($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - return $this->json( - $entity, - Response::HTTP_OK, - [], - $this->getContextForSerializationPostAlter($action, $request, $_format, $entity) - ); - } - public function entityPut($action, Request $request, $id, string $_format): Response - { - $entity = $this->getEntity($action, $id, $request, $_format); - - $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format); - if ($postFetch instanceof Response) { - return $postFetch; - } - - if (NULL === $entity) { - throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found", $this->getCrudName(), $id)); - } - - $response = $this->checkACL($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - $response = $this->onPostCheckACL($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - $response = $this->onBeforeSerialize($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - try { - $entity = $this->deserialize($action, $request, $_format, $entity); - } catch (NotEncodableValueException $e) { - throw new BadRequestException("invalid json", 400, $e); - } - - $errors = $this->validate($action, $request, $_format, $entity); - - $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors); - if ($response instanceof Response) { - return $response; - } - - if ($errors->count() > 0) { - $response = $this->json($errors); - $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); - - return $response; - } - - $this->getDoctrine()->getManager()->flush(); - - $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors); - if ($response instanceof Response) { - return $response; - } - - return $this->json( - $entity, - Response::HTTP_OK, - [], - $this->getContextForSerializationPostAlter($action, $request, $_format, $entity) - ); - } public function entityDelete($action, Request $request, $id, string $_format): Response { - $entity = $this->getEntity($action, $id, $request, $_format); + $entity = $this->getEntity($action, $id, $request); - if (NULL === $entity) { - throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found", $this->getCrudName(), $id)); + if (null === $entity) { + throw $this->createNotFoundException(sprintf('The %s with id %s ' + . 'is not found', $this->getCrudName(), $id)); } $response = $this->checkACL($action, $request, $_format, $entity); + if ($response instanceof Response) { return $response; } $response = $this->onPostCheckACL($action, $request, $_format, $entity); + if ($response instanceof Response) { return $response; } $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + if ($response instanceof Response) { return $response; } @@ -246,6 +88,7 @@ class ApiController extends AbstractCRUDController $errors = $this->validate($action, $request, $_format, $entity); $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors); + if ($response instanceof Response) { return $response; } @@ -261,6 +104,7 @@ class ApiController extends AbstractCRUDController $this->getDoctrine()->getManager()->flush(); $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors); + if ($response instanceof Response) { return $response; } @@ -268,51 +112,89 @@ class ApiController extends AbstractCRUDController return $this->json(Response::HTTP_OK); } - protected function onAfterValidation(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response + public function entityPost(Request $request, $_format): Response { - return null; + switch ($request->getMethod()) { + case Request::METHOD_POST: + return $this->entityPostAction('_entity', $request, $_format); + + default: + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This method is not implemented'); + } } - - protected function onAfterFlush(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response + public function entityPut($action, Request $request, $id, string $_format): Response { - return null; - } + $entity = $this->getEntity($action, $id, $request); - protected function getValidationGroups(string $action, Request $request, string $_format, $entity): ?array - { - return null; - } + $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format); - protected function validate(string $action, Request $request, string $_format, $entity, array $more = []): ConstraintViolationListInterface - { - $validationGroups = $this->getValidationGroups($action, $request, $_format, $entity); - - return $this->getValidator()->validate($entity, null, $validationGroups); - } - - /** - * Deserialize the content of the request into the class associated with the curd - */ - protected function deserialize(string $action, Request $request, string $_format, $entity = null): object - { - $default = []; - - if (NULL !== $entity) { - $default[AbstractNormalizer::OBJECT_TO_POPULATE] = $entity; + if ($postFetch instanceof Response) { + return $postFetch; } - $context = \array_merge( - $default, - $this->getContextForSerialization($action, $request, $_format, $entity) - ); + if (null === $entity) { + throw $this->createNotFoundException(sprintf('The %s with id %s ' + . 'is not found', $this->getCrudName(), $id)); + } - return $this->getSerializer()->deserialize($request->getContent(), $this->getEntityClass(), $_format, $context); + $response = $this->checkACL($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + try { + $entity = $this->deserialize($action, $request, $_format, $entity); + } catch (NotEncodableValueException $e) { + throw new BadRequestException('invalid json', 400, $e); + } + + $errors = $this->validate($action, $request, $_format, $entity); + + $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors); + + if ($response instanceof Response) { + return $response; + } + + if ($errors->count() > 0) { + $response = $this->json($errors); + $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); + + return $response; + } + + $this->getDoctrine()->getManager()->flush(); + + $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors); + + if ($response instanceof Response) { + return $response; + } + + return $this->json( + $entity, + Response::HTTP_OK, + [], + $this->getContextForSerializationPostAlter($action, $request, $_format, $entity) + ); } - /** - * Base action for indexing entities + * Base action for indexing entities. */ public function indexApi(Request $request, string $_format) { @@ -320,80 +202,15 @@ class ApiController extends AbstractCRUDController case Request::METHOD_GET: case REQUEST::METHOD_HEAD: return $this->indexApiAction('_index', $request, $_format); + default: - throw $this->createNotFoundException("This method is not supported"); + throw $this->createNotFoundException('This method is not supported'); } } - /** - * Build an index page. - * - * Some steps may be overriden during this process of rendering. - * - * This method: - * - * 1. Launch `onPreIndex` - * x. check acl. If it does return a response instance, return it - * x. launch `onPostCheckACL`. If it does return a response instance, return it - * 1. count the items, using `countEntities` - * 2. build a paginator element from the the number of entities ; - * 3. Launch `onPreIndexQuery`. If it does return a response instance, return it - * 3. build a query, using `queryEntities` - * x. fetch the results, using `getQueryResult` - * x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it - * 4. Serialize the entities in a Collection, using `SerializeCollection` - * - * @param string $action - * @param Request $request - * @return type - */ - protected function indexApiAction($action, Request $request, $_format) + public function onBeforeSerialize(string $action, Request $request, $_format, $entity): ?Response { - $this->onPreIndex($action, $request, $_format); - - $response = $this->checkACL($action, $request, $_format); - if ($response instanceof Response) { - return $response; - } - - if (!isset($entity)) { - $entity = ''; - } - - $response = $this->onPostCheckACL($action, $request, $_format, $entity); - if ($response instanceof Response) { - return $response; - } - - $totalItems = $this->countEntities($action, $request, $_format); - $paginator = $this->getPaginatorFactory()->create($totalItems); - - $response = $this->onPreIndexBuildQuery($action, $request, $_format, $totalItems, - $paginator); - - if ($response instanceof Response) { - return $response; - } - - $query = $this->queryEntities($action, $request, $_format, $paginator); - - $response = $this->onPostIndexBuildQuery($action, $request, $_format, $totalItems, - $paginator, $query); - - if ($response instanceof Response) { - return $response; - } - - $entities = $this->getQueryResult($action, $request, $_format, $totalItems, $paginator, $query); - - $response = $this->onPostIndexFetchQuery($action, $request, $_format, $totalItems, - $paginator, $entities); - - if ($response instanceof Response) { - return $response; - } - - return $this->serializeCollection($action, $request, $_format, $paginator, $entities); + return null; } /** @@ -402,7 +219,7 @@ class ApiController extends AbstractCRUDController * This method: * * 1. Fetch the base entity (throw 404 if not found) - * 2. checkACL, + * 2. checkACL, * 3. run onPostCheckACL, return response if any, * 4. deserialize posted data into the entity given by $postedDataType, with the context in $postedDataContext * 5. run 'add+$property' for POST method, or 'remove+$property' for DELETE method @@ -410,42 +227,44 @@ class ApiController extends AbstractCRUDController * 7. run onAfterValidation * 8. if errors, return a 422 response with errors * 9. if $forcePersist === true, persist the entity - * 10. flush the data + * 10. flush the data * 11. run onAfterFlush * 12. return a 202 response for DELETE with empty body, or HTTP 200 for post with serialized posted entity * * @param string action * @param mixed id - * @param Request $request - * @param string $_format * @param string $property the name of the property. This will be used to make a `add+$property` and `remove+$property` method * @param string $postedDataType the type of the posted data (the content) * @param string $postedDataContext a context to deserialize posted data (the content) * @param bool $forcePersist force to persist the created element (only for POST request) + * @param mixed $id * @throw BadRequestException if unable to deserialize the posted data * @throw BadRequestException if the method is not POST or DELETE - * - */ + */ protected function addRemoveSomething(string $action, $id, Request $request, string $_format, string $property, string $postedDataType, array $postedDataContext = [], bool $forcePersist = false): Response { $entity = $this->getEntity($action, $id, $request); $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format); + if ($postFetch instanceof Response) { return $postFetch; } $response = $this->checkACL($action, $request, $_format, $entity); + if ($response instanceof Response) { return $response; } $response = $this->onPostCheckACL($action, $request, $_format, $entity); + if ($response instanceof Response) { return $response; } $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + if ($response instanceof Response) { return $response; } @@ -453,25 +272,30 @@ class ApiController extends AbstractCRUDController try { $postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext); } catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) { - throw new BadRequestException(sprintf("Unable to deserialize posted ". - "data: %s", $e->getMessage()), 0, $e); + throw new BadRequestException(sprintf('Unable to deserialize posted ' . + 'data: %s', $e->getMessage()), 0, $e); } switch ($request->getMethod()) { case Request::METHOD_DELETE: // oups... how to use property accessor to remove element ? - $entity->{'remove'.\ucfirst($property)}($postedData); + $entity->{'remove' . ucfirst($property)}($postedData); + break; + case Request::METHOD_POST: - $entity->{'add'.\ucfirst($property)}($postedData); + $entity->{'add' . ucfirst($property)}($postedData); + break; + default: - throw new BadRequestException("this method is not supported"); + throw new BadRequestException('this method is not supported'); } $errors = $this->validate($action, $request, $_format, $entity, [$postedData]); $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors, [$postedData]); + if ($response instanceof Response) { return $response; } @@ -487,8 +311,8 @@ class ApiController extends AbstractCRUDController $this->getDoctrine()->getManager()->flush(); - $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors, [$postedData]); + if ($response instanceof Response) { return $response; } @@ -496,19 +320,318 @@ class ApiController extends AbstractCRUDController switch ($request->getMethod()) { case Request::METHOD_DELETE: return $this->json('', Response::HTTP_OK); + case Request::METHOD_POST: return $this->json( $postedData, Response::HTTP_OK, - [], + [], $this->getContextForSerializationPostAlter($action, $request, $_format, $entity, [$postedData]) ); } + + throw new Exception('Unable to handle such request method.'); } - + /** - * Serialize collections + * Deserialize the content of the request into the class associated with the curd. * + * @param mixed|null $entity + */ + protected function deserialize(string $action, Request $request, string $_format, $entity = null): object + { + $default = []; + + if (null !== $entity) { + $default[AbstractNormalizer::OBJECT_TO_POPULATE] = $entity; + } + + $context = array_merge( + $default, + $this->getContextForSerialization($action, $request, $_format, $entity) + ); + + return $this->getSerializer()->deserialize($request->getContent(), $this->getEntityClass(), $_format, $context); + } + + /** + * The view action. + * + * Some steps may be overriden during this process of rendering: + * + * This method: + * + * 1. fetch the entity, using `getEntity` + * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, + * this response is returned. + * 2. throw an HttpNotFoundException if entity is null + * 3. check ACL using `checkACL` ; + * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * this response is returned ; + * 5. Serialize the entity and return the result. The serialization context is given by `getSerializationContext` + * + * @param mixed $id + * @param mixed $_format + */ + protected function entityGet(string $action, Request $request, $id, $_format = 'html'): Response + { + $entity = $this->getEntity($action, $id, $request); + + $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format); + + if ($postFetch instanceof Response) { + return $postFetch; + } + + $response = $this->checkACL($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + if ('json' === $_format) { + $context = $this->getContextForSerialization($action, $request, $_format, $entity); + + return $this->json($entity, Response::HTTP_OK, [], $context); + } + + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This format is not implemented'); + } + + protected function entityPostAction($action, Request $request, string $_format): Response + { + $entity = $this->createEntity($action, $request); + + try { + $entity = $this->deserialize($action, $request, $_format, $entity); + } catch (NotEncodableValueException $e) { + throw new BadRequestException('invalid json', 400, $e); + } + + $errors = $this->validate($action, $request, $_format, $entity); + + $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors); + + if ($response instanceof Response) { + return $response; + } + + if ($errors->count() > 0) { + $response = $this->json($errors); + $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); + + return $response; + } + + $response = $this->checkACL($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + $this->getDoctrine()->getManager()->persist($entity); + $this->getDoctrine()->getManager()->flush(); + + $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors); + + if ($response instanceof Response) { + return $response; + } + $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + return $this->json( + $entity, + Response::HTTP_OK, + [], + $this->getContextForSerializationPostAlter($action, $request, $_format, $entity) + ); + } + + protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array + { + switch ($request->getMethod()) { + case Request::METHOD_GET: + return ['groups' => ['read']]; + + case Request::METHOD_PUT: + case Request::METHOD_PATCH: + case Request::METHOD_POST: + return ['groups' => ['write']]; + + default: + throw new LogicException('get context for serialization is not implemented for this method'); + } + } + + /** + * Get the context for serialization post alter query (in case of + * PATCH, PUT, or POST method). + * + * This is called **after** the entity was altered. + * + * @param mixed $entity + */ + protected function getContextForSerializationPostAlter(string $action, Request $request, string $_format, $entity, array $more = []): array + { + return ['groups' => ['read']]; + } + + /** + * get the role given from the config. + * + * @param mixed $entity + * @param mixed $_format + */ + protected function getRoleFor(string $action, Request $request, $entity, $_format): string + { + $actionConfig = $this->getActionConfig($action); + + if (null !== $actionConfig['roles'][$request->getMethod()]) { + return $actionConfig['roles'][$request->getMethod()]; + } + + if ($this->crudConfig['base_role']) { + return $this->crudConfig['base_role']; + } + + throw new RuntimeException(sprintf('the config does not have any role for the ' . + 'method %s nor a global role for the whole action. Add those to your ' . + 'configuration or override the required method', $request->getMethod())); + } + + protected function getSerializer(): SerializerInterface + { + return $this->get('serializer'); + } + + protected function getValidationGroups(string $action, Request $request, string $_format, $entity): ?array + { + return null; + } + + /** + * Build an index page. + * + * Some steps may be overriden during this process of rendering. + * + * This method: + * + * 1. Launch `onPreIndex` + * x. check acl. If it does return a response instance, return it + * x. launch `onPostCheckACL`. If it does return a response instance, return it + * 1. count the items, using `countEntities` + * 2. build a paginator element from the the number of entities ; + * 3. Launch `onPreIndexQuery`. If it does return a response instance, return it + * 3. build a query, using `queryEntities` + * x. fetch the results, using `getQueryResult` + * x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it + * 4. Serialize the entities in a Collection, using `SerializeCollection` + * + * @param string $action + * @param mixed $_format + */ + protected function indexApiAction($action, Request $request, $_format) + { + $this->onPreIndex($action, $request, $_format); + + $response = $this->checkACL($action, $request, $_format); + + if ($response instanceof Response) { + return $response; + } + + $entity = ''; + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + + if ($response instanceof Response) { + return $response; + } + + $totalItems = $this->countEntities($action, $request, $_format); + $paginator = $this->getPaginatorFactory()->create($totalItems); + + $response = $this->onPreIndexBuildQuery( + $action, + $request, + $_format, + $totalItems, + $paginator + ); + + if ($response instanceof Response) { + return $response; + } + + $query = $this->queryEntities($action, $request, $_format, $paginator); + + $response = $this->onPostIndexBuildQuery( + $action, + $request, + $_format, + $totalItems, + $paginator, + $query + ); + + if ($response instanceof Response) { + return $response; + } + + $entities = $this->getQueryResult($action, $request, $_format, $totalItems, $paginator, $query); + + $response = $this->onPostIndexFetchQuery( + $action, + $request, + $_format, + $totalItems, + $paginator, + $entities + ); + + if ($response instanceof Response) { + return $response; + } + + return $this->serializeCollection($action, $request, $_format, $paginator, $entities); + } + + protected function onAfterFlush(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response + { + return null; + } + + protected function onAfterValidation(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response + { + return null; + } + + /** + * Serialize collections. + * + * @param mixed $entities */ protected function serializeCollection(string $action, Request $request, string $_format, PaginatorInterface $paginator, $entities): Response { @@ -518,56 +641,11 @@ class ApiController extends AbstractCRUDController return $this->json($model, Response::HTTP_OK, [], $context); } - - protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array + protected function validate(string $action, Request $request, string $_format, $entity, array $more = []): ConstraintViolationListInterface { - switch ($request->getMethod()) { - case Request::METHOD_GET: - return [ 'groups' => [ 'read' ]]; - case Request::METHOD_PUT: - case Request::METHOD_PATCH: - case Request::METHOD_POST: - return [ 'groups' => [ 'write' ]]; - default: - throw new \LogicException("get context for serialization is not implemented for this method"); - } - } + $validationGroups = $this->getValidationGroups($action, $request, $_format, $entity); - /** - * Get the context for serialization post alter query (in case of - * PATCH, PUT, or POST method) - * - * This is called **after** the entity was altered. - */ - protected function getContextForSerializationPostAlter(string $action, Request $request, string $_format, $entity, array $more = []): array - { - return [ 'groups' => [ 'read' ]]; - } - - /** - * get the role given from the config. - */ - protected function getRoleFor(string $action, Request $request, $entity, $_format): string - { - $actionConfig = $this->getActionConfig($action); - - if (NULL !== $actionConfig['roles'][$request->getMethod()]) { - return $actionConfig['roles'][$request->getMethod()]; - } - - if ($this->crudConfig['base_role']) { - return $this->crudConfig['base_role']; - } - - throw new \RuntimeException(sprintf("the config does not have any role for the ". - "method %s nor a global role for the whole action. Add those to your ". - "configuration or override the required method", $request->getMethod())); - - } - - protected function getSerializer(): SerializerInterface - { - return $this->get('serializer'); + return $this->getValidator()->validate($entity, null, $validationGroups); } } diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php index d3112fd97..0e2881b54 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php @@ -1,96 +1,260 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\CRUD\Controller; -use Chill\MainBundle\Templating\Listing\FilterOrderHelper; -use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface; -use Doctrine\ORM\QueryBuilder; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Form\FormInterface; -use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Component\Security\Core\Role\Role; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Chill\MainBundle\CRUD\Resolver\Resolver; use Chill\MainBundle\CRUD\Form\CRUDDeleteEntityForm; +use Chill\MainBundle\CRUD\Resolver\Resolver; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface; +use Doctrine\ORM\QueryBuilder; +use LogicException; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Translation\TranslatorInterface; +use function array_key_exists; +use function array_merge; -/** - * Class CRUDController - * - * @package Chill\MainBundle\CRUD\Controller - */ class CRUDController extends AbstractController { - /** - * The crud configuration + * The crud configuration. * * This configuration si defined by `chill_main['crud']`. - * - * @var array */ - protected $crudConfig; + protected array $crudConfig; - /** - * @param array $config - */ - public function setCrudConfig(array $config) - { - $this->crudConfig = $config; - } - - /** - * @param $parameter - * @return Response - */ - public function CRUD($parameter) + public function CRUD(?string $parameter): Response { return new Response($parameter); } /** - * @param Request $request * @param $id - * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ - public function delete(Request $request, $id) + public function delete(Request $request, $id): Response { return $this->deleteAction('delete', $request, $id); } /** + * BAse method for edit action. + * + * IMplemented by the method formEditAction, with action as 'edit' + * + * @param mixed $id + */ + public function edit(Request $request, $id): Response + { + return $this->formEditAction('edit', $request, $id); + } + + /** + * Get the context for the serialization. + * + * @param mixed $entity + */ + public function getContextForSerialization(string $action, Request $request, $entity, string $_format): array + { + return []; + } + + public static function getSubscribedServices(): array + { + return array_merge( + parent::getSubscribedServices(), + [ + 'chill_main.paginator_factory' => PaginatorFactory::class, + 'translator' => TranslatorInterface::class, + AuthorizationHelper::class => AuthorizationHelper::class, + EventDispatcherInterface::class => EventDispatcherInterface::class, + Resolver::class => Resolver::class, + SerializerInterface::class => SerializerInterface::class, + FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class, + ] + ); + } + + /** + * Base method called by index action. + * + * @return type + */ + public function index(Request $request) + { + return $this->indexEntityAction('index', $request); + } + + /** + * Base method for new action. + * + * Implemented by the method formNewAction, with action as 'new' + */ + public function new(Request $request): Response + { + return $this->formCreateAction('new', $request); + } + + public function setCrudConfig(array $config): void + { + $this->crudConfig = $config; + } + + /** + * Base method for the view action. + * + * Implemented by the method viewAction, with action as 'view' + * + * @param mixed $id + */ + public function view(Request $request, $id): Response + { + return $this->viewAction('view', $request, $id); + } + + /** + * build a default role name, using the crud resolver. + * + * This method should not be overriden. Override `getRoleFor` instead. + * * @param string $action - * @param Request $request + * + * @return string + */ + protected function buildDefaultRole($action) + { + return $this->getCrudResolver()->buildDefaultRole( + $this->getCrudName(), + $action + ); + } + + protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper + { + return null; + } + + /** + * Build the base query for listing all entities, normally use in a listing + * page. + * + * This base query does not contains any `WHERE` or `SELECT` clauses. Those + * are added by other methods, like `queryEntities` and `countQueries`. + * + * @return QueryBuilder + */ + protected function buildQueryEntities(string $action, Request $request) + { + $query = $this + ->getDoctrine() + ->getManager() + ->createQueryBuilder() + ->select('e') + ->from($this->getEntityClass(), 'e'); + + $this->customizeQuery($action, $request, $query); + + return $query; + } + + /** + * check the acl. Called by every action. + * + * By default, check the role given by `getRoleFor` for the value given in + * $entity. + * + * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException + * if not accessible. + * + * @param mixed $entity + * + * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException + */ + protected function checkACL(string $action, $entity) + { + $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity); + } + + /** + * Count the number of entities. + */ + protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int + { + return $this->buildQueryEntities($action, $request) + ->select('COUNT(e)') + ->getQuery() + ->getSingleScalarResult(); + } + + /** + * Create an entity. + */ + protected function createEntity(string $action, Request $request): object + { + $type = $this->getEntityClass(); + + return new $type(); + } + + /** + * Create a form. + * + * use the method `getFormClassFor` + * + * A hook is available: `customizeForm` allow you to customize the form + * if needed. + * + * It is preferable to override customizeForm instead of overriding + * this method. + * + * @param mixed $entity + * @param string $formClass + */ + protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface + { + $formClass = $formClass ?? $this->getFormClassFor($action); + + $form = $this->createForm($formClass, $entity, $formOptions); + + $this->customizeForm($action, $form); + + return $form; + } + + /** + * Customize the form created by createFormFor. + */ + protected function customizeForm(string $action, FormInterface $form) + { + } + + protected function customizeQuery(string $action, Request $request, $query): void + { + } + + /** * @param $id * @param null $formClass - * @return null|\Symfony\Component\HttpFoundation\RedirectResponse|Response|void */ - protected function deleteAction(string $action, Request $request, $id, $formClass = null) + protected function deleteAction(string $action, Request $request, $id, $formClass = null): Response { - $this->onPreDelete($action, $request, $id); + $this->onPreDelete($action, $request); $entity = $this->getEntity($action, $id, $request); @@ -100,17 +264,24 @@ class CRUDController extends AbstractController return $postFetch; } - if (NULL === $entity) { - throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found"), $this->getCrudName(), $id); + if (null === $entity) { + throw $this->createNotFoundException( + sprintf( + 'The %s with id %s is not found', + $this->getCrudName(), + $id + ) + ); } $response = $this->checkACL($action, $entity); + if ($response instanceof Response) { return $response; } $response = $this->onPostCheckACL($action, $request, $entity); + if ($response instanceof Response) { return $response; } @@ -139,515 +310,39 @@ class CRUDController extends AbstractController return $result; } - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]); + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_view', ['id' => $entity->getId()]); + } - } elseif ($form->isSubmitted()) { + if ($form->isSubmitted()) { $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } $defaultTemplateParameters = [ 'form' => $form->createView(), 'entity' => $entity, - 'crud_name' => $this->getCrudName() + 'crud_name' => $this->getCrudName(), ]; return $this->render( $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) - ); + ); } /** - * @param string $action - * @param Request $request - */ - protected function onPreDelete(string $action, Request $request) {} - - /** - * @param string $action - * @param $entity - * @param FormInterface $form - * @param Request $request - */ - protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request) {} - - /** - * @param string $action - * @param $entity - * @param FormInterface $form - * @param Request $request - */ - protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request) {} - - /** - * @param string $action - * @param $entity - * @param FormInterface $form - * @param Request $request - */ - protected function removeEntity(string $action, $entity, FormInterface $form, Request $request) - { - $this->getDoctrine() - ->getManager() - ->remove($entity); - } - - /** - * Base method called by index action. + * Duplicate an entity. * - * @param Request $request - * @return type - */ - public function index(Request $request) - { - return $this->indexEntityAction('index', $request); - } - - /** - * Build an index page. - * - * Some steps may be overriden during this process of rendering. - * - * This method: - * - * 1. Launch `onPreIndex` - * 2. check acl. If it does return a response instance, return it - * 3. launch `onPostCheckACL`. If it does return a response instance, return it - * 4. count the items, using `countEntities` - * 5. build a paginator element from the the number of entities ; - * 6. Launch `onPreIndexQuery`. If it does return a response instance, return it - * 7. fetch the results, using `getQueryResult` - * - * Internally, this build a query, using `queryEntities` - * - * 8. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it - * 9. create default parameters: - * - * The default parameters are: - * - * * entities: the list en entities ; - * * crud_name: the name of the crud ; - * * paginator: a paginator element ; - * 10. Launch rendering, the parameter is fetch using `getTemplateFor` - * The parameters may be personnalized using `generateTemplateParameter`. - * - * @param string $action - * @param Request $request - * @return type - */ - protected function indexEntityAction($action, Request $request) - { - $this->onPreIndex($action, $request); - - $response = $this->checkACL($action, null); - if ($response instanceof Response) { - return $response; - } - - if (!isset($entity)) { - $entity = ''; - } - - $response = $this->onPostCheckACL($action, $request, $entity); - if ($response instanceof Response) { - return $response; - } - - $filterOrder = $this->buildFilterOrderHelper($action, $request); - $totalItems = $this->countEntities($action, $request, $filterOrder); - $paginator = $this->getPaginatorFactory()->create($totalItems); - - $response = $this->onPreIndexBuildQuery($action, $request, $totalItems, - $paginator); - - if ($response instanceof Response) { - return $response; - } - - $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder); - - $response = $this->onPostIndexFetchQuery($action, $request, $totalItems, - $paginator, $entities); - - if ($response instanceof Response) { - return $response; - } - - $defaultTemplateParameters = [ - 'entities' => $entities, - 'crud_name' => $this->getCrudName(), - 'paginator' => $paginator, - 'filter_order' => $filterOrder - ]; - - return $this->render( - $this->getTemplateFor($action, $entities, $request), - $this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters) - ); - } - - protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper - { - return null; - } - - /** - * @param string $action - * @param Request $request - */ - protected function onPreIndex(string $action, Request $request) { } - - /** - * method used by indexAction - * - * @param string $action - * @param Request $request - * @param int $totalItems - * @param PaginatorInterface $paginator - */ - protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) { } - - /** - * method used by indexAction - * - * @param string $action - * @param Request $request - * @param int $totalItems - * @param PaginatorInterface $paginator - * @param mixed $query - */ - protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) { } - - /** - * method used by indexAction - * - * @param string $action - * @param Request $request - * @param int $totalItems - * @param PaginatorInterface $paginator - * @param mixed $entities - */ - protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $entities) { } - - /** - * Build the base query for listing all entities, normally use in a listing - * page. - * - * This base query does not contains any `WHERE` or `SELECT` clauses. Those - * are added by other methods, like `queryEntities` and `countQueries`. - * - * @param string $action - * @param Request $request - * @return QueryBuilder - */ - protected function buildQueryEntities(string $action, Request $request) - { - $query = $this->getDoctrine()->getManager() - ->createQueryBuilder() - ->select('e') - ->from($this->getEntityClass(), 'e') - ; - - $this->customizeQuery($action, $request, $query); - - return $query; - } - - protected function customizeQuery(string $action, Request $request, $query): void {} - - /** - * Query the entity. - * - * By default, get all entities. - * - * The method `orderEntity` is called internally to order entities. - * - * It returns, by default, a query builder. - * - * @param string $action - * @param Request $request - * @param PaginatorInterface $paginator - * @return type - */ - protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null) - { - $query = $this->buildQueryEntities($action, $request, $filterOrder) - ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) - ->setMaxResults($paginator->getItemsPerPage()); - - // allow to order queries and return the new query - return $this->orderQuery($action, $query, $request, $paginator); - } - - - /** - * Add ordering fields in the query build by self::queryEntities - * - * @param string $action - * @param QueryBuilder|mixed $query by default, an instance of QueryBuilder - * @param Request $request - * @param PaginatorInterface $paginator - * @return QueryBuilder|mixed - */ - protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) - { - return $query; - } - - /** - * Get the result of the query - * - * @param string $action - * @param Request $request - * @param int $totalItems - * @param PaginatorInterface $paginator * @return mixed */ - protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, - ?FilterOrderHelper $filterOrder = null) + protected function duplicateEntity(string $action, Request $request) { - $query = $this->queryEntities($action, $request, $paginator, $filterOrder); + $id = $request->query->get('duplicate_id', 0); + $originalEntity = $this->getEntity($action, $id, $request); - return $query->getQuery()->getResult(); - } + $this->getDoctrine()->getManager() + ->detach($originalEntity); - /** - * Count the number of entities - * - * @param string $action - * @param Request $request - * @return int - */ - protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int - { - return $this->buildQueryEntities($action, $request, $filterOrder) - ->select('COUNT(e)') - ->getQuery() - ->getSingleScalarResult() - ; - } - - /** - * BAse method for edit action - * - * IMplemented by the method formEditAction, with action as 'edit' - * - * @param Request $request - * @param mixed $id - * @return Response - */ - public function edit(Request $request, $id): Response - { - return $this->formEditAction('edit', $request, $id); - } - - /** - * Base method for new action - * - * Implemented by the method formNewAction, with action as 'new' - * - * @param Request $request - * @return Response - */ - public function new(Request $request): Response - { - return $this->formCreateAction('new', $request); - } - - /** - * Base method for the view action. - * - * Implemented by the method viewAction, with action as 'view' - * - * @param Request $request - * @param mixed $id - * @return Response - */ - public function view(Request $request, $id): Response - { - return $this->viewAction('view', $request, $id); - } - - /** - * The view action. - * - * Some steps may be overriden during this process of rendering: - * - * This method: - * - * 1. fetch the entity, using `getEntity` - * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, - * this response is returned. - * 2. throw an HttpNotFoundException if entity is null - * 3. check ACL using `checkACL` ; - * 4. launch `onPostCheckACL`. If the result is an instance of Response, - * this response is returned ; - * 5. generate default template parameters: - * - * * `entity`: the fetched entity ; - * * `crud_name`: the crud name - * 6. Launch rendering, the parameter is fetch using `getTemplateFor` - * The parameters may be personnalized using `generateTemplateParameter`. - * - * @param string $action - * @param Request $request - * @param mixed $id - * @return Response - */ - protected function viewAction(string $action, Request $request, $id, $_format = 'html'): Response - { - $entity = $this->getEntity($action, $id, $request); - - $postFetch = $this->onPostFetchEntity($action, $request, $entity); - - if ($postFetch instanceof Response) { - return $postFetch; - } - - if (NULL === $entity) { - throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found", $this->getCrudName(), $id)); - } - - $response = $this->checkACL($action, $entity); - if ($response instanceof Response) { - return $response; - } - - $response = $this->onPostCheckACL($action, $request, $entity); - if ($response instanceof Response) { - return $response; - } - - if ($_format === 'html') { - $defaultTemplateParameters = [ - 'entity' => $entity, - 'crud_name' => $this->getCrudName() - ]; - - return $this->render( - $this->getTemplateFor($action, $entity, $request), - $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) - ); - } elseif ($_format === 'json') { - $context = $this->getContextForSerialization($action, $request, $entity, $_format); - - return $this->json($entity, Response::HTTP_OK, [], $context); - } else { - throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This format is not implemented"); - } - - } - - /** - * Get the context for the serialization - */ - public function getContextForSerialization(string $action, Request $request, $entity, string $_format): array - { - return []; - } - - - - /** - * The edit action. - * - * Some steps may be overriden during this process of rendering: - * - * This method: - * - * 1. fetch the entity, using `getEntity` - * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, - * this response is returned. - * 2. throw an HttpNotFoundException if entity is null - * 3. check ACL using `checkACL` ; - * 4. launch `onPostCheckACL`. If the result is an instance of Response, - * this response is returned ; - * 5. generate a form using `createFormFor`, and handle request on this form; - * - * If the form is valid, the entity is stored and flushed, and a redirection - * is returned. - * - * In this case, those hooks are available: - * - * * onFormValid - * * onPreFlush - * * onPostFlush - * * onBeforeRedirectAfterSubmission. If this method return an instance of - * Response, this response is returned. - * - * 5. generate default template parameters: - * - * * `entity`: the fetched entity ; - * * `crud_name`: the crud name ; - * * `form`: the formview instance. - * - * 6. Launch rendering, the parameter is fetch using `getTemplateFor` - * The parameters may be personnalized using `generateTemplateParameter`. - * - * @param string $action - * @param Request $request - * @param mixed $id - * @param string $formClass - * @param array $formOptions - * @return Response - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - protected function formEditAction(string $action, Request $request, $id, string $formClass = null, array $formOptions = []): Response - { - $entity = $this->getEntity($action, $id, $request); - - if (NULL === $entity) { - throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found", $this->getCrudName(), $id)); - } - - $response = $this->checkACL($action, $entity); - if ($response instanceof Response) { - return $response; - } - - $response = $this->onPostCheckACL($action, $request, $entity); - if ($response instanceof Response) { - return $response; - } - - $form = $this->createFormFor($action, $entity, $formClass, $formOptions); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $this->onFormValid($action, $entity, $form, $request); - $em = $this->getDoctrine()->getManager(); - - $this->onPreFlush($action, $entity, $form, $request); - $em->flush(); - $this->onPostFlush($action, $entity, $form, $request); - - $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); - - $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); - - if ($result instanceof Response) { - return $result; - } - - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'); - - } elseif ($form->isSubmitted()) { - $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); - } - - $defaultTemplateParameters = [ - 'form' => $form->createView(), - 'entity' => $entity, - 'crud_name' => $this->getCrudName() - ]; - - return $this->render( - $this->getTemplateFor($action, $entity, $request), - $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) - ); + return $originalEntity; } /** @@ -688,10 +383,7 @@ class CRUDController extends AbstractController * 6. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. * - * @param string $action - * @param Request $request * @param type $formClass - * @return Response */ protected function formCreateAction(string $action, Request $request, $formClass = null): Response { @@ -702,11 +394,13 @@ class CRUDController extends AbstractController } $response = $this->checkACL($action, $entity); + if ($response instanceof Response) { return $response; } $response = $this->onPostCheckACL($action, $request, $entity); + if ($response instanceof Response) { return $response; } @@ -735,189 +429,135 @@ class CRUDController extends AbstractController return $result; } - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]); + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_view', ['id' => $entity->getId()]); + } - } elseif ($form->isSubmitted()) { + if ($form->isSubmitted()) { $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } $defaultTemplateParameters = [ 'form' => $form->createView(), 'entity' => $entity, - 'crud_name' => $this->getCrudName() + 'crud_name' => $this->getCrudName(), ]; return $this->render( $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) - ); + ); } /** - * get the instance of the entity with the given id + * The edit action. * - * @param string $id - * @return object - */ - protected function getEntity($action, $id, Request $request): ?object - { - return $this->getDoctrine() - ->getRepository($this->getEntityClass()) - ->find($id); - } - - /** - * Duplicate an entity + * Some steps may be overriden during this process of rendering: * - * @param string $action - * @param Request $request - * @return mixed - */ - protected function duplicateEntity(string $action, Request $request) - { - $id = $request->query->get('duplicate_id', 0); - $originalEntity = $this->getEntity($action, $id, $request); - - $this->getDoctrine()->getManager() - ->detach($originalEntity); - - return $originalEntity; - } - - /** + * This method: * - * @return string the complete fqdn of the class - */ - protected function getEntityClass(): string - { - return $this->crudConfig['class']; - } - - /** + * 1. fetch the entity, using `getEntity` + * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, + * this response is returned. + * 2. throw an HttpNotFoundException if entity is null + * 3. check ACL using `checkACL` ; + * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * this response is returned ; + * 5. generate a form using `createFormFor`, and handle request on this form; * - * @return string the crud name - */ - protected function getCrudName(): string - { - return $this->crudConfig['name']; - } - - /** - * check the acl. Called by every action. + * If the form is valid, the entity is stored and flushed, and a redirection + * is returned. * - * By default, check the role given by `getRoleFor` for the value given in - * $entity. + * In this case, those hooks are available: * - * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException - * if not accessible. + * * onFormValid + * * onPreFlush + * * onPostFlush + * * onBeforeRedirectAfterSubmission. If this method return an instance of + * Response, this response is returned. * - * @param string $action - * @param mixed $entity - * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException - */ - protected function checkACL(string $action, $entity) - { - $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity); - } - - /** - * get the role given from the config. + * 5. generate default template parameters: * - * @param string $action - * @return string - */ - protected function getRoleFor($action) - { - if (\array_key_exists('role', $this->getActionConfig($action))) { - return $this->getActionConfig($action)['role']; - } - - return $this->buildDefaultRole($action); - } - - /** - * build a default role name, using the crud resolver. + * * `entity`: the fetched entity ; + * * `crud_name`: the crud name ; + * * `form`: the formview instance. * - * This method should not be overriden. Override `getRoleFor` instead. + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * The parameters may be personnalized using `generateTemplateParameter`. * - * @param string $action - * @return string - */ - protected function buildDefaultRole($action) - { - return $this->getCrudResolver()->buildDefaultRole($this->getCrudName(), - $action); - } - - /** - * get the default form class from config - * - * @param string $action - * @return string the FQDN of the form class - */ - protected function getFormClassFor($action) - { - if ($action === 'delete') { - return $this->crudConfig[$action]['form_class'] - ?? $this->getDefaultDeleteFormClass($action); - } - - return $this->crudConfig[$action]['form_class'] - ?? $this->crudConfig['form_class']; - } - - protected function getDefaultDeleteFormClass($action) - { - return CRUDDeleteEntityForm::class; - } - - /** - * Create a form - * - * use the method `getFormClassFor` - * - * A hook is available: `customizeForm` allow you to customize the form - * if needed. - * - * It is preferable to override customizeForm instead of overriding - * this method. - * - * @param string $action - * @param mixed $entity + * @param mixed $id * @param string $formClass - * @param array $formOptions - * @return FormInterface - */ - protected function createFormFor(string $action, $entity, string $formClass = null, array $formOptions = []): FormInterface - { - $formClass = $formClass ?? $this->getFormClassFor($action); - - $form = $this->createForm($formClass, $entity, $formOptions); - - $this->customizeForm($action, $form); - - return $form; - } - - /** - * Customize the form created by createFormFor. * - * @param string $action - * @param FormInterface $form + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ - protected function customizeForm(string $action, FormInterface $form) + protected function formEditAction(string $action, Request $request, $id, ?string $formClass = null, array $formOptions = []): Response { + $entity = $this->getEntity($action, $id, $request); + if (null === $entity) { + throw $this->createNotFoundException( + sprintf( + 'The %s with id %s is not found', + $this->getCrudName(), + $id + ) + ); + } + + $response = $this->checkACL($action, $entity); + + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $entity); + + if ($response instanceof Response) { + return $response; + } + + $form = $this->createFormFor($action, $entity, $formClass, $formOptions); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->onFormValid($action, $entity, $form, $request); + $em = $this->getDoctrine()->getManager(); + + $this->onPreFlush($action, $entity, $form, $request); + $em->flush(); + $this->onPostFlush($action, $entity, $form, $request); + + $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); + + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); + + if ($result instanceof Response) { + return $result; + } + + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_index'); + } + + if ($form->isSubmitted()) { + $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); + } + + $defaultTemplateParameters = [ + 'form' => $form->createView(), + 'entity' => $entity, + 'crud_name' => $this->getCrudName(), + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); } /** * Generate a message which explains an error about the form. * * Used in form actions - * - * @param string $action - * @param FormInterface $form - * @return string */ protected function generateFormErrorMessage(string $action, FormInterface $form): string { @@ -927,26 +567,31 @@ class CRUDController extends AbstractController } /** - * Generate a success message when a form could be flushed successfully + * Generate a success message when a form could be flushed successfully. * * @param string $action * @param mixed $entity - * @return string */ protected function generateFormSuccessMessage($action, $entity): string { switch ($action) { case 'edit': - $msg = "crud.edit.success"; + $msg = 'crud.edit.success'; + break; + case 'new': - $msg = "crud.new.success"; + $msg = 'crud.new.success'; + break; + case 'delete': - $msg = "crud.delete.success"; + $msg = 'crud.delete.success'; + break; + default: - $msg = "crud.default.success"; + $msg = 'crud.default.success'; } return $this->getTranslator()->trans($msg); @@ -955,10 +600,8 @@ class CRUDController extends AbstractController /** * Customize template parameters. * - * @param string $action * @param mixed $entity - * @param Request $request - * @param array $defaultTemplateParameters + * * @return array */ protected function generateTemplateParameter( @@ -971,17 +614,137 @@ class CRUDController extends AbstractController } /** - * Create an entity. + * Include services. + * + * @return mixed + */ + protected function getActionConfig(string $action) + { + return $this->crudConfig['actions'][$action]; + } + + protected function getAuthorizationHelper(): AuthorizationHelper + { + return $this->container->get(AuthorizationHelper::class); + } + + /** + * @return string the crud name + */ + protected function getCrudName(): string + { + return $this->crudConfig['name']; + } + + protected function getCrudResolver(): Resolver + { + return $this->get(Resolver::class); + } + + protected function getDefaultDeleteFormClass($action) + { + return CRUDDeleteEntityForm::class; + } + + /** + * get the instance of the entity with the given id. + * + * @param string $id + * @param mixed $action * - * @param string $action - * @param Request $request * @return object */ - protected function createEntity(string $action, Request $request): object + protected function getEntity($action, $id, Request $request): ?object { - $type = $this->getEntityClass(); + return $this->getDoctrine() + ->getRepository($this->getEntityClass()) + ->find($id); + } - return new $type; + /** + * @return string the complete fqdn of the class + */ + protected function getEntityClass(): string + { + return $this->crudConfig['class']; + } + + protected function getEventDispatcher(): EventDispatcherInterface + { + return $this->get(EventDispatcherInterface::class); + } + + protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface + { + return $this->get(FilterOrderHelperFactoryInterface::class); + } + + /** + * get the default form class from config. + * + * @param string $action + * + * @return string the FQDN of the form class + */ + protected function getFormClassFor($action) + { + if ('delete' === $action) { + return $this->crudConfig[$action]['form_class'] + ?? $this->getDefaultDeleteFormClass($action); + } + + return $this->crudConfig[$action]['form_class'] + ?? $this->crudConfig['form_class']; + } + + /** + * @todo (check how to do this with dependency injection and make changes...) + */ + protected function getPaginatorFactory(): PaginatorFactory + { + return $this->container->get('chill_main.paginator_factory'); + } + + /** + * Get the result of the query. + * + * @return mixed + */ + protected function getQueryResult( + string $action, + Request $request, + int $totalItems, + PaginatorInterface $paginator, + ?FilterOrderHelper $filterOrder = null + ) { + $query = $this->queryEntities($action, $request, $paginator, $filterOrder); + + return $query->getQuery()->getResult(); + } + + /** + * @return \Chill\MainBundle\Entity\Center[] + */ + protected function getReachableCenters(Role $role, ?Scope $scope = null) + { + return $this->getAuthorizationHelper() + ->getReachableCenters($this->getUser(), $role, $scope); + } + + /** + * get the role given from the config. + * + * @param string $action + * + * @return string + */ + protected function getRoleFor($action) + { + if (array_key_exists('role', $this->getActionConfig($action))) { + return $this->getActionConfig($action)['role']; + } + + return $this->buildDefaultRole($action); } /** @@ -993,9 +756,10 @@ class CRUDController extends AbstractController * * @param string $action * @param mixed $entity the entity for the current request, or an array of entities - * @param Request $request + * + * @throws LogicException if no template are available + * * @return string the path to the template - * @throws \LogicException if no template are available */ protected function getTemplateFor($action, $entity, Request $request) { @@ -1006,26 +770,34 @@ class CRUDController extends AbstractController switch ($action) { case 'new': return '@ChillMain/CRUD/new.html.twig'; + case 'edit': return '@ChillMain/CRUD/edit.html.twig'; + case 'index': return '@ChillMain/CRUD/index.html.twig'; + case 'view': return '@ChillMain/CRUD/view.html.twig'; + case 'delete': return '@ChillMain/CRUD/delete.html.twig'; + default: - throw new \LogicException("the view for action $action is not " - . "defined. You should override ".__METHOD__." to add this " - . "action"); + throw new LogicException("the view for action {$action} is not " + . 'defined. You should override ' . __METHOD__ . ' to add this ' + . 'action'); } } + protected function getTranslator(): TranslatorInterface + { + return $this->container->get('translator'); + } + /** * @param $action * @param $entity - * @param Request $request - * @return bool */ protected function hasCustomTemplate($action, $entity, Request $request): bool { @@ -1033,69 +805,93 @@ class CRUDController extends AbstractController } /** + * Build an index page. + * + * Some steps may be overriden during this process of rendering. + * + * This method: + * + * 1. Launch `onPreIndex` + * 2. check acl. If it does return a response instance, return it + * 3. launch `onPostCheckACL`. If it does return a response instance, return it + * 4. count the items, using `countEntities` + * 5. build a paginator element from the the number of entities ; + * 6. Launch `onPreIndexQuery`. If it does return a response instance, return it + * 7. fetch the results, using `getQueryResult` + * + * Internally, this build a query, using `queryEntities` + * + * 8. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it + * 9. create default parameters: + * + * The default parameters are: + * + * * entities: the list en entities ; + * * crud_name: the name of the crud ; + * * paginator: a paginator element ; + * 10. Launch rendering, the parameter is fetch using `getTemplateFor` + * The parameters may be personnalized using `generateTemplateParameter`. + * * @param string $action - * @param $entity - * @param FormInterface $form - * @param Request $request */ - protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) + protected function indexEntityAction($action, Request $request) { - } + $this->onPreIndex($action, $request); - /** - * @param string $action - * @param $entity - * @param FormInterface $form - * @param Request $request - */ - protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) - { - } + $response = $this->checkACL($action, null); - /** - * @param string $action - * @param $entity - * @param FormInterface $form - * @param Request $request - */ - protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) - { - } + if ($response instanceof Response) { + return $response; + } - /** - * @param string $action - * @param $entity - * @param FormInterface $form - * @param Request $request - */ - protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) - { - } + $entity = ''; - /** - * @param $action - * @param Request $request - * @param $entity - * @return null|Response - */ - protected function onPostFetchEntity($action, Request $request, $entity): ?Response - { - return null; - } + $response = $this->onPostCheckACL($action, $request, $entity); - /** - * @param $action - * @param Request $request - * @param $entity - * @return null|Response - */ - protected function onPostCheckACL($action, Request $request, $entity): ?Response - { - return null; - } + if ($response instanceof Response) { + return $response; + } - protected function onFormValid(string $action, object $entity, FormInterface $form, Request $request) - { + $filterOrder = $this->buildFilterOrderHelper($action, $request); + $totalItems = $this->countEntities($action, $request, $filterOrder); + $paginator = $this->getPaginatorFactory()->create($totalItems); + + $response = $this->onPreIndexBuildQuery( + $action, + $request, + $totalItems, + $paginator + ); + + if ($response instanceof Response) { + return $response; + } + + $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder); + + $response = $this->onPostIndexFetchQuery( + $action, + $request, + $totalItems, + $paginator, + $entities + ); + + if ($response instanceof Response) { + return $response; + } + + $defaultTemplateParameters = [ + 'entities' => $entities, + 'crud_name' => $this->getCrudName(), + 'paginator' => $paginator, + 'filter_order' => $filterOrder, + ]; + + return $this->render( + $this->getTemplateFor($action, $entities, $request), + $this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters) + ); } /** @@ -1107,113 +903,242 @@ class CRUDController extends AbstractController * * save-and-new: return to new page of current crud ; * * save-and-view: return to view page of current crud ; * - * @param string $action * @param mixed $entity - * @param FormInterface $form - * @param Request $request + * * @return \Symfony\Component\HttpFoundation\RedirectResponse */ protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request) { - $next = $request->request->get("submit", "save-and-close"); + $next = $request->request->get('submit', 'save-and-close'); switch ($next) { - case "save-and-close": - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'); - case "save-and-new": - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_new', $request->query->all()); + case 'save-and-close': + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_index'); + + case 'save-and-new': + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_new', $request->query->all()); + default: - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [ - 'id' => $entity->getId() - ]); + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_view', [ + 'id' => $entity->getId(), + ]); } } + protected function onFormValid(string $action, object $entity, FormInterface $form, Request $request) + { + } + /** - * Include services + * @param $action + * @param $entity + */ + protected function onPostCheckACL($action, Request $request, $entity): ?Response + { + return null; + } + + /** + * @param $action + * @param $entity + */ + protected function onPostFetchEntity($action, Request $request, $entity): ?Response + { + return null; + } + + /** + * @param $entity + */ + protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) + { + } + + /** + * method used by indexAction. * - * @param string $action - * @return mixed + * @param mixed $query */ - protected function getActionConfig(string $action) + protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) { - return $this->crudConfig['actions'][$action]; } /** - * @todo (check how to do this with dependency injection and make changes...) - * @return PaginatorFactory + * method used by indexAction. + * + * @param mixed $entities */ - protected function getPaginatorFactory(): PaginatorFactory + protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $entities) { - return $this->container->get('chill_main.paginator_factory'); } /** - * @return TranslatorInterface + * @param $entity */ - protected function getTranslator(): TranslatorInterface + protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) { - return $this->container->get('translator'); } /** - * @return AuthorizationHelper + * @param $entity */ - protected function getAuthorizationHelper(): AuthorizationHelper + protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPreDelete(string $action, Request $request) { - return $this->container->get(AuthorizationHelper::class); } /** - * @param Role $role - * @param Scope|null $scope - * @return \Chill\MainBundle\Entity\Center[] + * @param $entity */ - protected function getReachableCenters(Role $role, Scope $scope = null) + protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPreIndex(string $action, Request $request) { - return $this->getAuthorizationHelper() - ->getReachableCenters($this->getUser(), $role, $scope) - ; } /** - * @return EventDispatcherInterface + * method used by indexAction. */ - protected function getEventDispatcher(): EventDispatcherInterface + protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) { - return $this->get(EventDispatcherInterface::class); } /** - * @return Resolver + * @param $entity */ - protected function getCrudResolver(): Resolver + protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) { - return $this->get(Resolver::class); - } - - protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface - { - return $this->get(FilterOrderHelperFactoryInterface::class); } /** - * @return array + * @param $entity */ - public static function getSubscribedServices(): array + protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request) { - return \array_merge( - parent::getSubscribedServices(), - [ - 'chill_main.paginator_factory' => PaginatorFactory::class, - 'translator' => TranslatorInterface::class, - AuthorizationHelper::class => AuthorizationHelper::class, - EventDispatcherInterface::class => EventDispatcherInterface::class, - Resolver::class => Resolver::class, - SerializerInterface::class => SerializerInterface::class, - FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class, - ] - ); + } + + /** + * Add ordering fields in the query build by self::queryEntities. + * + * @param mixed|QueryBuilder $query by default, an instance of QueryBuilder + * + * @return mixed|QueryBuilder + */ + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) + { + return $query; + } + + /** + * Query the entity. + * + * By default, get all entities. + * + * The method `orderEntity` is called internally to order entities. + * + * It returns, by default, a query builder. + * + * @return type + */ + protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null) + { + $query = $this->buildQueryEntities($action, $request) + ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) + ->setMaxResults($paginator->getItemsPerPage()); + + // allow to order queries and return the new query + return $this->orderQuery($action, $query, $request, $paginator); + } + + /** + * @param $entity + */ + protected function removeEntity(string $action, $entity, FormInterface $form, Request $request) + { + $this->getDoctrine() + ->getManager() + ->remove($entity); + } + + /** + * The view action. + * + * Some steps may be overriden during this process of rendering: + * + * This method: + * + * 1. fetch the entity, using `getEntity` + * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, + * this response is returned. + * 2. throw an HttpNotFoundException if entity is null + * 3. check ACL using `checkACL` ; + * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * this response is returned ; + * 5. generate default template parameters: + * + * * `entity`: the fetched entity ; + * * `crud_name`: the crud name + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * The parameters may be personnalized using `generateTemplateParameter`. + * + * @param mixed $id + * @param mixed $_format + */ + protected function viewAction(string $action, Request $request, $id, $_format = 'html'): Response + { + $entity = $this->getEntity($action, $id, $request); + + $postFetch = $this->onPostFetchEntity($action, $request, $entity); + + if ($postFetch instanceof Response) { + return $postFetch; + } + + if (null === $entity) { + throw $this->createNotFoundException( + sprintf( + 'The %s with id %s is not found', + $this->getCrudName(), + $id + ) + ); + } + + $response = $this->checkACL($action, $entity); + + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $entity); + + if ($response instanceof Response) { + return $response; + } + + if ('html' === $_format) { + $defaultTemplateParameters = [ + 'entity' => $entity, + 'crud_name' => $this->getCrudName(), + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + if ('json' === $_format) { + $context = $this->getContextForSerialization($action, $request, $entity, $_format); + + return $this->json($entity, Response::HTTP_OK, [], $context); + } + + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This format is not implemented'); } } diff --git a/src/Bundle/ChillMainBundle/CRUD/Form/CRUDDeleteEntityForm.php b/src/Bundle/ChillMainBundle/CRUD/Form/CRUDDeleteEntityForm.php index 6b382fca3..00cb7bfc4 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Form/CRUDDeleteEntityForm.php +++ b/src/Bundle/ChillMainBundle/CRUD/Form/CRUDDeleteEntityForm.php @@ -1,33 +1,20 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\CRUD\Form; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; - /** - * Class CRUDDeleteEntityForm - * - * @package Chill\MainBundle\CRUD\Form + * Class CRUDDeleteEntityForm. */ class CRUDDeleteEntityForm extends AbstractType { diff --git a/src/Bundle/ChillMainBundle/CRUD/Resolver/Resolver.php b/src/Bundle/ChillMainBundle/CRUD/Resolver/Resolver.php index f6242a0f1..11b84e586 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Resolver/Resolver.php +++ b/src/Bundle/ChillMainBundle/CRUD/Resolver/Resolver.php @@ -1,122 +1,116 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\CRUD\Resolver; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\PropertyAccess\PropertyAccess; +use LogicException; +use function array_key_exists; +use function strtoupper; /** - * Class Resolver - * - * @package Chill\MainBundle\CRUD\Resolver + * Class Resolver. */ class Resolver { /** - * @var EntityManagerInterface + * The key to get the role necessary for the action. */ - protected $em; - + public const ROLE = 'role'; + /** - * @var \Symfony\Component\PropertyAccess\PropertyAccessor + * @deprecated */ - protected $propertyAccess; - + public const ROLE_EDIT = 'role.edit'; + + /** + * @deprecated + */ + public const ROLE_VIEW = 'role.view'; + /** * @var array */ protected $crudConfig; - + /** - * @deprecated + * @var EntityManagerInterface */ - const ROLE_VIEW = 'role.view'; - + protected $em; + /** - * @deprecated + * @var \Symfony\Component\PropertyAccess\PropertyAccessor */ - const ROLE_EDIT = 'role.edit'; - - /** - * The key to get the role necessary for the action - */ - const ROLE = 'role'; - + protected $propertyAccess; + /** * Resolver constructor. - * - * @param EntityManagerInterface $em - * @param array $crudConfig */ - function __construct(EntityManagerInterface $em, array $crudConfig) + public function __construct(EntityManagerInterface $em, array $crudConfig) { $this->em = $em; - - foreach($crudConfig as $conf) { + + foreach ($crudConfig as $conf) { $this->crudConfig[$conf['name']] = $conf; } } - - /** - * @param $key - * @param $crudName - * @param null $action - * @return string - */ - public function getConfigValue($key, $crudName, $action = null) - { - $config = $this->crudConfig[$crudName]; - - switch ($key) { - case self::ROLE: - return $config['actions'][$action]['role'] ?? $this->buildDefaultRole($crudName, $action); - } - } - + /** * @param $crudName * @param $action + * * @return string */ public function buildDefaultRole($crudName, $action) { if (empty($this->crudConfig[$crudName]['base_role'])) { - throw new \LogicException(sprintf("the base role is not defined. You must define " - . "on or override %s or %s methods", __METHOD__, "getRoleFor")); + throw new LogicException(sprintf('the base role is not defined. You must define ' + . 'on or override %s or %s methods', __METHOD__, 'getRoleFor')); } - - return \strtoupper( - $this->crudConfig[$crudName]['base_role']. - '_'. - $action); + + return strtoupper( + $this->crudConfig[$crudName]['base_role'] . + '_' . + $action + ); } - + + /** + * @param $key + * @param $crudName + * @param null $action + * + * @return string + */ + public function getConfigValue($key, $crudName, $action = null) + { + $config = $this->crudConfig[$crudName]; + + switch ($key) { + case self::ROLE: + return $config['actions'][$action]['role'] ?? $this->buildDefaultRole($crudName, $action); + } + } + /** * @param $crudName * @param $action + * * @return bool */ public function hasAction($crudName, $action) { - return \array_key_exists($action, - $this->crudConfig[$crudName]['actions']); + return array_key_exists( + $action, + $this->crudConfig[$crudName]['actions'] + ); } } diff --git a/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php b/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php index 36aadb8b7..33cce8a2f 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php +++ b/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php @@ -1,152 +1,103 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\CRUD\Routing; +use RuntimeException; use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\HttpFoundation\Request; -use Chill\MainBundle\CRUD\Controller\ApiController; -use Chill\MainBundle\CRUD\Controller\CRUDController; +use function array_filter; +use function array_keys; +use function array_search; +use function count; +use function in_array; +use const ARRAY_FILTER_USE_BOTH; -/** - * Class CRUDRoutesLoader - * Load the route for CRUD - * - * @package Chill\MainBundle\CRUD\Routing - */ class CRUDRoutesLoader extends Loader { - protected array $crudConfig = []; - - protected array $apiCrudConfig = []; - - /** - * @var bool - */ - private $isLoaded = false; + private const ALL_INDEX_METHODS = [Request::METHOD_GET, Request::METHOD_HEAD]; private const ALL_SINGLE_METHODS = [ Request::METHOD_GET, Request::METHOD_POST, Request::METHOD_PUT, - Request::METHOD_DELETE + Request::METHOD_DELETE, ]; - private const ALL_INDEX_METHODS = [ Request::METHOD_GET, Request::METHOD_HEAD ]; - - /** - * CRUDRoutesLoader constructor. - * - * @param $crudConfig the config from cruds - * @param $apicrudConfig the config from api_crud - */ - public function __construct(array $crudConfig, array $apiConfig) + protected array $apiCrudConfig = []; + + protected array $crudConfig = []; + + private bool $isLoaded = false; + + public function __construct(array $crudConfig, array $apiCrudConfig) { $this->crudConfig = $crudConfig; - $this->apiConfig = $apiConfig; + $this->apiCrudConfig = $apiCrudConfig; + + parent::__construct(); } - + + /** + * Load routes for CRUD and CRUD Api. + * + * @param mixed $resource + * @param mixed|null $type + */ + public function load($resource, $type = null): RouteCollection + { + if (true === $this->isLoaded) { + throw new RuntimeException('Do not add the "CRUD" loader twice'); + } + + $collection = new RouteCollection(); + + foreach ($this->crudConfig as $crudConfig) { + $collection->addCollection($this->loadCrudConfig($crudConfig)); + } + + foreach ($this->apiCrudConfig as $crudConfig) { + $collection->addCollection($this->loadApi($crudConfig)); + } + + return $collection; + } + /** * @param mixed $resource * @param null $type + * * @return bool */ public function supports($resource, $type = null) { return 'CRUD' === $type; } - - /** - * Load routes for CRUD and CRUD Api - */ - public function load($resource, $type = null): RouteCollection - { - if (true === $this->isLoaded) { - throw new \RuntimeException('Do not add the "CRUD" loader twice'); - } - $collection = new RouteCollection(); - - foreach ($this->crudConfig as $crudConfig) { - $collection->addCollection($this->loadCrudConfig($crudConfig)); - } - foreach ($this->apiConfig as $crudConfig) { - $collection->addCollection($this->loadApi($crudConfig)); - } - - return $collection; - } - /** - * Load routes for CRUD (without api) + * Load routes for api single. * * @param $crudConfig - * @return RouteCollection - */ - protected function loadCrudConfig($crudConfig): RouteCollection - { - $collection = new RouteCollection(); - $controller ='cscrud_'.$crudConfig['name'].'_controller'; - - foreach ($crudConfig['actions'] as $name => $action) { - // defaults (controller name) - $defaults = [ - '_controller' => $controller.':'.($action['controller_action'] ?? $name) - ]; - - if ($name === 'index') { - $path = "{_locale}".$crudConfig['base_path']; - $route = new Route($path, $defaults); - } elseif ($name === 'new') { - $path = "{_locale}".$crudConfig['base_path'].'/'.$name; - $route = new Route($path, $defaults); - } else { - $path = "{_locale}".$crudConfig['base_path'].($action['path'] ?? '/{id}/'.$name); - $requirements = $action['requirements'] ?? [ - '{id}' => '\d+' - ]; - $route = new Route($path, $defaults, $requirements); - } - - $collection->add('chill_crud_'.$crudConfig['name'].'_'.$name, $route); - } - - return $collection; - } - - /** - * Load routes for api single - * - * @param $crudConfig - * @return RouteCollection */ protected function loadApi(array $crudConfig): RouteCollection { $collection = new RouteCollection(); - $controller = 'csapi_'.$crudConfig['name'].'_controller'; + $controller = 'csapi_' . $crudConfig['name'] . '_controller'; foreach ($crudConfig['actions'] as $name => $action) { // filter only on single actions - $singleCollection = $action['single_collection'] ?? $name === '_entity' ? 'single' : NULL; + $singleCollection = $action['single_collection'] ?? '_entity' === $name ? 'single' : null; + if ('collection' === $singleCollection) { // continue; } @@ -155,46 +106,61 @@ class CRUDRoutesLoader extends Loader switch ($name) { case '_entity': $controllerAction = 'entityApi'; + break; + case '_index': $controllerAction = 'indexApi'; + break; + default: - $controllerAction = $name.'Api'; + $controllerAction = $name . 'Api'; + break; } $defaults = [ - '_controller' => $controller.':'.($action['controller_action'] ?? $controllerAction) + '_controller' => $controller . ':' . ($action['controller_action'] ?? $controllerAction), ]; // path are rewritten // if name === 'default', we rewrite it to nothing :-) - $localName = \in_array($name, [ '_entity', '_index' ]) ? '' : '/'.$name; + $localName = in_array($name, ['_entity', '_index'], true) ? '' : '/' . $name; + if ('collection' === $action['single_collection'] || '_index' === $name) { - $localPath = $action['path'] ?? $localName.'.{_format}'; + $localPath = $action['path'] ?? $localName . '.{_format}'; } else { - $localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}'; + $localPath = $action['path'] ?? '/{id}' . $localName . '.{_format}'; } - $path = $crudConfig['base_path'].$localPath; + $path = $crudConfig['base_path'] . $localPath; - $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ]; + $requirements = $action['requirements'] ?? ['{id}' => '\d+']; - $methods = \array_keys(\array_filter($action['methods'], function($value, $key) { return $value; }, - ARRAY_FILTER_USE_BOTH)); + $methods = array_keys(array_filter( + $action['methods'], + static function ($value, $key) { return $value; }, + ARRAY_FILTER_USE_BOTH + )); if (count($methods) === 0) { - throw new \RuntimeException("The api configuration named \"{$crudConfig['name']}\", action \"{$name}\", ". - "does not have any allowed methods. You should remove this action from the config ". - "or allow, at least, one method"); + throw new RuntimeException("The api configuration named \"{$crudConfig['name']}\", action \"{$name}\", " . + 'does not have any allowed methods. You should remove this action from the config ' . + 'or allow, at least, one method'); } - if ('_entity' === $name && \in_array(Request::METHOD_POST, $methods)) { - unset($methods[\array_search(Request::METHOD_POST, $methods)]); - $entityPostRoute = $this->createEntityPostRoute($name, $crudConfig, $action, - $controller); - $collection->add("chill_api_single_{$crudConfig['name']}_{$name}_create", - $entityPostRoute); + if ('_entity' === $name && in_array(Request::METHOD_POST, $methods, true)) { + unset($methods[array_search(Request::METHOD_POST, $methods, true)]); + $entityPostRoute = $this->createEntityPostRoute( + $name, + $crudConfig, + $action, + $controller + ); + $collection->add( + "chill_api_single_{$crudConfig['name']}_{$name}_create", + $entityPostRoute + ); } if (count($methods) === 0) { @@ -205,8 +171,44 @@ class CRUDRoutesLoader extends Loader $route = new Route($path, $defaults, $requirements); $route->setMethods($methods); - - $collection->add('chill_api_single_'.$crudConfig['name'].'_'.$name, $route); + + $collection->add('chill_api_single_' . $crudConfig['name'] . '_' . $name, $route); + } + + return $collection; + } + + /** + * Load routes for CRUD (without api). + * + * @param $crudConfig + */ + protected function loadCrudConfig($crudConfig): RouteCollection + { + $collection = new RouteCollection(); + $controller = 'cscrud_' . $crudConfig['name'] . '_controller'; + + foreach ($crudConfig['actions'] as $name => $action) { + // defaults (controller name) + $defaults = [ + '_controller' => $controller . ':' . ($action['controller_action'] ?? $name), + ]; + + if ('index' === $name) { + $path = '{_locale}' . $crudConfig['base_path']; + $route = new Route($path, $defaults); + } elseif ('new' === $name) { + $path = '{_locale}' . $crudConfig['base_path'] . '/' . $name; + $route = new Route($path, $defaults); + } else { + $path = '{_locale}' . $crudConfig['base_path'] . ($action['path'] ?? '/{id}/' . $name); + $requirements = $action['requirements'] ?? [ + '{id}' => '\d+', + ]; + $route = new Route($path, $defaults, $requirements); + } + + $collection->add('chill_crud_' . $crudConfig['name'] . '_' . $name, $route); } return $collection; @@ -214,14 +216,14 @@ class CRUDRoutesLoader extends Loader private function createEntityPostRoute(string $name, $crudConfig, array $action, $controller): Route { - $localPath = $action['path'].'.{_format}'; + $localPath = $action['path'] . '.{_format}'; $defaults = [ - '_controller' => $controller.':'.($action['controller_action'] ?? 'entityPost') + '_controller' => $controller . ':' . ($action['controller_action'] ?? 'entityPost'), ]; - $path = $crudConfig['base_path'].$localPath; + $path = $crudConfig['base_path'] . $localPath; $requirements = $action['requirements'] ?? []; $route = new Route($path, $defaults, $requirements); - $route->setMethods([ Request::METHOD_POST ]); + $route->setMethods([Request::METHOD_POST]); return $route; } diff --git a/src/Bundle/ChillMainBundle/CRUD/Templating/TwigCRUDResolver.php b/src/Bundle/ChillMainBundle/CRUD/Templating/TwigCRUDResolver.php index 00956bf2e..8704a79ca 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Templating/TwigCRUDResolver.php +++ b/src/Bundle/ChillMainBundle/CRUD/Templating/TwigCRUDResolver.php @@ -1,36 +1,23 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\CRUD\Templating; use Chill\MainBundle\CRUD\Resolver\Resolver; -use Twig\TwigFilter; -use Twig\TwigFunction; use Twig\Extension\AbstractExtension; -use Twig\Environment; +use Twig\TwigFunction; /** * Class TwigCRUDResolver - * Twig filters to display data in crud template - * - * @package Chill\MainBundle\CRUD\Templating + * Twig filters to display data in crud template. */ class TwigCRUDResolver extends AbstractExtension { @@ -38,49 +25,54 @@ class TwigCRUDResolver extends AbstractExtension * @var Resolver */ protected $resolver; - + /** * TwigCRUDResolver constructor. - * - * @param Resolver $resolver */ - function __construct(Resolver $resolver) + public function __construct(Resolver $resolver) { $this->resolver = $resolver; } - - /** - * @return array|TwigFunction[] - */ - public function getFunctions() - { - return [ - new TwigFunction('chill_crud_config', [$this, 'getConfig'], - ['is_safe' => 'html']), - new TwigFunction('chill_crud_action_exists', [$this, 'hasAction'], - []), - ]; - } - + /** * @param $configKey * @param $crudName * @param null $action + * * @return string */ public function getConfig($configKey, $crudName, $action = null) { return $this->resolver->getConfigValue($configKey, $crudName, $action); } - + + /** + * @return array|TwigFunction[] + */ + public function getFunctions() + { + return [ + new TwigFunction( + 'chill_crud_config', + [$this, 'getConfig'], + ['is_safe' => 'html'] + ), + new TwigFunction( + 'chill_crud_action_exists', + [$this, 'hasAction'], + [] + ), + ]; + } + /** * @param $crudName * @param $action + * * @return bool */ public function hasAction($crudName, $action) { return $this->resolver->hasAction($crudName, $action); } - } diff --git a/src/Bundle/ChillMainBundle/Center/GroupingCenterInterface.php b/src/Bundle/ChillMainBundle/Center/GroupingCenterInterface.php index 5f10d35c8..4fb6071cd 100644 --- a/src/Bundle/ChillMainBundle/Center/GroupingCenterInterface.php +++ b/src/Bundle/ChillMainBundle/Center/GroupingCenterInterface.php @@ -1,27 +1,35 @@ addTag('chill_main.scope_resolver'); $container->registerForAutoconfiguration(ChillEntityRenderInterface::class) ->addTag('chill.render_entity'); + $container->registerForAutoconfiguration(SearchApiInterface::class) + ->addTag('chill.search_api_provider'); $container->addCompilerPass(new SearchableServicesCompilerPass()); $container->addCompilerPass(new ConfigConsistencyCompilerPass()); diff --git a/src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php b/src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php index 99bd17d8f..1f07af303 100644 --- a/src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php +++ b/src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php @@ -1,123 +1,114 @@ em = $em; $this->passwordEncoder = $passwordEncoder; $this->validator = $validator; $this->logger = $logger; - - - $this->userRepository = $em->getRepository(User::class); - + $this->userRepository = $userRepository; + parent::__construct('chill:main:import-users'); } - - + protected function appendUserToFile(User $user) + { + $this->output->insertOne([ + $user->getEmail(), + $user->getUsername(), + $user->getId(), + ]); + } + + protected function concatenateViolations(ConstraintViolationListInterface $list) + { + $str = []; + + foreach ($list as $e) { + /* @var $e \Symfony\Component\Validator\ConstraintViolationInterface */ + $str[] = $e->getMessage(); + } + + return implode(';', $str); + } + protected function configure() { $this @@ -126,82 +117,89 @@ class ChillImportUsersCommand extends Command ->addArgument('csvfile', InputArgument::REQUIRED, 'Path to the csv file. Columns are: `username`, `email`, `center` (can contain alias), `permission group`') ->addOption('grouping-centers', null, InputOption::VALUE_OPTIONAL, 'Path to a csv file to aggregate multiple centers into a single alias') ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not commit the changes') - ->addOption('csv-dump', null, InputOption::VALUE_REQUIRED, 'A path to dump a summary of the created file') - ; + ->addOption('csv-dump', null, InputOption::VALUE_REQUIRED, 'A path to dump a summary of the created file'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function createOrGetGroupCenter(Center $center, PermissionsGroup $pg): GroupCenter { - $this->tempOutput = $output; - $this->tempInput = $input; - - if ($input->getOption('dry-run')) { - $this->doChanges = false; - } - - $this->prepareWriter(); - - if ($input->hasOption('grouping-centers')) { - $this->prepareGroupingCenters(); - } - - try { - $this->loadUsers(); - } - catch(\Exception $e) { - throw $e; - } - } - - protected function prepareWriter() - { - $this->output = $output = Writer::createFromPath($this->tempInput - ->getOption('csv-dump'), 'a+'); - - $output->insertOne([ - 'email', - 'username', - 'id' - ]); - } - - protected function appendUserToFile(User $user) - { - $this->output->insertOne( [ - $user->getEmail(), - $user->getUsername(), - $user->getId() - ]); - } - - protected function loadUsers() - { - $reader = Reader::createFromPath($this->tempInput->getArgument('csvfile')); - $reader->setHeaderOffset(0); - - foreach ($reader->getRecords() as $line => $r) { - $this->logger->debug("starting handling new line", [ - 'line' => $line - ]); - - if ($this->doesUserExists($r)) { - $this->tempOutput->writeln(sprintf("User with username '%s' already " - . "exists, skipping", $r["username"])); - - $this->logger->info("One user already exists, skipping creation", [ - 'username_in_file' => $r['username'], - 'email_in_file' => $r['email'], - 'line' => $line - ]); - - continue; + if (array_key_exists($center->getId(), $this->groupCenters)) { + if (array_key_exists($pg->getId(), $this->groupCenters[$center->getId()])) { + return $this->groupCenters[$center->getId()][$pg->getId()]; } - - $user = $this->createUser($line, $r); - $this->appendUserToFile($user); } + + $repository = $this->em->getRepository(GroupCenter::class); + + $groupCenter = $repository->findOneBy([ + 'center' => $center, + 'permissionsGroup' => $pg, + ]); + + if (null === $groupCenter) { + $groupCenter = new GroupCenter(); + $groupCenter + ->setCenter($center) + ->setPermissionsGroup($pg); + + $this->em->persist($groupCenter); + } + + $this->groupCenters[$center->getId()][$pg->getId()] = $groupCenter; + + return $groupCenter; } - + + protected function createUser($offset, $data) + { + $user = new User(); + $user + ->setEmail(trim($data['email'])) + ->setUsername(trim($data['username'])) + ->setEnabled(true) + ->setPassword($this->passwordEncoder->encodePassword( + $user, + bin2hex(random_bytes(32)) + )); + + $errors = $this->validator->validate($user); + + if ($errors->count() > 0) { + $errorMessages = $this->concatenateViolations($errors); + + $this->tempOutput->writeln(sprintf('%d errors found with user with username "%s" at line %d', $errors->count(), $data['username'], $offset)); + $this->tempOutput->writeln($errorMessages); + + throw new RuntimeException('Found errors while creating an user. ' + . 'Watch messages in command output'); + } + + $pgs = $this->getPermissionGroup($data['permission group']); + $centers = $this->getCenters($data['center']); + + foreach ($pgs as $pg) { + foreach ($centers as $center) { + $groupcenter = $this->createOrGetGroupCenter($center, $pg); + + if (false === $user->getGroupCenters()->contains($groupcenter)) { + $user->addGroupCenter($groupcenter); + } + } + } + + if ($this->doChanges) { + $this->em->persist($user); + $this->em->flush(); + } + + $this->logger->notice('Create user', [ + 'username' => $user->getUsername(), + 'id' => $user->getId(), + 'nb_of_groupCenters' => $user->getGroupCenters()->count(), + ]); + + return $user; + } + protected function doesUserExists($data) { if ($this->userRepository->countByUsernameOrEmail($data['username']) > 0) { @@ -211,238 +209,206 @@ class ChillImportUsersCommand extends Command if ($this->userRepository->countByUsernameOrEmail($data['email']) > 0) { return true; } - + return false; } - - protected function createUser($offset, $data) - { - $user = new User(); - $user - ->setEmail(\trim($data['email'])) - ->setUsername(\trim($data['username'])) - ->setEnabled(true) - ->setPassword($this->passwordEncoder->encodePassword($user, - \bin2hex(\random_bytes(32)))) - ; - - $errors = $this->validator->validate($user); - - if ($errors->count() > 0) { - $errorMessages = $this->concatenateViolations($errors); - - $this->tempOutput->writeln(sprintf("%d errors found with user with username \"%s\" at line %d", $errors->count(), $data['username'], $offset)); - $this->tempOutput->writeln($errorMessages); - throw new \RuntimeException("Found errors while creating an user. " - . "Watch messages in command output"); - } - - $pgs = $this->getPermissionGroup($data['permission group']); - $centers = $this->getCenters($data['center']); - - foreach($pgs as $pg) { - foreach ($centers as $center) { - $groupcenter = $this->createOrGetGroupCenter($center, $pg); - - if (FALSE === $user->getGroupCenters()->contains($groupcenter)) { - $user->addGroupCenter($groupcenter); - } - } - } - + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->tempOutput = $output; + $this->tempInput = $input; - if ($this->doChanges) { - $this->em->persist($user); - $this->em->flush(); + if ($input->getOption('dry-run')) { + $this->doChanges = false; } - - $this->logger->notice("Create user", [ - 'username' => $user->getUsername(), - 'id' => $user->getId(), - 'nb_of_groupCenters' => $user->getGroupCenters()->count() - ]); - return $user; - } - - protected function getPermissionGroup($alias) - { - if (\array_key_exists($alias, $this->permissionGroups)) { - return $this->permissionGroups[$alias]; + $this->prepareWriter(); + + if ($input->hasOption('grouping-centers')) { + $this->prepareGroupingCenters(); } - - $permissionGroupsByName = []; - - foreach($this->em->getRepository(PermissionsGroup::class) - ->findAll() as $permissionGroup) { - $permissionGroupsByName[$permissionGroup->getName()] = $permissionGroup; - } - - if (count($permissionGroupsByName) === 0) { - throw new \RuntimeException("no permission groups found. Create them " - . "before importing users"); - } - - $question = new ChoiceQuestion("To which permission groups associate with \"$alias\" ?", - \array_keys($permissionGroupsByName)); - $question - ->setMultiselect(true) - ->setAutocompleterValues(\array_keys($permissionGroupsByName)) - ->setNormalizer(function($value) { - if (NULL === $value) { return ''; } - - return \trim($value); - }) - ; - $helper = $this->getHelper('question'); - - $keys = $helper->ask($this->tempInput, $this->tempOutput, $question); - - $this->tempOutput->writeln("You have chosen ".\implode(", ", $keys)); - - if ($helper->ask($this->tempInput, $this->tempOutput, - new ConfirmationQuestion("Are you sure ?", true))) { - - foreach ($keys as $key) { - $this->permissionGroups[$alias][] = $permissionGroupsByName[$key]; - } - - return $this->permissionGroups[$alias]; - } else { - $this->logger->error("Error while responding to a a question"); - - $this->tempOutput("Ok, I accept, but I do not know what to do. Please try again."); - - throw new \RuntimeException("Error while responding to a question"); + + try { + $this->loadUsers(); + } catch (Exception $e) { + throw $e; } } - - /** - * - * @param Center $center - * @param \Chill\MainBundle\Command\PermissionGroup $pg - * @return GroupCenter - */ - protected function createOrGetGroupCenter(Center $center, PermissionsGroup $pg): GroupCenter - { - if (\array_key_exists($center->getId(), $this->groupCenters)) { - if (\array_key_exists($pg->getId(), $this->groupCenters[$center->getId()])) { - return $this->groupCenters[$center->getId()][$pg->getId()]; - } - } - - $repository = $this->em->getRepository(GroupCenter::class); - - $groupCenter = $repository->findOneBy(array( - 'center' => $center, - 'permissionsGroup' => $pg - )); - - if ($groupCenter === NULL) { - $groupCenter = new GroupCenter(); - $groupCenter - ->setCenter($center) - ->setPermissionsGroup($pg) - ; - - $this->em->persist($groupCenter); - } - - $this->groupCenters[$center->getId()][$pg->getId()] = $groupCenter; - - return $groupCenter; - } - - protected function prepareGroupingCenters() - { - $reader = Reader::createFromPath($this->tempInput->getOption('grouping-centers')); - $reader->setHeaderOffset(0); - - foreach ($reader->getRecords() as $r) { - $this->centers[$r['alias']] = - \array_merge( - $this->centers[$r['alias']] ?? [], - $this->getCenters($r['center'] - ) - ); - } - } - + /** * return a list of centers matching the name of alias. - * + * * If the name match one center, this center is returned in an array. - * - * If the name match an alias, the centers corresponding to the alias are + * + * If the name match an alias, the centers corresponding to the alias are * returned in an array. - * + * * If the center is not found or alias is not created, a new center is created * and suggested to user - * + * * @param string $name the name of the center or the alias regrouping center + * * @return Center[] */ protected function getCenters($name) { // sanitize - $name = \trim($name); - - if (\array_key_exists($name, $this->centers)) { + $name = trim($name); + + if (array_key_exists($name, $this->centers)) { return $this->centers[$name]; } - + // search for a center with given name $center = $this->em->getRepository(Center::class) ->findOneByName($name); - + if ($center instanceof Center) { $this->centers[$name] = [$center]; - + return $this->centers[$name]; } - + // suggest and create $center = (new Center()) ->setName($name); - - $this->tempOutput->writeln("Center with name \"$name\" not found."); + + $this->tempOutput->writeln("Center with name \"{$name}\" not found."); $qFormatter = $this->getHelper('question'); - $question = new ConfirmationQuestion("Create a center with name \"$name\" ?", true); - + $question = new ConfirmationQuestion("Create a center with name \"{$name}\" ?", true); + if ($qFormatter->ask($this->tempInput, $this->tempOutput, $question)) { - $this->centers[$name] = [ $center ]; - + $this->centers[$name] = [$center]; + $errors = $this->validator->validate($center); - + if ($errors->count() > 0) { $errorMessages = $this->concatenateViolations($errors); - - $this->tempOutput->writeln(sprintf("%d errors found with center with name \"%s\"", $errors->count(), $name)); + + $this->tempOutput->writeln(sprintf('%d errors found with center with name "%s"', $errors->count(), $name)); $this->tempOutput->writeln($errorMessages); - - throw new \RuntimeException("Found errors while creating one center. " - . "Watch messages in command output"); + + throw new RuntimeException('Found errors while creating one center. ' + . 'Watch messages in command output'); } - + $this->em->persist($center); - + return $this->centers[$name]; } - + return null; } - - protected function concatenateViolations(ConstraintViolationListInterface $list) + + protected function getPermissionGroup($alias) { - $str = []; - - foreach ($list as $e) { - /* @var $e \Symfony\Component\Validator\ConstraintViolationInterface */ - $str[] = $e->getMessage(); + if (array_key_exists($alias, $this->permissionGroups)) { + return $this->permissionGroups[$alias]; } - - return \implode(";", $str); + + $permissionGroupsByName = []; + + foreach ($this->em->getRepository(PermissionsGroup::class) + ->findAll() as $permissionGroup) { + $permissionGroupsByName[$permissionGroup->getName()] = $permissionGroup; + } + + if (count($permissionGroupsByName) === 0) { + throw new RuntimeException('no permission groups found. Create them ' + . 'before importing users'); + } + + $question = new ChoiceQuestion( + "To which permission groups associate with \"{$alias}\" ?", + array_keys($permissionGroupsByName) + ); + $question + ->setMultiselect(true) + ->setAutocompleterValues(array_keys($permissionGroupsByName)) + ->setNormalizer(static function ($value) { + if (null === $value) { + return ''; + } + + return trim($value); + }); + $helper = $this->getHelper('question'); + + $keys = $helper->ask($this->tempInput, $this->tempOutput, $question); + + $this->tempOutput->writeln('You have chosen ' . implode(', ', $keys)); + + if ($helper->ask( + $this->tempInput, + $this->tempOutput, + new ConfirmationQuestion('Are you sure ?', true) + )) { + foreach ($keys as $key) { + $this->permissionGroups[$alias][] = $permissionGroupsByName[$key]; + } + + return $this->permissionGroups[$alias]; + } + + $this->logger->error('Error while responding to a a question'); + $this->tempOutput->writeln('Ok, I accept, but I do not know what to do. Please try again.'); + + throw new RuntimeException('Error while responding to a question'); + } + + protected function loadUsers() + { + $reader = Reader::createFromPath($this->tempInput->getArgument('csvfile')); + $reader->setHeaderOffset(0); + + foreach ($reader->getRecords() as $line => $r) { + $this->logger->debug('starting handling new line', [ + 'line' => $line, + ]); + + if ($this->doesUserExists($r)) { + $this->tempOutput->writeln(sprintf("User with username '%s' already " + . 'exists, skipping', $r['username'])); + + $this->logger->info('One user already exists, skipping creation', [ + 'username_in_file' => $r['username'], + 'email_in_file' => $r['email'], + 'line' => $line, + ]); + + continue; + } + + $user = $this->createUser($line, $r); + $this->appendUserToFile($user); + } + } + + protected function prepareGroupingCenters() + { + $reader = Reader::createFromPath($this->tempInput->getOption('grouping-centers')); + $reader->setHeaderOffset(0); + + foreach ($reader->getRecords() as $r) { + $this->centers[$r['alias']] = + array_merge( + $this->centers[$r['alias']] ?? [], + $this->getCenters( + $r['center'] + ) + ); + } + } + + protected function prepareWriter() + { + $this->output = $output = Writer::createFromPath($this->tempInput + ->getOption('csv-dump'), 'a+'); + + $output->insertOne([ + 'email', + 'username', + 'id', + ]); } - } diff --git a/src/Bundle/ChillMainBundle/Command/ChillUserSendRenewPasswordCodeCommand.php b/src/Bundle/ChillMainBundle/Command/ChillUserSendRenewPasswordCodeCommand.php index bea2fa1a5..c94ea35c2 100644 --- a/src/Bundle/ChillMainBundle/Command/ChillUserSendRenewPasswordCodeCommand.php +++ b/src/Bundle/ChillMainBundle/Command/ChillUserSendRenewPasswordCodeCommand.php @@ -1,88 +1,94 @@ logger = $logger; $this->em = $em; $this->recoverPasswordHelper = $recoverPasswordHelper; $this->eventDispatcher = $eventDispatcher; - + parent::__construct(); } - - protected function configure() { $this @@ -91,113 +97,114 @@ class ChillUserSendRenewPasswordCodeCommand extends Command ->addArgument('csvfile', InputArgument::REQUIRED, 'CSV file with the list of users') ->addOption('template', null, InputOption::VALUE_REQUIRED, 'Template for email') ->addOption('expiration', null, InputOption::VALUE_REQUIRED, 'Expiration of the link, as an unix timestamp') - ->addOption('subject', null, InputOption::VALUE_REQUIRED, 'Subject of the email', 'Recover your password') - ; + ->addOption('subject', null, InputOption::VALUE_REQUIRED, 'Subject of the email', 'Recover your password'); } protected function execute(InputInterface $input, OutputInterface $output) { $this->input = $input; $this->output = $output; - + $reader = $this->getReader(); - - foreach($reader->getRecords() as $offset => $r) { + + foreach ($reader->getRecords() as $offset => $r) { $user = $this->getUser($r); - - if ($user === null) { + + if (null === $user) { $this->onUserNotFound($r, $offset); + continue; } - + $this->sendRecoverCode($user); } } - - protected function sendRecoverCode(User $user) - { - if (empty($user->getEmail())) { - $this->logger->alert("User without email", [ - 'user_id' => $user->getId(), - 'username' => $user->getUsername() - ]); - - return; - } - - $template = $this->input->getOption('template'); - $expiration = \DateTime::createFromFormat('U', - $this->input->getOption('expiration')); - - $this->recoverPasswordHelper - ->sendRecoverEmail( - $user, - $expiration, - $template, - [ 'expiration' => $expiration], - false, - [ '_locale' => 'fr' ], - $this->input->getOption('subject') - ); - } - - protected function onUserNotFound($row, $offset) - { - $this->logger->alert('User not found', \array_merge([ - 'offset' => $offset - ], $row)); - } - - protected function getUser($row) - { - /* @var $userRepository \Chill\MainBundle\Repository\UserRepository */ - $userRepository = $this->em->getRepository(User::class); - - try { - if (\array_key_exists('email', $row)) { - return $userRepository->findOneByUsernameOrEmail(\trim($row['email'])); - } - } catch (\Doctrine\ORM\NoResultException $e) { - // continue, we will try username - } - - try { - if (\array_key_exists('username', $row)) { - return $userRepository->findOneByUsernameOrEmail(\trim($row['username'])); - } - } catch (\Doctrine\ORM\NoResultException $e) { - return null; - } - } - + /** - * + * @throws Exception + * * @return Reader - * @throws \Exception */ protected function getReader() { try { $reader = Reader::createFromPath($this->input->getArgument('csvfile')); - } catch (\Exception $e) { - $this->logger->error("The csv file could not be read", [ - 'path' => $this->input->getArgument('csvfile') + } catch (Exception $e) { + $this->logger->error('The csv file could not be read', [ + 'path' => $this->input->getArgument('csvfile'), ]); - + throw $e; } - + $reader->setHeaderOffset(0); - + $headers = $reader->getHeader(); - - if (FALSE === \in_array('username', $headers) - && FALSE === \in_array('email', $headers)) { - throw new \InvalidArgumentException("The csv file does not have an " - . "username or email header"); + + if (false === in_array('username', $headers, true) + && false === in_array('email', $headers, true)) { + throw new InvalidArgumentException('The csv file does not have an ' + . 'username or email header'); } - + return $reader; } + protected function getUser($row) + { + /** @var \Chill\MainBundle\Repository\UserRepository $userRepository */ + $userRepository = $this->em->getRepository(User::class); + + try { + if (array_key_exists('email', $row)) { + return $userRepository->findOneByUsernameOrEmail(trim($row['email'])); + } + } catch (\Doctrine\ORM\NoResultException $e) { + // continue, we will try username + } + + try { + if (array_key_exists('username', $row)) { + return $userRepository->findOneByUsernameOrEmail(trim($row['username'])); + } + } catch (\Doctrine\ORM\NoResultException $e) { + return null; + } + } + + protected function onUserNotFound($row, $offset) + { + $this->logger->alert('User not found', array_merge([ + 'offset' => $offset, + ], $row)); + } + + protected function sendRecoverCode(User $user) + { + if (empty($user->getEmail())) { + $this->logger->alert('User without email', [ + 'user_id' => $user->getId(), + 'username' => $user->getUsername(), + ]); + + return; + } + + $template = $this->input->getOption('template'); + $expiration = DateTime::createFromFormat( + 'U', + $this->input->getOption('expiration') + ); + + $this->recoverPasswordHelper + ->sendRecoverEmail( + $user, + $expiration, + $template, + ['expiration' => $expiration], + false, + ['_locale' => 'fr'], + $this->input->getOption('subject') + ); + } } diff --git a/src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php b/src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php index a5c53a551..dfb912eb9 100644 --- a/src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php +++ b/src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php @@ -1,96 +1,90 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Command; use Doctrine\ORM\EntityManager; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Intl\Intl; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Intl\Intl; +use function in_array; /* * Load or update the languages entities command */ class LoadAndUpdateLanguagesCommand extends Command { + public const INCLUDE_ANCIENT = 'include_ancient'; + + public const INCLUDE_REGIONAL_VERSION = 'include_regional'; + + // Array of ancien languages (to exclude) + private $ancientToExclude = ['ang', 'egy', 'fro', 'goh', 'grc', 'la', 'non', 'peo', 'pro', 'sga', + 'dum', 'enm', 'frm', 'gmh', 'mga', 'akk', 'phn', 'zxx', 'got', 'und', ]; + + private $availableLanguages; /** * @var EntityManager */ private $entityManager; - - private $availableLanguages; - + // The regional version of language are language with _ in the code // This array contains regional code to not exclude - private $regionalVersionToInclude = ["ro_MD"]; + private $regionalVersionToInclude = ['ro_MD']; - // Array of ancien languages (to exclude) - private $ancientToExclude = ["ang", "egy", "fro", "goh", "grc", "la", "non", "peo", "pro", "sga", - "dum", "enm", "frm", "gmh", "mga", "akk", "phn", "zxx", "got", "und"]; - - const INCLUDE_REGIONAL_VERSION = 'include_regional'; - const INCLUDE_ANCIENT = 'include_ancient'; - /** * LoadCountriesCommand constructor. * - * @param EntityManager $entityManager * @param $availableLanguages */ public function __construct(EntityManager $entityManager, $availableLanguages) { - $this->entityManager=$entityManager; - $this->availableLanguages=$availableLanguages; + $this->entityManager = $entityManager; + $this->availableLanguages = $availableLanguages; parent::__construct(); } - - - /* - * (non-PHPdoc) + + /** + * (non-PHPdoc). + * * @see \Symfony\Component\Console\Command\Command::configure() */ protected function configure() { $this ->setName('chill:main:languages:populate') - ->setDescription('Load or update languages in db. This command does not delete existing '. + ->setDescription('Load or update languages in db. This command does not delete existing ' . 'languages, but will update names according to available languages') ->addOption( - self::INCLUDE_REGIONAL_VERSION, - null, - InputOption::VALUE_NONE, - 'Include the regional languages. The regional languages are languages with code containing _ excepted ' - . implode(',', $this->regionalVersionToInclude) . '.') + self::INCLUDE_REGIONAL_VERSION, + null, + InputOption::VALUE_NONE, + 'Include the regional languages. The regional languages are languages with code containing _ excepted ' + . implode(',', $this->regionalVersionToInclude) . '.' + ) ->addOption( - self::INCLUDE_ANCIENT, - null, - InputOption::VALUE_NONE, - 'Include the ancient languages that are languages with code ' - . implode(', ', $this->ancientToExclude) . '.') - ; + self::INCLUDE_ANCIENT, + null, + InputOption::VALUE_NONE, + 'Include the ancient languages that are languages with code ' + . implode(', ', $this->ancientToExclude) . '.' + ); } - - /* - * (non-PHPdoc) + + /** + * (non-PHPdoc). + * * @see \Symfony\Component\Console\Command\Command::execute() */ protected function execute(InputInterface $input, OutputInterface $output) @@ -98,7 +92,7 @@ class LoadAndUpdateLanguagesCommand extends Command $em = $this->entityManager; $chillAvailableLanguages = $this->availableLanguages; $languageBundle = Intl::getLanguageBundle(); - $languages = array(); + $languages = []; foreach ($chillAvailableLanguages as $avLang) { $languages[$avLang] = $languageBundle->getLanguageNames($avLang); @@ -109,25 +103,25 @@ class LoadAndUpdateLanguagesCommand extends Command foreach ($languageCodes as $code) { $excludeCode = ( ( - ! $input->getOption(self::INCLUDE_REGIONAL_VERSION) - and strpos($code, '_') - and !in_array($code, $this->regionalVersionToInclude) - ) or ( - ! $input->getOption(self::INCLUDE_ANCIENT) - and in_array($code, $this->ancientToExclude) + !$input->getOption(self::INCLUDE_REGIONAL_VERSION) + && strpos($code, '_') + && !in_array($code, $this->regionalVersionToInclude, true) + ) || ( + !$input->getOption(self::INCLUDE_ANCIENT) + && in_array($code, $this->ancientToExclude, true) ) ); $langageDB = $em->getRepository('ChillMainBundle:Language')->find($code); - if(! $excludeCode) { - if (! $langageDB) { + if (!$excludeCode) { + if (!$langageDB) { $langageDB = new \Chill\MainBundle\Entity\Language(); $langageDB->setId($code); $em->persist($langageDB); } - $avLangNames = array(); + $avLangNames = []; foreach ($chillAvailableLanguages as $avLang) { $avLangNames[$avLang] = $languages[$avLang][$code]; @@ -135,10 +129,10 @@ class LoadAndUpdateLanguagesCommand extends Command $langageDB->setName($avLangNames); } else { - if($langageDB) { + if ($langageDB) { $em->remove($langageDB); } - echo "Code excluded : ".$code." - ".$languageBundle->getLanguageName($code)."\n"; + echo 'Code excluded : ' . $code . ' - ' . $languageBundle->getLanguageName($code) . "\n"; } } diff --git a/src/Bundle/ChillMainBundle/Command/LoadCountriesCommand.php b/src/Bundle/ChillMainBundle/Command/LoadCountriesCommand.php index 036ad1de7..b47146324 100644 --- a/src/Bundle/ChillMainBundle/Command/LoadCountriesCommand.php +++ b/src/Bundle/ChillMainBundle/Command/LoadCountriesCommand.php @@ -1,96 +1,102 @@ entityManager=$entityManager; - $this->availableLanguages=$availableLanguages; + $this->entityManager = $entityManager; + $this->availableLanguages = $availableLanguages; parent::__construct(); } - - /* - * (non-PHPdoc) + + public static function prepareCountryList($languages) + { + $regionBundle = Intl::getRegionBundle(); + $countries = []; + + foreach ($languages as $language) { + $countries[$language] = $regionBundle->getCountryNames($language); + } + + $countryEntities = []; + + foreach ($countries[$languages[0]] as $countryCode => $name) { + $names = []; + + foreach ($languages as $language) { + $names[$language] = $countries[$language][$countryCode]; + } + + $country = new \Chill\MainBundle\Entity\Country(); + $country->setName($names)->setCountryCode($countryCode); + $countryEntities[] = $country; + } + + return $countryEntities; + } + + /** + * (non-PHPdoc). + * * @see \Symfony\Component\Console\Command\Command::configure() */ protected function configure() { $this->setName('chill:main:countries:populate') - ->setDescription('Load or update countries in db. This command does not delete existing countries, '. + ->setDescription('Load or update countries in db. This command does not delete existing countries, ' . 'but will update names according to available languages'); } - - /* - * (non-PHPdoc) + + /** + * (non-PHPdoc). + * * @see \Symfony\Component\Console\Command\Command::execute() */ protected function execute(InputInterface $input, OutputInterface $output) { $countries = static::prepareCountryList($this->availableLanguages); $em = $this->entityManager; - - foreach($countries as $country) { + + foreach ($countries as $country) { $countryStored = $em->getRepository('ChillMainBundle:Country') - ->findOneBy(array('countryCode' => $country->getCountryCode())); - - if (NULL === $countryStored) { + ->findOneBy(['countryCode' => $country->getCountryCode()]); + + if (null === $countryStored) { $em->persist($country); } else { $countryStored->setName($country->getName()); } } - + $em->flush(); } - - public static function prepareCountryList($languages) - { - $regionBundle = Intl::getRegionBundle(); - - foreach ($languages as $language) { - $countries[$language] = $regionBundle->getCountryNames($language); - } - - $countryEntities = array(); - - foreach ($countries[$languages[0]] as $countryCode => $name) { - $names = array(); - - foreach ($languages as $language) { - $names[$language] = $countries[$language][$countryCode]; - } - - $country = new \Chill\MainBundle\Entity\Country(); - $country->setName($names)->setCountryCode($countryCode); - $countryEntities[] = $country; - } - - return $countryEntities; - } } diff --git a/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php b/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php index 21090a61f..a5f3c8611 100644 --- a/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php +++ b/src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php @@ -1,243 +1,219 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Command; use Chill\MainBundle\Doctrine\Model\Point; use Chill\MainBundle\Entity\Country; -use Doctrine\ORM\EntityManager; +use Chill\MainBundle\Entity\PostalCode; +use Doctrine\ORM\EntityManagerInterface; +use Exception; +use RuntimeException; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Filesystem\Filesystem; -use Chill\MainBundle\Entity\PostalCode; use Symfony\Component\Validator\Validator\ValidatorInterface; +use function count; -/** - * Class LoadPostalCodesCommand - * - * @package Chill\MainBundle\Command - * @author Julien Fastré - */ class LoadPostalCodesCommand extends Command { - - /** - * @var EntityManager - */ - private $entityManager; - - /** - * @var ValidatorInterface - */ - private $validator; - - /** - * LoadPostalCodesCommand constructor. - * - * @param EntityManager $entityManager - * @param ValidatorInterface $validator - */ - public function __construct(EntityManager $entityManager, ValidatorInterface $validator) + private EntityManagerInterface $entityManager; + + private ValidatorInterface $validator; + + public function __construct(EntityManagerInterface $entityManager, ValidatorInterface $validator) { $this->entityManager = $entityManager; $this->validator = $validator; parent::__construct(); } - + protected function configure() { $this->setName('chill:main:postal-code:populate') - ->setDescription("Add the postal code from a csv file.") - ->setHelp("This script will try to avoid existing postal code " + ->setDescription('Add the postal code from a csv file.') + ->setHelp('This script will try to avoid existing postal code ' . "using the postal code and name. \n" - . "The CSV file must have the following columns: " - . "postal code, label, country code." - . "Optionally, the csv file can have the following " - . "columns after the country code: reference code, latitude, longitude, source. " - . "The latitude and longitude columns are supposed to be in WGS84 and expressed in decimal degrees. " - . "The CSV file should not have any header row.") - ->addArgument('csv_file', InputArgument::REQUIRED, "the path to " - . "the csv file. See the help for specifications.") - ->addOption( - 'delimiter', - 'd', - InputOption::VALUE_OPTIONAL, - "The delimiter character of the csv file", - ",") - ->addOption( - 'enclosure', - null, - InputOption::VALUE_OPTIONAL, - "The enclosure character of the csv file", - '"' - ) - ->addOption( - 'escape', - null, - InputOption::VALUE_OPTIONAL, - "The escape character of the csv file", - "\\" - ) - ; + . 'The CSV file must have the following columns: ' + . 'postal code, label, country code.' + . 'Optionally, the csv file can have the following ' + . 'columns after the country code: reference code, latitude, longitude, source. ' + . 'The latitude and longitude columns are supposed to be in WGS84 and expressed in decimal degrees. ' + . 'The CSV file should not have any header row.') + ->addArgument('csv_file', InputArgument::REQUIRED, 'the path to ' + . 'the csv file. See the help for specifications.') + ->addOption( + 'delimiter', + 'd', + InputOption::VALUE_OPTIONAL, + 'The delimiter character of the csv file', + ',' + ) + ->addOption( + 'enclosure', + null, + InputOption::VALUE_OPTIONAL, + 'The enclosure character of the csv file', + '"' + ) + ->addOption( + 'escape', + null, + InputOption::VALUE_OPTIONAL, + 'The escape character of the csv file', + '\\' + ); } - + protected function execute(InputInterface $input, OutputInterface $output) { - try { - $csv = $this->getCSVResource($input); - } catch (\RuntimeException $e) { - $output->writeln('Error during opening the csv file : '. - $e->getMessage().''); - } - + $csv = $this->getCSVResource($input); + if ($output->getVerbosity() === OutputInterface::VERBOSITY_VERY_VERBOSE) { $output->writeln('The content of the file is ...'); $output->write(file_get_contents($input->getArgument('csv_file'))); } - + $num = 0; $line = 0; - - while (($row = fgetcsv( - $csv, - 0, - $input->getOption('delimiter'), - $input->getOption('enclosure'), - $input->getOption('escape'))) !== false) { - - try{ + + while (false !== ($row = fgetcsv( + $csv, + 0, + $input->getOption('delimiter'), + $input->getOption('enclosure'), + $input->getOption('escape') + ))) { + try { $this->addPostalCode($row, $output); - $num++; + ++$num; } catch (ExistingPostalCodeException $ex) { - $output->writeln(' on line '.$line.' : '.$ex->getMessage().''); + $output->writeln(' on line ' . $line . ' : ' . $ex->getMessage() . ''); } catch (CountryCodeNotFoundException $ex) { - $output->writeln(' on line '.$line.' : '.$ex->getMessage().''); + $output->writeln(' on line ' . $line . ' : ' . $ex->getMessage() . ''); } catch (PostalCodeNotValidException $ex) { - $output->writeln(' on line '.$line.' : '.$ex->getMessage().''); + $output->writeln(' on line ' . $line . ' : ' . $ex->getMessage() . ''); } - $line ++; + ++$line; } - + $this->entityManager->flush(); - - $output->writeln(''.$num.' were added !'); + + $output->writeln('' . $num . ' were added !'); } - - private function getCSVResource(InputInterface $input) - { - $fs = new Filesystem(); - $filename = $input->getArgument('csv_file'); - - if (!$fs->exists($filename)) { - throw new \RuntimeException("The file does not exists or you do not " - . "have the right to read it."); - } - - $resource = fopen($filename, 'r'); - - if ($resource == FALSE) { - throw new \RuntimeException("The file '$filename' could not be opened."); - } - - return $resource; - } - + private function addPostalCode($row, OutputInterface $output) { if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { - $output->writeln('handling row: '. $row[0].' | '. $row[1].' | '. $row[2]); + $output->writeln('handling row: ' . $row[0] . ' | ' . $row[1] . ' | ' . $row[2]); } $em = $this->entityManager; $country = $em - ->getRepository(Country::class) - ->findOneBy(array('countryCode' => $row[2])); - - if ($country === NULL) { - throw new CountryCodeNotFoundException(sprintf("The country with code %s is not found. Aborting to insert postal code with %s - %s", - $row[2], $row[0], $row[1])); + ->getRepository(Country::class) + ->findOneBy(['countryCode' => $row[2]]); + + if (null === $country) { + throw new CountryCodeNotFoundException(sprintf( + 'The country with code %s is not found. Aborting to insert postal code with %s - %s', + $row[2], + $row[0], + $row[1] + )); } - + // try to find an existing postal code $existingPC = $em - ->getRepository(PostalCode::class) - ->findBy(array('code' => $row[0], 'name' => $row[1])); - + ->getRepository(PostalCode::class) + ->findBy(['code' => $row[0], 'name' => $row[1]]); + if (count($existingPC) > 0) { - throw new ExistingPostalCodeException(sprintf("A postal code with code : %s and name : %s already exists, skipping", - $row[0], $row[1])); + throw new ExistingPostalCodeException(sprintf( + 'A postal code with code : %s and name : %s already exists, skipping', + $row[0], + $row[1] + )); } - + $postalCode = (new PostalCode()) - ->setCode($row[0]) - ->setName($row[1]) - ->setCountry($country) - ; - - if (NULL != $row[3]){ + ->setCode($row[0]) + ->setName($row[1]) + ->setCountry($country); + + if (null !== $row[3]) { $postalCode->setRefPostalCodeId($row[3]); } - if (NULL != $row[4] & NULL != $row[5]){ + if (null !== $row[4] & null !== $row[5]) { $postalCode->setCenter(Point::fromLonLat((float) $row[5], (float) $row[4])); } - if (NULL != $row[6]){ + if (null !== $row[6]) { $postalCode->setPostalCodeSource($row[6]); } $errors = $this->validator->validate($postalCode); - - if ($errors->count() == 0) { + + if ($errors->count() === 0) { $em->persist($postalCode); } else { - $msg = ""; + $msg = ''; + foreach ($errors as $error) { - $msg .= " ".$error->getMessage(); + $msg .= ' ' . $error->getMessage(); } - + throw new PostalCodeNotValidException($msg); } - - - + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { - $output->writeln(sprintf('Creating postal code with code: %s, name: %s, countryCode: %s', - $postalCode->getCode(), $postalCode->getName(), $postalCode->getCountry()->getCountryCode())); + $output->writeln(sprintf( + 'Creating postal code with code: %s, name: %s, countryCode: %s', + $postalCode->getCode(), + $postalCode->getName(), + $postalCode->getCountry()->getCountryCode() + )); } } + + private function getCSVResource(InputInterface $input) + { + $fs = new Filesystem(); + $filename = $input->getArgument('csv_file'); + + if (!$fs->exists($filename)) { + throw new RuntimeException('The file does not exists or you do not ' + . 'have the right to read it.'); + } + + $resource = fopen($filename, 'rb'); + + if (false === $resource) { + throw new RuntimeException("The file '{$filename}' could not be opened."); + } + + return $resource; + } } - -class ExistingPostalCodeException extends \Exception +class ExistingPostalCodeException extends Exception { - } -class CountryCodeNotFoundException extends \Exception +class CountryCodeNotFoundException extends Exception { - } -class PostalCodeNotValidException extends \Exception +class PostalCodeNotValidException extends Exception { - } diff --git a/src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php b/src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php index 9e00bd58f..d61aed91c 100644 --- a/src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php +++ b/src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php @@ -1,101 +1,57 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Command; +use Chill\MainBundle\Entity\User; use Doctrine\ORM\EntityManager; +use LogicException; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; -use Chill\MainBundle\Entity\User; use Symfony\Component\Security\Core\Encoder\EncoderFactory; use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; -use Symfony\Component\Security\Core\Security; /** - * Class SetPasswordCommand - * - * @package Chill\MainBundle\Command - * @author Julien Fastré + * Class SetPasswordCommand. */ class SetPasswordCommand extends Command { - /** * @var EntityManager */ private $entityManager; - + /** * SetPasswordCommand constructor. - * - * @param EntityManager $entityManager */ public function __construct(EntityManager $entityManager) { $this->entityManager = $entityManager; parent::__construct(); } - - public function configure() - { - $this->setName('chill:user:set_password') - ->setDescription('set a password to user') - ->addArgument('username', InputArgument::REQUIRED, 'the user\'s ' - . 'username you want to change password') - ->addArgument('password', InputArgument::OPTIONAL, 'the new password') - ; - } - - public function execute(InputInterface $input, OutputInterface $output) - { - $user = $this->_getUser($input->getArgument('username')); - - if ($user === NULL) { - throw new \LogicException("The user with username '". - $input->getArgument('username')."' is not found"); - } - - $password = $input->getArgument('password'); - if ($password === NULL) { - $dialog = $this->getHelperSet()->get('dialog'); - $password = $dialog->askHiddenResponse($output, "the new password :" - . ""); - } - - $this->_setPassword($user, $password); - } - + public function _getUser($username) { return $this->entityManager ->getRepository('ChillMainBundle:User') - ->findOneBy(array('username' => $username)); + ->findOneBy(['username' => $username]); } - + public function _setPassword(User $user, $password) { $defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000); $encoders = [ - User::class => $defaultEncoder + User::class => $defaultEncoder, ]; $encoderFactory = new EncoderFactory($encoders); $user->setPassword( @@ -103,4 +59,33 @@ class SetPasswordCommand extends Command ); $this->entityManager->flush($user); } + + public function configure() + { + $this->setName('chill:user:set_password') + ->setDescription('set a password to user') + ->addArgument('username', InputArgument::REQUIRED, 'the user\'s ' + . 'username you want to change password') + ->addArgument('password', InputArgument::OPTIONAL, 'the new password'); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $user = $this->_getUser($input->getArgument('username')); + + if (null === $user) { + throw new LogicException("The user with username '" . + $input->getArgument('username') . "' is not found"); + } + + $password = $input->getArgument('password'); + + if (null === $password) { + $dialog = $this->getHelperSet()->get('dialog'); + $password = $dialog->askHiddenResponse($output, 'the new password :' + . ''); + } + + $this->_setPassword($user, $password); + } } diff --git a/src/Bundle/ChillMainBundle/Controller/AddressApiController.php b/src/Bundle/ChillMainBundle/Controller/AddressApiController.php index 5c90cc9a7..db87874e2 100644 --- a/src/Bundle/ChillMainBundle/Controller/AddressApiController.php +++ b/src/Bundle/ChillMainBundle/Controller/AddressApiController.php @@ -1,5 +1,14 @@ flush(); return $this->json($new, Response::HTTP_OK, [], [ - AbstractNormalizer::GROUPS => ['read'] + AbstractNormalizer::GROUPS => ['read'], ]); - } - } diff --git a/src/Bundle/ChillMainBundle/Controller/AddressReferenceAPIController.php b/src/Bundle/ChillMainBundle/Controller/AddressReferenceAPIController.php index 3ec690677..498c77c68 100644 --- a/src/Bundle/ChillMainBundle/Controller/AddressReferenceAPIController.php +++ b/src/Bundle/ChillMainBundle/Controller/AddressReferenceAPIController.php @@ -1,29 +1,83 @@ addressReferenceRepository = $addressReferenceRepository; + $this->paginatorFactory = $paginatorFactory; + } + + /** + * @Route("/api/1.0/main/address-reference/by-postal-code/{id}/search.json") + */ + public function search(PostalCode $postalCode, Request $request): JsonResponse + { + $this->denyAccessUnlessGranted('ROLE_USER'); + + if (!$request->query->has('q')) { + throw new BadRequestHttpException('You must supply a "q" parameter'); + } + + $pattern = $request->query->get('q'); + + if ('' === trim($pattern)) { + throw new BadRequestHttpException('the search pattern is empty'); + } + + $nb = $this->addressReferenceRepository->countByPostalCodePattern($postalCode, $pattern); + $paginator = $this->paginatorFactory->create($nb); + $addresses = $this->addressReferenceRepository->findByPostalCodePattern( + $postalCode, + $pattern, + false, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); + + return $this->json( + new Collection($addresses, $paginator), + Response::HTTP_OK, + [], + [AbstractNormalizer::GROUPS => ['read']] + ); + } protected function customizeQuery(string $action, Request $request, $qb): void { if ($request->query->has('postal_code')) { - $qb->where('e.postcode = :postal_code') - ->setParameter('postal_code', $request->query->get('postal_code')); - + ->setParameter('postal_code', $request->query->get('postal_code')); } - } protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format) @@ -33,5 +87,4 @@ class AddressReferenceAPIController extends ApiController return $query; } - } diff --git a/src/Bundle/ChillMainBundle/Controller/AdminController.php b/src/Bundle/ChillMainBundle/Controller/AdminController.php index 1fe10ef08..96ba627e4 100644 --- a/src/Bundle/ChillMainBundle/Controller/AdminController.php +++ b/src/Bundle/ChillMainBundle/Controller/AdminController.php @@ -1,24 +1,14 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -27,7 +17,6 @@ use Symfony\Component\Routing\Annotation\Route; class AdminController extends AbstractController { - /** * @Route("/{_locale}/admin", name="chill_main_admin_central") */ @@ -36,14 +25,13 @@ class AdminController extends AbstractController return $this->render('@ChillMain/Admin/index.html.twig'); } - public function indexPermissionsAction() - { - return $this->render('@ChillMain/Admin/layout_permissions.html.twig'); - } - public function indexLocationsAction() { return $this->render('@ChillMain/Admin/layout_location.html.twig'); } + public function indexPermissionsAction() + { + return $this->render('@ChillMain/Admin/layout_permissions.html.twig'); + } } diff --git a/src/Bundle/ChillMainBundle/Controller/AdminCountryCRUDController.php b/src/Bundle/ChillMainBundle/Controller/AdminCountryCRUDController.php index 310a36c60..a0447a1c7 100644 --- a/src/Bundle/ChillMainBundle/Controller/AdminCountryCRUDController.php +++ b/src/Bundle/ChillMainBundle/Controller/AdminCountryCRUDController.php @@ -1,20 +1,18 @@ paginatorFactory = $paginator; - } } diff --git a/src/Bundle/ChillMainBundle/Controller/CenterController.php b/src/Bundle/ChillMainBundle/Controller/CenterController.php index c85140ff4..02ea19e20 100644 --- a/src/Bundle/ChillMainBundle/Controller/CenterController.php +++ b/src/Bundle/ChillMainBundle/Controller/CenterController.php @@ -1,40 +1,30 @@ getDoctrine()->getManager(); - - $entities = $em->getRepository('ChillMainBundle:Center')->findAll(); - - return $this->render('@ChillMain/Center/index.html.twig', array( - 'entities' => $entities, - )); - } /** * Creates a new Center entity. - * */ public function createAction(Request $request) { @@ -47,71 +37,19 @@ class CenterController extends AbstractController $em->persist($center); $em->flush(); - return $this->redirect($this->generateUrl('admin_center_show', array('id' => $center->getId()))); + return $this->redirect($this->generateUrl('admin_center_show', ['id' => $center->getId()])); } - return $this->render('@ChillMain/Center/new.html.twig', array( + return $this->render('@ChillMain/Center/new.html.twig', [ 'entity' => $center, - 'form' => $form->createView(), - )); - } - - /** - * Creates a form to create a Center entity. - * - * @param Center $center The entity - * - * @return \Symfony\Component\Form\Form The form - */ - private function createCreateForm(Center $center) - { - $form = $this->createForm(CenterType::class, $center, array( - 'action' => $this->generateUrl('admin_center_create'), - 'method' => 'POST', - )); - - $form->add('submit', SubmitType::class, array('label' => 'Create')); - - return $form; - } - - /** - * Displays a form to create a new Center entity. - * - */ - public function newAction() - { - $center = new Center(); - $form = $this->createCreateForm($center); - - return $this->render('@ChillMain/Center/new.html.twig', array( - 'entity' => $center, - 'form' => $form->createView(), - )); - } - - /** - * Finds and displays a Center entity. - * - */ - public function showAction($id) - { - $em = $this->getDoctrine()->getManager(); - - $center = $em->getRepository('ChillMainBundle:Center')->find($id); - - if (!$center) { - throw $this->createNotFoundException('Unable to find Center entity.'); - } - - return $this->render('@ChillMain/Center/show.html.twig', array( - 'entity' => $center - )); + 'form' => $form->createView(), + ]); } /** * Displays a form to edit an existing Center entity. * + * @param mixed $id */ public function editAction($id) { @@ -124,33 +62,65 @@ class CenterController extends AbstractController } $editForm = $this->createEditForm($center); - return $this->render('@ChillMain/Center/edit.html.twig', array( - 'entity' => $center, - 'edit_form' => $editForm->createView() - )); + + return $this->render('@ChillMain/Center/edit.html.twig', [ + 'entity' => $center, + 'edit_form' => $editForm->createView(), + ]); } /** - * Creates a form to edit a Center entity. - * - * @param Center $center The entity - * - * @return \Symfony\Component\Form\Form The form - */ - private function createEditForm(Center $center) + * Lists all Center entities. + */ + public function indexAction() { - $form = $this->createForm(CenterType::class, $center, array( - 'action' => $this->generateUrl('admin_center_update', array('id' => $center->getId())), - 'method' => 'PUT', - )); + $em = $this->getDoctrine()->getManager(); - $form->add('submit', SubmitType::class, array('label' => 'Update')); + $entities = $em->getRepository('ChillMainBundle:Center')->findAll(); - return $form; + return $this->render('@ChillMain/Center/index.html.twig', [ + 'entities' => $entities, + ]); } + + /** + * Displays a form to create a new Center entity. + */ + public function newAction() + { + $center = new Center(); + $form = $this->createCreateForm($center); + + return $this->render('@ChillMain/Center/new.html.twig', [ + 'entity' => $center, + 'form' => $form->createView(), + ]); + } + + /** + * Finds and displays a Center entity. + * + * @param mixed $id + */ + public function showAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $center = $em->getRepository('ChillMainBundle:Center')->find($id); + + if (!$center) { + throw $this->createNotFoundException('Unable to find Center entity.'); + } + + return $this->render('@ChillMain/Center/show.html.twig', [ + 'entity' => $center, + ]); + } + /** * Edits an existing Center entity. * + * @param mixed $id */ public function updateAction(Request $request, $id) { @@ -168,12 +138,50 @@ class CenterController extends AbstractController if ($editForm->isValid()) { $em->flush(); - return $this->redirect($this->generateUrl('admin_center_edit', array('id' => $id))); + return $this->redirect($this->generateUrl('admin_center_edit', ['id' => $id])); } - return $this->render('@ChillMain/Center/edit.html.twig', array( - 'entity' => $center, - 'edit_form' => $editForm->createView() - )); + return $this->render('@ChillMain/Center/edit.html.twig', [ + 'entity' => $center, + 'edit_form' => $editForm->createView(), + ]); + } + + /** + * Creates a form to create a Center entity. + * + * @param Center $center The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createCreateForm(Center $center) + { + $form = $this->createForm(CenterType::class, $center, [ + 'action' => $this->generateUrl('admin_center_create'), + 'method' => 'POST', + ]); + + $form->add('submit', SubmitType::class, ['label' => 'Create']); + + return $form; + } + + /** + * Creates a form to edit a Center entity. + * + * @param Center $center The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createEditForm(Center $center) + { + $form = $this->createForm(CenterType::class, $center, [ + 'action' => $this->generateUrl('admin_center_update', ['id' => $center->getId()]), + 'method' => 'PUT', + ]); + + $form->add('submit', SubmitType::class, ['label' => 'Update']); + + return $form; } } diff --git a/src/Bundle/ChillMainBundle/Controller/DefaultController.php b/src/Bundle/ChillMainBundle/Controller/DefaultController.php index 8ad6d8d3a..2c8102a39 100644 --- a/src/Bundle/ChillMainBundle/Controller/DefaultController.php +++ b/src/Bundle/ChillMainBundle/Controller/DefaultController.php @@ -1,23 +1,29 @@ isGranted('ROLE_ADMIN')) { - return $this->redirectToRoute('chill_main_admin_central', [], 302); } - + return $this->render('@ChillMain/layout.html.twig'); } @@ -25,62 +31,61 @@ class DefaultController extends AbstractController { return $this->redirect($this->generateUrl('chill_main_homepage')); } - + public function testAction() { return $this->render('@ChillMain/Tabs/index.html.twig', [ 'tabs' => [ 'test1' => [ [ - 'name' => "Link 1", - 'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.", + 'name' => 'Link 1', + 'content' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.', ], [ - 'name' => "Link 2", - 'content' => "Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.", + 'name' => 'Link 2', + 'content' => 'Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.', ], [ - 'name' => "Link 3", - 'content' => "In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.", + 'name' => 'Link 3', + 'content' => 'In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.', ], [ - 'name' => "Link 4", - 'content' => "Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.", + 'name' => 'Link 4', + 'content' => 'Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.', ], [ - 'name' => "Link 5", - 'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.", - ] + 'name' => 'Link 5', + 'content' => 'Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.', + ], ], 'test2' => [ [ - 'name' => "Link 1", - 'link' => "http://localhost", - 'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.", + 'name' => 'Link 1', + 'link' => 'http://localhost', + 'content' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.', ], [ - 'name' => "Link 2", + 'name' => 'Link 2', //'link' => "http://localhost", - 'content' => "Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.", + 'content' => 'Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.', ], [ - 'name' => "Link 3", + 'name' => 'Link 3', //'link' => "http://localhost", - 'content' => "In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.", + 'content' => 'In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.', ], [ - 'name' => "Link 4", - 'link' => "http://localhost", + 'name' => 'Link 4', + 'link' => 'http://localhost', //'content' => "Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.", ], [ - 'name' => "Link 5", + 'name' => 'Link 5', //'link' => "http://localhost", - 'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.", - ] - ] - ] + 'content' => 'Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.', + ], + ], + ], ]); } - } diff --git a/src/Bundle/ChillMainBundle/Controller/ExportController.php b/src/Bundle/ChillMainBundle/Controller/ExportController.php index 71f6c0543..809d0502d 100644 --- a/src/Bundle/ChillMainBundle/Controller/ExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/ExportController.php @@ -1,86 +1,70 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Request; +use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\FormatterType; -use Symfony\Component\Form\Extension\Core\Type\FormType; use Chill\MainBundle\Form\Type\Export\PickCenterType; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; -use Chill\MainBundle\Export\ExportManager; -use Psr\Log\LoggerInterface; -use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\Form\FormFactoryInterface; use Chill\MainBundle\Redis\ChillRedis; +use LogicException; +use Psr\Log\LoggerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Translation\TranslatorInterface; +use function count; +use function serialize; +use function unserialize; /** * Class ExportController * Controller used for exporting data. - * - * @package Chill\MainBundle\Controller */ class ExportController extends AbstractController { - /** - * * @var ExportManager */ protected $exportManager; - + /** - * - * @var LoggerInterface - */ - protected $logger; - - /** - * - * @var SessionInterface - */ - protected $session; - - /** - * * @var FormFactoryInterface */ protected $formFactory; - + + /** + * @var LoggerInterface + */ + protected $logger; + /** - * * @var ChillRedis */ protected $redis; - + + /** + * @var SessionInterface + */ + protected $session; + /** - * * @var TranslatorInterface */ protected $translator; - + public function __construct( ChillRedis $chillRedis, ExportManager $exportManager, @@ -97,11 +81,63 @@ class ExportController extends AbstractController $this->translator = $translator; } - + public function downloadResultAction(Request $request, $alias) + { + /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ + $exportManager = $this->exportManager; + $key = $request->query->get('key', null); + + [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key); + + $formatterAlias = $exportManager->getFormatterAlias($dataExport['export']); + + if (null !== $formatterAlias) { + $formater = $exportManager->getFormatter($formatterAlias); + } else { + $formater = null; + } + + $viewVariables = [ + 'alias' => $alias, + 'export' => $exportManager->getExport($alias), + ]; + + if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) { + // due to a bug in php, we add the mime type in the download view + $viewVariables['mime_type'] = 'text/csv'; + } + + return $this->render('@ChillMain/Export/download.html.twig', $viewVariables); + } + /** - * Render the list of available exports + * Generate a report. + * + * This action must work with GET queries. + * + * @param string $alias + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function generateAction(Request $request, $alias) + { + /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ + $exportManager = $this->exportManager; + $key = $request->query->get('key', null); + + [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key); + + return $exportManager->generate( + $alias, + $dataCenters['centers'], + $dataExport['export'], + null !== $dataFormatter ? $dataFormatter['formatter'] : [] + ); + } + + /** + * Render the list of available exports. * - * @param Request $request * @return \Symfony\Component\HttpFoundation\Response */ public function indexAction(Request $request) @@ -109,14 +145,14 @@ class ExportController extends AbstractController $exportManager = $this->exportManager; $exports = $exportManager->getExportsGrouped(true); - - return $this->render('@ChillMain/Export/layout.html.twig', array( - 'grouped_exports' => $exports - )); + + return $this->render('@ChillMain/Export/layout.html.twig', [ + 'grouped_exports' => $exports, + ]); } /** - * handle the step to build a query for an export + * handle the step to build a query for an export. * * This action has three steps : * @@ -129,6 +165,7 @@ class ExportController extends AbstractController * * @param string $request * @param Request $alias + * * @return \Symfony\Component\HttpFoundation\Response */ public function newAction(Request $request, $alias) @@ -137,7 +174,7 @@ class ExportController extends AbstractController $exportManager = $this->exportManager; $export = $exportManager->getExport($alias); - if ($exportManager->isGrantedForElement($export) === FALSE) { + if ($exportManager->isGrantedForElement($export) === false) { throw $this->createAccessDeniedException('The user does not have access to this export'); } @@ -146,77 +183,87 @@ class ExportController extends AbstractController switch ($step) { case 'centers': return $this->selectCentersStep($request, $export, $alias); + case 'export': return $this->exportFormStep($request, $export, $alias); + break; + case 'formatter': return $this->formatterFormStep($request, $export, $alias); + break; + case 'generate': return $this->forwardToGenerate($request, $export, $alias); + break; + default: - throw $this->createNotFoundException("The given step '$step' is invalid"); + throw $this->createNotFoundException("The given step '{$step}' is invalid"); } } /** - * - * @param Request $request - * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * create a form to show on different steps. + * * @param string $alias - * @return Response - * @throws type + * @param array $data the data from previous step. Required for steps 'formatter' and 'generate_formatter' + * @param mixed $step + * + * @return \Symfony\Component\Form\Form */ - protected function selectCentersStep(Request $request, $export, $alias) + protected function createCreateFormExport($alias, $step, $data = []) { - /* @var $exportManager \Chill\MainBundle\Export\ExportManager */ + /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ $exportManager = $this->exportManager; + $isGenerate = strpos($step, 'generate_') === 0; - $form = $this->createCreateFormExport($alias, 'centers'); + $builder = $this->formFactory + ->createNamedBuilder(null, FormType::class, [], [ + 'method' => $isGenerate ? 'GET' : 'POST', + 'csrf_protection' => $isGenerate ? false : true, + ]); - if ($request->getMethod() === 'POST') { - $form->handleRequest($request); - if ($form->isValid()) { - $this->logger->debug('form centers is valid', array( - 'location' => __METHOD__)); - - $data = $form->getData(); - - // check ACL - if ($exportManager->isGrantedForElement($export, NULL, - $exportManager->getPickedCenters($data['centers'])) === FALSE) { - throw $this->createAccessDeniedException('you do not have ' - . 'access to this export for those centers'); - } - - $this->session->set('centers_step_raw', - $request->request->all()); - $this->session->set('centers_step', $data); - - return $this->redirectToRoute('chill_main_export_new', array( - 'step' => $this->getNextStep('centers', $export), - 'alias' => $alias - )); - - } + if ('centers' === $step || 'generate_centers' === $step) { + $builder->add('centers', PickCenterType::class, [ + 'export_alias' => $alias, + ]); } - return $this->render('@ChillMain/Export/new_centers_step.html.twig', - array( - 'form' => $form->createView(), - 'export' => $export - )); + if ('export' === $step || 'generate_export' === $step) { + $builder->add('export', ExportType::class, [ + 'export_alias' => $alias, + 'picked_centers' => $exportManager->getPickedCenters($data['centers']), + ]); + } + + if ('formatter' === $step || 'generate_formatter' === $step) { + $builder->add('formatter', FormatterType::class, [ + 'formatter_alias' => $exportManager + ->getFormatterAlias($data['export']), + 'export_alias' => $alias, + 'aggregator_aliases' => $exportManager + ->getUsedAggregatorsAliases($data['export']), + ]); + } + + $builder->add('submit', SubmitType::class, [ + 'label' => 'Generate', + ]); + + return $builder->getForm(); } /** - * Render the export form + * Render the export form. * * When the method is POST, the form is stored if valid, and a redirection * is done to next step. * * @param string $alias - * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export + * * @return \Symfony\Component\HttpFoundation\Response */ protected function exportFormStep(Request $request, $export, $alias) @@ -226,12 +273,11 @@ class ExportController extends AbstractController // check we have data from the previous step (export step) $data = $this->session->get('centers_step', null); - if ($data === null) { - - return $this->redirectToRoute('chill_main_export_new', array( - 'step' => $this->getNextStep('export', $export, true), - 'alias' => $alias - )); + if (null === $data) { + return $this->redirectToRoute('chill_main_export_new', [ + 'step' => $this->getNextStep('export', $export, true), + 'alias' => $alias, + ]); } $export = $exportManager->getExport($alias); @@ -240,123 +286,36 @@ class ExportController extends AbstractController if ($request->getMethod() === 'POST') { $form->handleRequest($request); - if ($form->isValid()) { - $this->logger->debug('form export is valid', array( - 'location' => __METHOD__)); + if ($form->isValid()) { + $this->logger->debug('form export is valid', [ + 'location' => __METHOD__, ]); // store data for reusing in next steps $data = $form->getData(); - $this->session->set('export_step_raw', - $request->request->all()); + $this->session->set( + 'export_step_raw', + $request->request->all() + ); $this->session->set('export_step', $data); //redirect to next step return $this->redirect( - $this->generateUrl('chill_main_export_new', array( - 'step' => $this->getNextStep('export', $export), - 'alias' => $alias - ))); - } else { - $this->logger->debug('form export is invalid', array( - 'location' => __METHOD__)); + $this->generateUrl('chill_main_export_new', [ + 'step' => $this->getNextStep('export', $export), + 'alias' => $alias, + ]) + ); } + $this->logger->debug('form export is invalid', [ + 'location' => __METHOD__, ]); } - return $this->render('@ChillMain/Export/new.html.twig', array( + return $this->render('@ChillMain/Export/new.html.twig', [ 'form' => $form->createView(), 'export_alias' => $alias, - 'export' => $export - )); - } - - /** - * create a form to show on different steps. - * - * @param string $alias - * @param string $step, can either be 'export', 'formatter', 'generate_export' or 'generate_formatter' (last two are used by generate action) - * @param array $data the data from previous step. Required for steps 'formatter' and 'generate_formatter' - * @return \Symfony\Component\Form\Form - */ - protected function createCreateFormExport($alias, $step, $data = array()) - { - /* @var $exportManager \Chill\MainBundle\Export\ExportManager */ - $exportManager = $this->exportManager; - $isGenerate = strpos($step, 'generate_') === 0; - - $builder = $this->formFactory - ->createNamedBuilder(null, FormType::class, array(), array( - 'method' => $isGenerate ? 'GET' : 'POST', - 'csrf_protection' => $isGenerate ? false : true, - )); - - if ($step === 'centers' or $step === 'generate_centers') { - $builder->add('centers', PickCenterType::class, array( - 'export_alias' => $alias - )); - } - - if ($step === 'export' or $step === 'generate_export') { - $builder->add('export', ExportType::class, array( - 'export_alias' => $alias, - 'picked_centers' => $exportManager->getPickedCenters($data['centers']) - )); - } - - if ($step === 'formatter' or $step === 'generate_formatter') { - $builder->add('formatter', FormatterType::class, array( - 'formatter_alias' => $exportManager - ->getFormatterAlias($data['export']), - 'export_alias' => $alias, - 'aggregator_aliases' => $exportManager - ->getUsedAggregatorsAliases($data['export']) - )); - } - - $builder->add('submit', SubmitType::class, array( - 'label' => 'Generate' - )); - - return $builder->getForm(); - } - - /** - * get the next step. If $reverse === true, the previous step is returned. - * - * This method provides a centralized way of handling next/previous step. - * - * @param string $step the current step - * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export - * @param boolean $reverse set to true to get the previous step - * @return string the next/current step - * @throws \LogicException if there is no step before or after the given step - */ - private function getNextStep($step, $export, $reverse = false) - { - switch($step) { - case 'centers': - if ($reverse !== false) { - throw new \LogicException("there is no step before 'export'"); - } - return 'export'; - case 'export': - if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { - return $reverse ? 'centers' : 'formatter'; - } elseif ($export instanceof \Chill\MainBundle\Export\DirectExportInterface) { - return $reverse ? 'centers' : 'generate'; - } - - case 'formatter' : - return $reverse ? 'export' : 'generate'; - case 'generate' : - if ($reverse === false) { - throw new \LogicException("there is no step after 'generate'"); - } - return 'formatter'; - - default: - throw new \LogicException("the step $step is not defined."); - } + 'export' => $export, + ]); } /** @@ -365,23 +324,21 @@ class ExportController extends AbstractController * If the form is posted and valid, store the data in session and * redirect to the next step. * - * @param Request $request - * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * @param string $alias + * * @return \Symfony\Component\HttpFoundation\Response */ protected function formatterFormStep(Request $request, $export, $alias) { - // check we have data from the previous step (export step) $data = $this->session->get('export_step', null); - if ($data === null) { - - return $this->redirectToRoute('chill_main_export_new', array( - 'step' => $this->getNextStep('formatter', $export, true), - 'alias' => $alias - )); + if (null === $data) { + return $this->redirectToRoute('chill_main_export_new', [ + 'step' => $this->getNextStep('formatter', $export, true), + 'alias' => $alias, + ]); } $form = $this->createCreateFormExport($alias, 'formatter', $data); @@ -392,23 +349,29 @@ class ExportController extends AbstractController if ($form->isValid()) { $dataFormatter = $form->getData(); $this->session->set('formatter_step', $dataFormatter); - $this->session->set('formatter_step_raw', - $request->request->all()); + $this->session->set( + 'formatter_step_raw', + $request->request->all() + ); //redirect to next step - return $this->redirect($this->generateUrl('chill_main_export_new', - array( - 'alias' => $alias, - 'step' => $this->getNextStep('formatter', $export) - ))); + return $this->redirect($this->generateUrl( + 'chill_main_export_new', + [ + 'alias' => $alias, + 'step' => $this->getNextStep('formatter', $export), + ] + )); } } - return $this->render('@ChillMain/Export/new_formatter_step.html.twig', - array( - 'form' => $form->createView(), - 'export' => $export - )); + return $this->render( + '@ChillMain/Export/new_formatter_step.html.twig', + [ + 'form' => $form->createView(), + 'export' => $export, + ] + ); } /** @@ -417,9 +380,9 @@ class ExportController extends AbstractController * * The data from previous steps is removed from session. * - * @param Request $request - * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * @param string $alias + * * @return \Symfony\Component\HttpFoundation\RedirectResponse */ protected function forwardToGenerate(Request $request, $export, $alias) @@ -428,22 +391,22 @@ class ExportController extends AbstractController $dataFormatter = $this->session->get('formatter_step_raw', null); $dataExport = $this->session->get('export_step_raw', null); - if ($dataFormatter === NULL and $export instanceof \Chill\MainBundle\Export\ExportInterface) { - return $this->redirectToRoute('chill_main_export_new', array( - 'alias' => $alias, 'step' => $this->getNextStep('generate', $export, true) - )); + if (null === $dataFormatter && $export instanceof \Chill\MainBundle\Export\ExportInterface) { + return $this->redirectToRoute('chill_main_export_new', [ + 'alias' => $alias, 'step' => $this->getNextStep('generate', $export, true), + ]); } - + $parameters = [ - 'formatter' => $dataFormatter ?? [], - 'export' => $dataExport ?? [], - 'centers' => $dataCenters ?? [], - 'alias' => $alias - ]; + 'formatter' => $dataFormatter ?? [], + 'export' => $dataExport ?? [], + 'centers' => $dataCenters ?? [], + 'alias' => $alias, + ]; unset($parameters['_token']); - $key = md5(uniqid(rand(), false)); - - $this->redis->setEx($key, 3600, \serialize($parameters)); + $key = md5(uniqid(mt_rand(), false)); + + $this->redis->setEx($key, 3600, serialize($parameters)); // remove data from session $this->session->remove('export_step_raw'); @@ -451,56 +414,30 @@ class ExportController extends AbstractController $this->session->remove('formatter_step_raw'); $this->session->remove('formatter_step'); - return $this->redirectToRoute('chill_main_export_download', [ 'key' => $key, 'alias' => $alias ]); + return $this->redirectToRoute('chill_main_export_download', ['key' => $key, 'alias' => $alias]); } - /** - * Generate a report. - * - * This action must work with GET queries. - * - * @param Request $request - * @param string $alias - * @return \Symfony\Component\HttpFoundation\Response - */ - public function generateAction(Request $request, $alias) - { - /* @var $exportManager \Chill\MainBundle\Export\ExportManager */ - $exportManager = $this->exportManager; - $key = $request->query->get('key', null); - - list($dataCenters, $dataExport, $dataFormatter) = $this->rebuildData($key); - - $r = $exportManager->generate( - $alias, - $dataCenters['centers'], - $dataExport['export'], - $dataFormatter !== NULL ? $dataFormatter['formatter'] : [] - ); - - return $r; - } - protected function rebuildData($key) { - if ($key === NULL) { - throw $this->createNotFoundException("key does not exists"); + if (null === $key) { + throw $this->createNotFoundException('key does not exists'); } - + if ($this->redis->exists($key) !== 1) { - $this->addFlash('error', $this->translator->trans("This report is not available any more")); - throw $this->createNotFoundException("key does not exists"); + $this->addFlash('error', $this->translator->trans('This report is not available any more')); + + throw $this->createNotFoundException('key does not exists'); } - + $serialized = $this->redis->get($key); - - if ($serialized === false) { - throw new \LogicException("the key could not be reached from redis"); + + if (false === $serialized) { + throw new LogicException('the key could not be reached from redis'); } - - $rawData = \unserialize($serialized); + + $rawData = unserialize($serialized); $alias = $rawData['alias']; - + $formCenters = $this->createCreateFormExport($alias, 'generate_centers'); $formCenters->submit($rawData['centers']); $dataCenters = $formCenters->getData(); @@ -510,40 +447,119 @@ class ExportController extends AbstractController $dataExport = $formExport->getData(); if (count($rawData['formatter']) > 0) { - $formFormatter = $this->createCreateFormExport($alias, 'generate_formatter', - $dataExport); + $formFormatter = $this->createCreateFormExport( + $alias, + 'generate_formatter', + $dataExport + ); $formFormatter->submit($rawData['formatter']); $dataFormatter = $formFormatter->getData(); } - + return [$dataCenters, $dataExport, $dataFormatter ?? null]; } - public function downloadResultAction(Request $request, $alias) - { - /* @var $exportManager \Chill\MainBundle\Export\ExportManager */ + /** + * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export + * @param string $alias + * + * @throws type + * + * @return Response + */ + protected function selectCentersStep(Request $request, $export, $alias) + { + /** @var \Chill\MainBundle\Export\ExportManager $exportManager */ $exportManager = $this->exportManager; - $key = $request->query->get('key', null); - - list($dataCenters, $dataExport, $dataFormatter) = $this->rebuildData($key); - - $formatterAlias = $exportManager->getFormatterAlias($dataExport['export']); - if ($formatterAlias !== null) { - $formater = $exportManager->getFormatter($formatterAlias); - } else { - $formater = null; + + $form = $this->createCreateFormExport($alias, 'centers'); + + if ($request->getMethod() === 'POST') { + $form->handleRequest($request); + + if ($form->isValid()) { + $this->logger->debug('form centers is valid', [ + 'location' => __METHOD__, ]); + + $data = $form->getData(); + + // check ACL + if ($exportManager->isGrantedForElement( + $export, + null, + $exportManager->getPickedCenters($data['centers']) + ) === false) { + throw $this->createAccessDeniedException('you do not have ' + . 'access to this export for those centers'); + } + + $this->session->set( + 'centers_step_raw', + $request->request->all() + ); + $this->session->set('centers_step', $data); + + return $this->redirectToRoute('chill_main_export_new', [ + 'step' => $this->getNextStep('centers', $export), + 'alias' => $alias, + ]); + } } - - $viewVariables = [ - 'alias' => $alias, - 'export' => $exportManager->getExport($alias) - ]; - - if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) { - // due to a bug in php, we add the mime type in the download view - $viewVariables['mime_type'] = 'text/csv'; + + return $this->render( + '@ChillMain/Export/new_centers_step.html.twig', + [ + 'form' => $form->createView(), + 'export' => $export, + ] + ); + } + + /** + * get the next step. If $reverse === true, the previous step is returned. + * + * This method provides a centralized way of handling next/previous step. + * + * @param string $step the current step + * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export + * @param bool $reverse set to true to get the previous step + * + * @throws LogicException if there is no step before or after the given step + * + * @return string the next/current step + */ + private function getNextStep($step, $export, $reverse = false) + { + switch ($step) { + case 'centers': + if (false !== $reverse) { + throw new LogicException("there is no step before 'export'"); + } + + return 'export'; + + case 'export': + if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { + return $reverse ? 'centers' : 'formatter'; + } + + if ($export instanceof \Chill\MainBundle\Export\DirectExportInterface) { + return $reverse ? 'centers' : 'generate'; + } + + // no break + case 'formatter': + return $reverse ? 'export' : 'generate'; + + case 'generate': + if (false === $reverse) { + throw new LogicException("there is no step after 'generate'"); + } + + return 'formatter'; + + default: + throw new LogicException("the step {$step} is not defined."); } - - return $this->render("@ChillMain/Export/download.html.twig", $viewVariables); } } diff --git a/src/Bundle/ChillMainBundle/Controller/LocationApiController.php b/src/Bundle/ChillMainBundle/Controller/LocationApiController.php index 5c0301959..525475e3c 100644 --- a/src/Bundle/ChillMainBundle/Controller/LocationApiController.php +++ b/src/Bundle/ChillMainBundle/Controller/LocationApiController.php @@ -1,33 +1,31 @@ andWhere($query->expr()->orX( - $query->expr()->andX( - $query->expr()->eq('e.createdBy', ':user'), - $query->expr()->gte('e.createdAt', ':dateBefore') - ), + $query->andWhere( $query->expr()->andX( $query->expr()->eq('e.availableForUsers', "'TRUE'"), - $query->expr()->eq('e.active', "'TRUE'") + $query->expr()->eq('e.active', "'TRUE'"), ) - )) - ->setParameters([ - 'user' => $this->getUser(), - 'dateBefore' => (new \DateTime())->sub(new \DateInterval('P6M')) - ]); + ); } } diff --git a/src/Bundle/ChillMainBundle/Controller/LocationController.php b/src/Bundle/ChillMainBundle/Controller/LocationController.php index fb1c1c61a..10087ec1e 100644 --- a/src/Bundle/ChillMainBundle/Controller/LocationController.php +++ b/src/Bundle/ChillMainBundle/Controller/LocationController.php @@ -1,5 +1,14 @@ where('e.availableForUsers = true'); //TODO not working - } - protected function createEntity(string $action, Request $request): object { $entity = parent::createEntity($action, $request); @@ -20,4 +24,9 @@ class LocationController extends CRUDController return $entity; } + + protected function customizeQuery(string $action, Request $request, $query): void + { + $query->where('e.availableForUsers = true'); //TODO not working + } } diff --git a/src/Bundle/ChillMainBundle/Controller/LocationTypeApiController.php b/src/Bundle/ChillMainBundle/Controller/LocationTypeApiController.php new file mode 100644 index 000000000..1c7fbdb89 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/LocationTypeApiController.php @@ -0,0 +1,31 @@ +andWhere( + $query->expr()->andX( + $query->expr()->eq('e.availableForUsers', "'TRUE'"), + $query->expr()->eq('e.active', "'TRUE'"), + ) + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/LocationTypeController.php b/src/Bundle/ChillMainBundle/Controller/LocationTypeController.php index 4f87cc300..542282260 100644 --- a/src/Bundle/ChillMainBundle/Controller/LocationTypeController.php +++ b/src/Bundle/ChillMainBundle/Controller/LocationTypeController.php @@ -1,10 +1,18 @@ helper = $helper; } - + /** - * Show a login form - * - * @param Request $request + * Show a login form. + * * @return Response */ public function loginAction(Request $request) { - - return $this->render('@ChillMain/Login/login.html.twig', array( + return $this->render('@ChillMain/Login/login.html.twig', [ 'last_username' => $this->helper->getLastUsername(), - 'error' => $this->helper->getLastAuthenticationError() - )); + 'error' => $this->helper->getLastAuthenticationError(), + ]); } - + public function LoginCheckAction(Request $request) { - } - } diff --git a/src/Bundle/ChillMainBundle/Controller/MenuController.php b/src/Bundle/ChillMainBundle/Controller/MenuController.php index 8820c8356..81a80f485 100644 --- a/src/Bundle/ChillMainBundle/Controller/MenuController.php +++ b/src/Bundle/ChillMainBundle/Controller/MenuController.php @@ -1,23 +1,30 @@ render($layout, array( + return $this->render($layout, [ 'menu_composer' => $this->get('chill.main.menu_composer'), 'menu' => $menu, 'args' => $args, - 'activeRouteKey' => $activeRouteKey - )); + 'activeRouteKey' => $activeRouteKey, + ]); } } diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php index c76e19d9e..4eed79740 100644 --- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php +++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php @@ -1,14 +1,22 @@ security = $security; } - /** * @Route("/show", name="chill_main_notification_show") */ public function showAction( - NotificationRepository $notificationRepository, NotificationRenderer $notificationRenderer, - PaginatorFactory $paginatorFactory) - { + NotificationRepository $notificationRepository, + NotificationRenderer $notificationRenderer, + PaginatorFactory $paginatorFactory + ) { $currentUser = $this->security->getUser(); $notificationsNbr = $notificationRepository->countAllForAttendee(($currentUser)); @@ -37,15 +45,17 @@ class NotificationController extends AbstractController $notifications = $notificationRepository->findAllForAttendee( $currentUser, - $limit=$paginator->getItemsPerPage(), - $offset= $paginator->getCurrentPage()->getFirstItemNumber()); + $limit = $paginator->getItemsPerPage(), + $offset = $paginator->getCurrentPage()->getFirstItemNumber() + ); + + $templateData = []; - $templateData = array(); foreach ($notifications as $notification) { $data = [ 'template' => $notificationRenderer->getTemplate($notification), 'template_data' => $notificationRenderer->getTemplateData($notification), - 'notification' => $notification + 'notification' => $notification, ]; $templateData[] = $data; } diff --git a/src/Bundle/ChillMainBundle/Controller/PasswordController.php b/src/Bundle/ChillMainBundle/Controller/PasswordController.php index a5cab5b9f..2dd700a89 100644 --- a/src/Bundle/ChillMainBundle/Controller/PasswordController.php +++ b/src/Bundle/ChillMainBundle/Controller/PasswordController.php @@ -1,72 +1,73 @@ recoverPasswordHelper = $recoverPasswordHelper; $this->eventDispatcher = $eventDispatcher; } - + + /** + * @return Response + */ + public function changeConfirmedAction() + { + return $this->render('@ChillMain/Password/recover_password_changed.html.twig'); + } + + /** + * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse + */ + public function recoverAction(Request $request) + { + if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { + return new Response($this->translator->trans('You are not allowed ' + . 'to try to recover password, due to mitigating possible ' + . 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN); + } + + $query = $request->query; + $username = $query->get(TokenManager::USERNAME_CANONICAL); + $hash = $query->getAlnum(TokenManager::HASH); + $token = $query->getAlnum(TokenManager::TOKEN); + $timestamp = $query->getAlnum(TokenManager::TIMESTAMP); + $user = $this->getDoctrine()->getRepository(User::class) + ->findOneByUsernameCanonical($username); + + if (null === $user) { + $this->eventDispatcher->dispatch( + PasswordRecoverEvent::INVALID_TOKEN, + new PasswordRecoverEvent($token, null, $request->getClientIp()) + ); + + throw $this->createNotFoundException(sprintf('User %s not found', $username)); + } + + if (true !== $this->tokenManager->verify($hash, $token, $user, $timestamp)) { + $this->eventDispatcher->dispatch( + PasswordRecoverEvent::INVALID_TOKEN, + new PasswordRecoverEvent($token, $user, $request->getClientIp()) + ); + + return new Response('Invalid token', Response::HTTP_FORBIDDEN); + } + + $form = $this->passwordForm($user); + $form->remove('actual_password'); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $password = $form->get('new_password')->getData(); + $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); + // logging for prod + $this + ->chillLogger + ->notice( + 'setting new password for user', + [ + 'user' => $user->getUsername(), + ] + ); + + $this->getDoctrine()->getManager()->flush(); + + return $this->redirectToRoute('password_request_recover_changed'); + } + + return $this->render('@ChillMain/Password/recover_password_form.html.twig', [ + 'form' => $form->createView(), + ]); + } + + /** + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + * + * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse + */ + public function requestRecoverAction(Request $request) + { + if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { + return new Response($this->translator->trans('You are not allowed ' + . 'to try to recover password, due to mitigating possible ' + . 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN); + } + + $form = $this->requestRecoverForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var \Doctrine\ORM\QueryBuilder $qb */ + $qb = $this->getDoctrine()->getManager() + ->createQueryBuilder(); + $qb->select('u') + ->from(User::class, 'u') + ->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))')) + ->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))')) + ->setParameter('pattern', $form->get('username_or_email')->getData()); + + $user = $qb->getQuery()->getSingleResult(); + + if (empty($user->getEmail())) { + $this->addFlash('error', $this->translator->trans('This account does not have an email address. ' + . 'Please ask your administrator to renew your password.')); + } else { + if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN, $user)) { + return new Response($this->translator->trans('You are not allowed ' + . 'to try to recover password, due to mitigating possible ' + . 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN); + } + + $this->recoverPasswordHelper->sendRecoverEmail( + $user, + (new DateTimeImmutable('now'))->add(new DateInterval('PT30M')) + ); + + // logging for prod + $this + ->chillLogger + ->notice( + 'Sending an email for password recovering', + [ + 'user' => $user->getUsername(), + ] + ); + + $this->eventDispatcher->dispatch( + PasswordRecoverEvent::ASK_TOKEN_SUCCESS, + new PasswordRecoverEvent(null, $user, $request->getClientIp()) + ); + + return $this->redirectToRoute('password_request_recover_confirm'); + } + } elseif ($form->isSubmitted() && false === $form->isValid()) { + $this->eventDispatcher->dispatch( + PasswordRecoverEvent::ASK_TOKEN_INVALID_FORM, + new PasswordRecoverEvent(null, null, $request->getClientIp()) + ); + } + + return $this->render('@ChillMain/Password/request_recover_password.html.twig', [ + 'form' => $form->createView(), + ]); + } + + /** + * @return Response + */ + public function requestRecoverConfirmAction() + { + return $this->render('@ChillMain/Password/request_recover_password_confirm.html.twig'); + } + /** - * @param Request $request * @return Response */ public function UserPasswordAction(Request $request) @@ -107,11 +262,11 @@ class PasswordController extends AbstractController ->chillLogger ->notice( 'update password for an user', - array( - 'method' => $request->getMethod(), - 'user' => $user->getUsername() - ) - ); + [ + 'method' => $request->getMethod(), + 'user' => $user->getUsername(), + ] + ); $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); @@ -119,186 +274,16 @@ class PasswordController extends AbstractController $em->flush(); $this->addFlash('success', $this->translator->trans('Password successfully updated!')); - - return $this->redirectToRoute('change_my_password'); + return $this->redirectToRoute('change_my_password'); } // render into a template - return $this->render('@ChillMain/Password/password.html.twig', array( - 'form' => $form->createView() - )); - - } - - /** - * @param User $user - * @return \Symfony\Component\Form\Form - */ - private function passwordForm(User $user) - { - return $this - ->createForm( - UserPasswordType::class, - [], - [ 'user' => $user ] - ) - ->add('submit', SubmitType::class, array('label' => 'Change password')) - ; - } - - /** - * @param Request $request - * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response - */ - public function recoverAction(Request $request) - { - if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { - return (new Response($this->translator->trans("You are not allowed " - . "to try to recover password, due to mitigating possible " - . "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN)); - } - - $query = $request->query; - $username = $query->get(TokenManager::USERNAME_CANONICAL); - $hash = $query->getAlnum(TokenManager::HASH); - $token = $query->getAlnum(TokenManager::TOKEN); - $timestamp = $query->getInt(TokenManager::TIMESTAMP); - $user = $this->getDoctrine()->getRepository(User::class) - ->findOneByUsernameCanonical($username); - - if (NULL === $user) { - $this->eventDispatcher->dispatch(PasswordRecoverEvent::INVALID_TOKEN, - new PasswordRecoverEvent($token, null, $request->getClientIp())); - - throw $this->createNotFoundException(sprintf('User %s not found', $username)); - } - - if (TRUE !== $this->tokenManager->verify($hash, $token, $user, $timestamp)) { - $this->eventDispatcher->dispatch(PasswordRecoverEvent::INVALID_TOKEN, - new PasswordRecoverEvent($token, $user, $request->getClientIp())); - - return new Response("Invalid token", Response::HTTP_FORBIDDEN); - } - - $form = $this->passwordForm($user); - $form->remove('actual_password'); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $password = $form->get('new_password')->getData(); - $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); - // logging for prod - $this - ->chillLogger - ->notice( - 'setting new password for user', - array( - 'user' => $user->getUsername() - ) - ); - - $this->getDoctrine()->getManager()->flush(); - - return $this->redirectToRoute('password_request_recover_changed'); - } - - return $this->render('@ChillMain/Password/recover_password_form.html.twig', [ - 'form' => $form->createView() - ]); - - } - - /** - * @return Response - */ - public function changeConfirmedAction() - { - return $this->render('@ChillMain/Password/recover_password_changed.html.twig'); - } - - /** - * @param Request $request - * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response - * @throws \Doctrine\ORM\NoResultException - * @throws \Doctrine\ORM\NonUniqueResultException - */ - public function requestRecoverAction(Request $request) - { - if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { - return (new Response($this->translator->trans("You are not allowed " - . "to try to recover password, due to mitigating possible " - . "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN)); - } - - $form = $this->requestRecoverForm(); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - /* @var $qb \Doctrine\ORM\QueryBuilder */ - $qb = $this->getDoctrine()->getManager() - ->createQueryBuilder(); - $qb->select('u') - ->from(User::class, 'u') - ->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))')) - ->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))' )) - ->setParameter('pattern', $form->get('username_or_email')->getData()) - ; - - $user = $qb->getQuery()->getSingleResult(); - - if (empty($user->getEmail())) { - $this->addFlash('error', $this->translator->trans('This account does not have an email address. ' - . 'Please ask your administrator to renew your password.')); - } else { - if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN, $user)) { - return (new Response($this->translator->trans("You are not allowed " - . "to try to recover password, due to mitigating possible " - . "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN)); - } - - $this->recoverPasswordHelper->sendRecoverEmail($user, - (new \DateTimeImmutable('now'))->add(new \DateInterval('PT30M'))); - - // logging for prod - $this - ->chillLogger - ->notice( - 'Sending an email for password recovering', - array( - 'user' => $user->getUsername() - ) - ); - - $this->eventDispatcher->dispatch( - PasswordRecoverEvent::ASK_TOKEN_SUCCESS, - new PasswordRecoverEvent(null, $user, $request->getClientIp()) - ); - - return $this->redirectToRoute('password_request_recover_confirm'); - } - } elseif ($form->isSubmitted() && FALSE === $form->isValid()) { - $this->eventDispatcher->dispatch( - PasswordRecoverEvent::ASK_TOKEN_INVALID_FORM, - new PasswordRecoverEvent(null, null, $request->getClientIp()) - ); - } - - return $this->render('@ChillMain/Password/request_recover_password.html.twig', [ - 'form' => $form->createView() + return $this->render('@ChillMain/Password/password.html.twig', [ + 'form' => $form->createView(), ]); } - - /** - * @return Response - */ - public function requestRecoverConfirmAction() - { - return $this->render('@ChillMain/Password/request_recover_password_confirm.html.twig'); - } - + /** * @return \Symfony\Component\Form\FormInterface */ @@ -310,27 +295,40 @@ class PasswordController extends AbstractController 'label' => 'Username or email', 'constraints' => [ new Callback([ - 'callback' => function($pattern, ExecutionContextInterface $context, $payload) { + 'callback' => function ($pattern, ExecutionContextInterface $context, $payload) { $qb = $this->getDoctrine()->getManager() - ->createQueryBuilder(); + ->createQueryBuilder(); $qb->select('COUNT(u)') ->from(User::class, 'u') ->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))')) - ->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))' )) - ->setParameter('pattern', $pattern) - ; + ->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))')) + ->setParameter('pattern', $pattern); if ((int) $qb->getQuery()->getSingleScalarResult() !== 1) { $context->addViolation('This username or email does not exists'); } - } - ]) - ] + }, + ]), + ], ]) ->add('submit', SubmitType::class, [ - 'label' => 'Request recover' + 'label' => 'Request recover', ]); - + return $builder->getForm(); } + + /** + * @return \Symfony\Component\Form\Form + */ + private function passwordForm(User $user) + { + return $this + ->createForm( + UserPasswordType::class, + [], + ['user' => $user] + ) + ->add('submit', SubmitType::class, ['label' => 'Change password']); + } } diff --git a/src/Bundle/ChillMainBundle/Controller/PermissionApiController.php b/src/Bundle/ChillMainBundle/Controller/PermissionApiController.php new file mode 100644 index 000000000..6a2afccb2 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/PermissionApiController.php @@ -0,0 +1,82 @@ +denormalizer = $denormalizer; + $this->security = $security; + } + + /** + * @Route("/api/1.0/main/permissions/info.json", methods={"POST"}) + * + * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface + */ + public function getPermissions(Request $request): JsonResponse + { + $this->denyAccessUnlessGranted('ROLE_USER'); + + $data = json_decode($request->getContent(), true); + + if (null === $data) { + throw new BadRequestHttpException(sprintf( + 'Could not decode json received, or data invalid: %s, %s', + json_last_error(), + json_last_error_msg() + )); + } + + if (!array_key_exists('object', $data)) { + throw new BadRequestHttpException('the object key is not present'); + } + + if (!array_key_exists('class', $data)) { + throw new BadRequestHttpException('the class key is not present'); + } + + if (null !== $data['object']) { + $object = $this->denormalizer->denormalize($data['object'], $data['class'], 'json'); + } else { + $object = null; + } + $roles = []; + + foreach (($data['roles'] ?? []) as $role) { + $roles[$role] = $this->security->isGranted($role, $object); + } + + return $this->json( + ['roles' => $roles], + 200, + [], + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php b/src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php index 0a46c4496..8cffd02b3 100644 --- a/src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php +++ b/src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php @@ -1,62 +1,65 @@ translatableStringHelper = $translatableStringHelper; $this->roleProvider = $roleProvider; $this->roleHierarchy = $roleHierarchy; $this->translator = $translator; $this->validator = $validator; } - + /** - * Lists all PermissionsGroup entities. + * @param int $id * + * @throws type + * + * @return Respon */ - public function indexAction() + public function addLinkRoleScopeAction(Request $request, $id) { $em = $this->getDoctrine()->getManager(); - $entities = $em->getRepository('ChillMainBundle:PermissionsGroup')->findAll(); + $permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id); - return $this->render('@ChillMain/PermissionsGroup/index.html.twig', array( - 'entities' => $entities, - )); + if (!$permissionsGroup) { + throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); + } + + $form = $this->createAddRoleScopeForm($permissionsGroup); + $form->handleRequest($request); + + if ($form->isValid()) { + $roleScope = $this->getPersistentRoleScopeBy( + $form['composed_role_scope']->getData()->getRole(), + $form['composed_role_scope']->getData()->getScope() + ); + + $permissionsGroup->addRoleScope($roleScope); + $violations = $this->validator->validate($permissionsGroup); + + if ($violations->count() === 0) { + $em->flush(); + + $this->addFlash( + 'notice', + $this->translator->trans('The permissions have been added') + ); + + return $this->redirect($this->generateUrl( + 'admin_permissionsgroup_edit', + ['id' => $id] + )); + } + + foreach ($violations as $error) { + $this->addFlash('error', $error->getMessage()); + } + } else { + foreach ($form->getErrors() as $error) { + $this->addFlash('error', $error->getMessage()); + } + } + + $editForm = $this->createEditForm($permissionsGroup); + + $deleteRoleScopesForm = []; + + foreach ($permissionsGroup->getRoleScopes() as $roleScope) { + $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm( + $permissionsGroup, + $roleScope + ); + } + + $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup); + + // sort role scope by title + + $roleProvider = $this->roleProvider; + $roleScopesSorted = []; + + foreach ($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { + /** @var RoleScope $roleScope */ + $title = $roleProvider->getRoleTitle($roleScope->getRole()); + $roleScopesSorted[$title][] = $roleScope; + } + ksort($roleScopesSorted); + + return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', [ + 'entity' => $permissionsGroup, + 'edit_form' => $editForm->createView(), + 'role_scopes_sorted' => $roleScopesSorted, + 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), + 'delete_role_scopes_form' => array_map(static function ($form) { + return $form->createView(); + }, $deleteRoleScopesForm), + 'add_role_scopes_form' => $addRoleScopesForm->createView(), + ]); } /** * Creates a new PermissionsGroup entity. - * */ public function createAction(Request $request) { @@ -103,284 +178,24 @@ class PermissionsGroupController extends AbstractController $em->persist($permissionsGroup); $em->flush(); - return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', - array('id' => $permissionsGroup->getId()))); + return $this->redirect($this->generateUrl( + 'admin_permissionsgroup_edit', + ['id' => $permissionsGroup->getId()] + )); } - return $this->render('@ChillMain/PermissionsGroup/new.html.twig', array( + return $this->render('@ChillMain/PermissionsGroup/new.html.twig', [ 'entity' => $permissionsGroup, - 'form' => $form->createView(), - )); + 'form' => $form->createView(), + ]); } /** - * Creates a form to create a PermissionsGroup entity. - * - * @param PermissionsGroup $permissionsGroup The entity - * - * @return \Symfony\Component\Form\Form The form - */ - private function createCreateForm(PermissionsGroup $permissionsGroup) - { - $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, array( - 'action' => $this->generateUrl('admin_permissionsgroup_create'), - 'method' => 'POST', - )); - - $form->add('submit', SubmitType::class, array('label' => 'Create')); - - return $form; - } - - /** - * Displays a form to create a new PermissionsGroup entity. - * - */ - public function newAction() - { - $permissionsGroup = new PermissionsGroup(); - $form = $this->createCreateForm($permissionsGroup); - - return $this->render('@ChillMain/PermissionsGroup/new.html.twig', array( - 'entity' => $permissionsGroup, - 'form' => $form->createView(), - )); - } - - /** - * Finds and displays a PermissionsGroup entity. - * - */ - public function showAction($id) - { - $em = $this->getDoctrine()->getManager(); - - $permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id); - - if (!$permissionsGroup) { - throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); - } - - $translatableStringHelper = $this->translatableStringHelper; - $roleScopes = $permissionsGroup->getRoleScopes()->toArray(); - - // sort $roleScopes by name - usort($roleScopes, - function(RoleScope $a, RoleScope $b) use ($translatableStringHelper) { - if ($a->getScope() === NULL) { - return 1; - } - if ($b->getScope() === NULL) { - return +1; - } - - return strcmp( - $translatableStringHelper->localize($a->getScope()->getName()), - $translatableStringHelper->localize($b->getScope()->getName()) - ); - }); - - // sort role scope by title - $roleProvider = $this->roleProvider; - $roleScopesSorted = array(); - foreach($roleScopes as $roleScope) { - - /* @var $roleScope RoleScope */ - $title = $roleProvider->getRoleTitle($roleScope->getRole()); - $roleScopesSorted[$title][] = $roleScope; - } - ksort($roleScopesSorted); - - return $this->render('@ChillMain/PermissionsGroup/show.html.twig', array( - 'entity' => $permissionsGroup, - 'role_scopes_sorted' => $roleScopesSorted, - 'expanded_roles' => $this->getExpandedRoles($roleScopes) - )); - } - - /** - * expand roleScopes to be easily shown in template - * - * @param array $roleScopes - * @return array - */ - private function getExpandedRoles(array $roleScopes) - { - $expandedRoles = array(); - foreach ($roleScopes as $roleScope) { - if (!array_key_exists($roleScope->getRole(), $expandedRoles)) { - $expandedRoles[$roleScope->getRole()] = - array_map( - function(Role $role) { - return $role->getRole(); - }, - $this->roleHierarchy - ->getReachableRoles( - array(new Role($roleScope->getRole())) - ) - ); - } - } - return $expandedRoles; - } - - /** - * Displays a form to edit an existing PermissionsGroup entity. - * - */ - public function editAction($id) - { - $em = $this->getDoctrine()->getManager(); - - $permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id); - - if (!$permissionsGroup) { - throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); - } - - // create all the forms - $editForm = $this->createEditForm($permissionsGroup); - - $deleteRoleScopesForm = array(); - foreach ($permissionsGroup->getRoleScopes() as $roleScope) { - $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm( - $permissionsGroup, $roleScope); - } - - $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup); - - // sort role scope by title - $roleProvider = $this->roleProvider; - $roleScopesSorted = array(); - foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { - /* @var $roleScope RoleScope */ - $title = $roleProvider->getRoleTitle($roleScope->getRole()); - $roleScopesSorted[$title][] = $roleScope; - } - ksort($roleScopesSorted); - - return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', array( - 'entity' => $permissionsGroup, - 'role_scopes_sorted' => $roleScopesSorted, - 'edit_form' => $editForm->createView(), - 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), - 'delete_role_scopes_form' => array_map( function($form) { - - return $form->createView(); - }, $deleteRoleScopesForm), - 'add_role_scopes_form' => $addRoleScopesForm->createView() - )); - } - - /** - * Creates a form to edit a PermissionsGroup entity. - * - * @param PermissionsGroup $permissionsGroup The entity - * - * @return \Symfony\Component\Form\Form The form - */ - private function createEditForm(PermissionsGroup $permissionsGroup) - { - $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, array( - 'action' => $this->generateUrl('admin_permissionsgroup_update', array('id' => $permissionsGroup->getId())), - 'method' => 'PUT', - )); - - $form->add('submit', SubmitType::class, array('label' => 'Update')); - - return $form; - } - - /** - * Edits an existing PermissionsGroup entity. - * - */ - public function updateAction(Request $request, $id) - { - $em = $this->getDoctrine()->getManager(); - - $permissionsGroup = $em - ->getRepository('ChillMainBundle:PermissionsGroup') - ->find($id); - - if (!$permissionsGroup) { - throw $this->createNotFoundException('Unable to find Permissions' - . 'Group entity.'); - } - - $editForm = $this->createEditForm($permissionsGroup); - $editForm->handleRequest($request); - - if ($editForm->isValid()) { - - $em->flush(); - - return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', array('id' => $id))); - } - - $deleteRoleScopesForm = array(); - foreach ($permissionsGroup->getRoleScopes() as $roleScope) { - $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm( - $permissionsGroup, $roleScope); - } - - $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup); - - // sort role scope by title - $roleProvider = $this->roleProvider; - $roleScopesSorted = array(); - foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { - /* @var $roleScope RoleScope */ - $title = $roleProvider->getRoleTitle($roleScope->getRole()); - $roleScopesSorted[$title][] = $roleScope; - } - ksort($roleScopesSorted); - - return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', array( - 'entity' => $permissionsGroup, - 'role_scopes_sorted' => $roleScopesSorted, - 'edit_form' => $editForm->createView(), - 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), - 'delete_role_scopes_form' => array_map( function($form) { - - return $form->createView(); - }, $deleteRoleScopesForm), - 'add_role_scopes_form' => $addRoleScopesForm->createView() - )); - } - - /** - * get a role scope by his parameters. The role scope is persisted if it - * doesn't exists in database. - * - * @param Scope $scope - * @param string $role - * @return RoleScope - */ - protected function getPersistentRoleScopeBy($role, Scope $scope = null) - { - $em = $this->getDoctrine()->getManager(); - - $roleScope = $em->getRepository('ChillMainBundle:RoleScope') - ->findOneBy(array('role' => $role, 'scope' => $scope)); - - if ($roleScope === NULL) { - $roleScope = (new RoleScope()) - ->setRole($role) - ->setScope($scope) - ; - - $em->persist($roleScope); - } - - return $roleScope; - } - - /** - * remove an association between permissionsGroup and roleScope + * remove an association between permissionsGroup and roleScope. * * @param int $pgid permissionsGroup id * @param int $rsid roleScope id + * * @return redirection to edit form */ public function deleteLinkRoleScopeAction($pgid, $rsid) @@ -400,48 +215,56 @@ class PermissionsGroupController extends AbstractController try { $permissionsGroup->removeRoleScope($roleScope); - } catch (\RuntimeException $ex) { - $this->addFlash('notice', + } catch (RuntimeException $ex) { + $this->addFlash( + 'notice', $this->translator->trans("The role '%role%' and circle " - . "'%scope%' is not associated with this permission group", array( + . "'%scope%' is not associated with this permission group", [ '%role%' => $this->translator->trans($roleScope->getRole()), '%scope%' => $this->translatableStringHelper - ->localize($roleScope->getScope()->getName()) - ))); + ->localize($roleScope->getScope()->getName()), + ]) + ); - return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', - array('id' => $pgid))); + return $this->redirect($this->generateUrl( + 'admin_permissionsgroup_edit', + ['id' => $pgid] + )); } $em->flush(); - if ($roleScope->getScope() !== NULL ) { - $this->addFlash('notice', + if ($roleScope->getScope() !== null) { + $this->addFlash( + 'notice', $this->translator->trans("The role '%role%' on circle " - . "'%scope%' has been removed", array( + . "'%scope%' has been removed", [ '%role%' => $this->translator->trans($roleScope->getRole()), '%scope%' => $this->translatableStringHelper - ->localize($roleScope->getScope()->getName()) - ))); + ->localize($roleScope->getScope()->getName()), + ]) + ); } else { - $this->addFlash('notice', - $this->translator->trans("The role '%role%' has been removed", array( - '%role%' => $this->translator->trans($roleScope->getRole()) - ))); + $this->addFlash( + 'notice', + $this->translator->trans("The role '%role%' has been removed", [ + '%role%' => $this->translator->trans($roleScope->getRole()), + ]) + ); } - return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', - array('id' => $pgid))); + return $this->redirect($this->generateUrl( + 'admin_permissionsgroup_edit', + ['id' => $pgid] + )); } /** + * Displays a form to edit an existing PermissionsGroup entity. * - * @param Request $request - * @param int $id - * @return Respon - * @throws type + * @param mixed $id */ - public function addLinkRoleScopeAction(Request $request, $id) + public function editAction($id) { $em = $this->getDoctrine()->getManager(); @@ -451,71 +274,249 @@ class PermissionsGroupController extends AbstractController throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); } - $form = $this->createAddRoleScopeForm($permissionsGroup); - $form->handleRequest($request); - - if ($form->isValid()) { - $roleScope = $this->getPersistentRoleScopeBy( - $form['composed_role_scope']->getData()->getRole(), - $form['composed_role_scope']->getData()->getScope() - ); - - $permissionsGroup->addRoleScope($roleScope); - $violations = $this->validator->validate($permissionsGroup); - - if ($violations->count() === 0) { - $em->flush(); - - $this->addFlash('notice', - $this->translator->trans("The permissions have been added")); - - return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', - array('id' => $id))); - } else { - foreach($violations as $error) { - $this->addFlash('error', $error->getMessage()); - } - } - - } else { - foreach ($form->getErrors() as $error) { - $this->addFlash('error', $error->getMessage()); - } - } - + // create all the forms $editForm = $this->createEditForm($permissionsGroup); - $deleteRoleScopesForm = array(); + $deleteRoleScopesForm = []; + foreach ($permissionsGroup->getRoleScopes() as $roleScope) { $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm( - $permissionsGroup, $roleScope); + $permissionsGroup, + $roleScope + ); } $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup); // sort role scope by title - $roleProvider = $this->roleProvider; - $roleScopesSorted = array(); - foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { - /* @var $roleScope RoleScope */ + $roleScopesSorted = []; + + foreach ($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { + /** @var RoleScope $roleScope */ $title = $roleProvider->getRoleTitle($roleScope->getRole()); $roleScopesSorted[$title][] = $roleScope; } ksort($roleScopesSorted); - return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', array( - 'entity' => $permissionsGroup, - 'edit_form' => $editForm->createView(), + return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', [ + 'entity' => $permissionsGroup, 'role_scopes_sorted' => $roleScopesSorted, + 'edit_form' => $editForm->createView(), 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), - 'delete_role_scopes_form' => array_map( function($form) { - + 'delete_role_scopes_form' => array_map(static function ($form) { return $form->createView(); }, $deleteRoleScopesForm), - 'add_role_scopes_form' => $addRoleScopesForm->createView() - )); + 'add_role_scopes_form' => $addRoleScopesForm->createView(), + ]); + } + /** + * Lists all PermissionsGroup entities. + */ + public function indexAction() + { + $em = $this->getDoctrine()->getManager(); + + $entities = $em->getRepository('ChillMainBundle:PermissionsGroup')->findAll(); + + return $this->render('@ChillMain/PermissionsGroup/index.html.twig', [ + 'entities' => $entities, + ]); + } + + /** + * Displays a form to create a new PermissionsGroup entity. + */ + public function newAction() + { + $permissionsGroup = new PermissionsGroup(); + $form = $this->createCreateForm($permissionsGroup); + + return $this->render('@ChillMain/PermissionsGroup/new.html.twig', [ + 'entity' => $permissionsGroup, + 'form' => $form->createView(), + ]); + } + + /** + * Finds and displays a PermissionsGroup entity. + * + * @param mixed $id + */ + public function showAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id); + + if (!$permissionsGroup) { + throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); + } + + $translatableStringHelper = $this->translatableStringHelper; + $roleScopes = $permissionsGroup->getRoleScopes()->toArray(); + + // sort $roleScopes by name + usort( + $roleScopes, + static function (RoleScope $a, RoleScope $b) use ($translatableStringHelper) { + if ($a->getScope() === null) { + return 1; + } + + if ($b->getScope() === null) { + return +1; + } + + return strcmp( + $translatableStringHelper->localize($a->getScope()->getName()), + $translatableStringHelper->localize($b->getScope()->getName()) + ); + } + ); + + // sort role scope by title + $roleProvider = $this->roleProvider; + $roleScopesSorted = []; + + foreach ($roleScopes as $roleScope) { + /** @var RoleScope $roleScope */ + $title = $roleProvider->getRoleTitle($roleScope->getRole()); + $roleScopesSorted[$title][] = $roleScope; + } + ksort($roleScopesSorted); + + return $this->render('@ChillMain/PermissionsGroup/show.html.twig', [ + 'entity' => $permissionsGroup, + 'role_scopes_sorted' => $roleScopesSorted, + 'expanded_roles' => $this->getExpandedRoles($roleScopes), + ]); + } + + /** + * Edits an existing PermissionsGroup entity. + * + * @param mixed $id + */ + public function updateAction(Request $request, $id) + { + $em = $this->getDoctrine()->getManager(); + + $permissionsGroup = $em + ->getRepository('ChillMainBundle:PermissionsGroup') + ->find($id); + + if (!$permissionsGroup) { + throw $this->createNotFoundException('Unable to find Permissions' + . 'Group entity.'); + } + + $editForm = $this->createEditForm($permissionsGroup); + $editForm->handleRequest($request); + + if ($editForm->isValid()) { + $em->flush(); + + return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', ['id' => $id])); + } + + $deleteRoleScopesForm = []; + + foreach ($permissionsGroup->getRoleScopes() as $roleScope) { + $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm( + $permissionsGroup, + $roleScope + ); + } + + $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup); + + // sort role scope by title + $roleProvider = $this->roleProvider; + $roleScopesSorted = []; + + foreach ($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { + /** @var RoleScope $roleScope */ + $title = $roleProvider->getRoleTitle($roleScope->getRole()); + $roleScopesSorted[$title][] = $roleScope; + } + ksort($roleScopesSorted); + + return $this->render('@ChillMain/PermissionsGroup/edit.html.twig', [ + 'entity' => $permissionsGroup, + 'role_scopes_sorted' => $roleScopesSorted, + 'edit_form' => $editForm->createView(), + 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), + 'delete_role_scopes_form' => array_map(static function ($form) { + return $form->createView(); + }, $deleteRoleScopesForm), + 'add_role_scopes_form' => $addRoleScopesForm->createView(), + ]); + } + + /** + * get a role scope by his parameters. The role scope is persisted if it + * doesn't exists in database. + * + * @param Scope $scope + * @param string $role + * + * @return RoleScope + */ + protected function getPersistentRoleScopeBy($role, ?Scope $scope = null) + { + $em = $this->getDoctrine()->getManager(); + + $roleScope = $em->getRepository('ChillMainBundle:RoleScope') + ->findOneBy(['role' => $role, 'scope' => $scope]); + + if (null === $roleScope) { + $roleScope = (new RoleScope()) + ->setRole($role) + ->setScope($scope); + + $em->persist($roleScope); + } + + return $roleScope; + } + + /** + * creates a form to add a role scope to permissionsgroup. + * + * @return \Symfony\Component\Form\Form The form + */ + private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup) + { + return $this->createFormBuilder() + ->setAction($this->generateUrl( + 'admin_permissionsgroup_add_role_scope', + ['id' => $permissionsGroup->getId()] + )) + ->setMethod('PUT') + ->add('composed_role_scope', ComposedRoleScopeType::class) + ->add('submit', SubmitType::class, ['label' => 'Add permission']) + ->getForm(); + } + + /** + * Creates a form to create a PermissionsGroup entity. + * + * @param PermissionsGroup $permissionsGroup The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createCreateForm(PermissionsGroup $permissionsGroup) + { + $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [ + 'action' => $this->generateUrl('admin_permissionsgroup_create'), + 'method' => 'POST', + ]); + + $form->add('submit', SubmitType::class, ['label' => 'Create']); + + return $form; } /** @@ -525,35 +526,63 @@ class PermissionsGroupController extends AbstractController * * @return \Symfony\Component\Form\Form The form */ - private function createDeleteRoleScopeForm(PermissionsGroup $permissionsGroup, - RoleScope $roleScope) - { + private function createDeleteRoleScopeForm( + PermissionsGroup $permissionsGroup, + RoleScope $roleScope + ) { return $this->createFormBuilder() - ->setAction($this->generateUrl('admin_permissionsgroup_delete_role_scope', - array('pgid' => $permissionsGroup->getId(), 'rsid' => $roleScope->getId()))) + ->setAction($this->generateUrl( + 'admin_permissionsgroup_delete_role_scope', + ['pgid' => $permissionsGroup->getId(), 'rsid' => $roleScope->getId()] + )) ->setMethod('DELETE') - ->add('submit', SubmitType::class, array('label' => 'Delete')) - ->getForm() - ; + ->add('submit', SubmitType::class, ['label' => 'Delete']) + ->getForm(); } /** - * creates a form to add a role scope to permissionsgroup + * Creates a form to edit a PermissionsGroup entity. + * + * @param PermissionsGroup $permissionsGroup The entity * - * @param PermissionsGroup $permissionsGroup * @return \Symfony\Component\Form\Form The form */ - private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup) + private function createEditForm(PermissionsGroup $permissionsGroup) { - return $this->createFormBuilder() - ->setAction($this->generateUrl('admin_permissionsgroup_add_role_scope', - array('id' => $permissionsGroup->getId()))) - ->setMethod('PUT') - ->add('composed_role_scope', ComposedRoleScopeType::class) - ->add('submit', SubmitType::class, array('label' => 'Add permission')) - ->getForm() - ; + $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [ + 'action' => $this->generateUrl('admin_permissionsgroup_update', ['id' => $permissionsGroup->getId()]), + 'method' => 'PUT', + ]); + + $form->add('submit', SubmitType::class, ['label' => 'Update']); + + return $form; } + /** + * expand roleScopes to be easily shown in template. + * + * @return array + */ + private function getExpandedRoles(array $roleScopes) + { + $expandedRoles = []; + foreach ($roleScopes as $roleScope) { + if (!array_key_exists($roleScope->getRole(), $expandedRoles)) { + $expandedRoles[$roleScope->getRole()] = + array_map( + static function (Role $role) { + return $role->getRole(); + }, + $this->roleHierarchy + ->getReachableRoles( + [new Role($roleScope->getRole())] + ) + ); + } + } + + return $expandedRoles; + } } diff --git a/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php b/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php index 026784208..fa1a29296 100644 --- a/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php +++ b/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php @@ -1,27 +1,96 @@ countryRepository = $countryRepository; + $this->postalCodeRepository = $postalCodeRepository; + $this->paginatorFactory = $paginatorFactory; + } + + /** + * @Route("/api/1.0/main/postal-code/search.json") + */ + public function search(Request $request): JsonResponse + { + $this->denyAccessUnlessGranted('ROLE_USER'); + + if (!$request->query->has('q')) { + throw new BadRequestHttpException('You must supply a "q" parameter'); + } + + $pattern = $request->query->get('q'); + + if ('' === trim($pattern)) { + throw new BadRequestHttpException('the search pattern is empty'); + } + + if ($request->query->has('country')) { + $country = $this->countryRepository->find($request->query->getInt('country')); + + if (null === $country) { + throw new NotFoundHttpException('country not found'); + } + } else { + $country = null; + } + + $nb = $this->postalCodeRepository->countByPattern($pattern, $country); + $paginator = $this->paginatorFactory->create($nb); + $codes = $this->postalCodeRepository->findByPattern( + $pattern, + $country, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); + + return $this->json( + new Collection($codes, $paginator), + Response::HTTP_OK, + [], + [AbstractNormalizer::GROUPS => ['read']] + ); + } protected function customizeQuery(string $action, Request $request, $qb): void { if ($request->query->has('country')) { - $qb->where('e.country = :country') - ->setParameter('country', $request->query->get('country')); - + ->setParameter('country', $request->query->get('country')); } } - } diff --git a/src/Bundle/ChillMainBundle/Controller/PostalCodeController.php b/src/Bundle/ChillMainBundle/Controller/PostalCodeController.php index fb88e0763..0280be61f 100644 --- a/src/Bundle/ChillMainBundle/Controller/PostalCodeController.php +++ b/src/Bundle/ChillMainBundle/Controller/PostalCodeController.php @@ -1,90 +1,78 @@ - * - * 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\MainBundle\Controller; - -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Annotation\Route; -use Chill\MainBundle\Entity\PostalCode; -use Symfony\Component\HttpFoundation\JsonResponse; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Doctrine\ORM\Query; /** - * Class PostalCodeController + * Chill is a software for social workers * - * @package Chill\MainBundle\Controller - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Controller; + +use Chill\MainBundle\Entity\PostalCode; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\ORM\Query; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; +use function array_map; + +/** + * Class PostalCodeController. */ class PostalCodeController extends AbstractController { /** - * * @var TranslatableStringHelper */ protected $translatableStringHelper; - + public function __construct(TranslatableStringHelper $translatableStringHelper) { $this->translatableStringHelper = $translatableStringHelper; } - /** - * * @Route( - * "{_locale}/postalcode/search" + * "{_locale}/postalcode/search" * ) - * @param Request $request + * * @return JsonResponse */ public function searchAction(Request $request) { $pattern = $request->query->getAlnum('q', ''); - + if (empty($pattern)) { - return new JsonResponse(["results" => [], "pagination" => [ "more" => false]]); + return new JsonResponse(['results' => [], 'pagination' => ['more' => false]]); } - + $query = $this->getDoctrine()->getManager() - ->createQuery(sprintf( - "SELECT p.id AS id, p.name AS name, p.code AS code, " - . "country.name AS country_name, " - . "country.countryCode AS country_code " - . "FROM %s p " - . "JOIN p.country country " - . "WHERE LOWER(p.name) LIKE LOWER(:pattern) OR LOWER(p.code) LIKE LOWER(:pattern) " - . "ORDER BY code" - , PostalCode::class) + ->createQuery( + sprintf( + 'SELECT p.id AS id, p.name AS name, p.code AS code, ' + . 'country.name AS country_name, ' + . 'country.countryCode AS country_code ' + . 'FROM %s p ' + . 'JOIN p.country country ' + . 'WHERE LOWER(p.name) LIKE LOWER(:pattern) OR LOWER(p.code) LIKE LOWER(:pattern) ' + . 'ORDER BY code', + PostalCode::class ) - ->setParameter('pattern', '%'.$pattern.'%') - ->setMaxResults(30) - ; - - $results = \array_map(function($row) { + ) + ->setParameter('pattern', '%' . $pattern . '%') + ->setMaxResults(30); + + $results = array_map(function ($row) { $row['country_name'] = $this->translatableStringHelper->localize($row['country_name']); - $row['text'] = $row['code']." ".$row["name"]." (".$row['country_name'].")"; - + $row['text'] = $row['code'] . ' ' . $row['name'] . ' (' . $row['country_name'] . ')'; + return $row; }, $query->getResult(Query::HYDRATE_ARRAY)); - - return new JsonResponse([ 'results' => $results, "pagination" => [ "more" => false ] ]); + + return new JsonResponse(['results' => $results, 'pagination' => ['more' => false]]); } } diff --git a/src/Bundle/ChillMainBundle/Controller/ScopeController.php b/src/Bundle/ChillMainBundle/Controller/ScopeController.php index 33e37ad94..07945c7fa 100644 --- a/src/Bundle/ChillMainBundle/Controller/ScopeController.php +++ b/src/Bundle/ChillMainBundle/Controller/ScopeController.php @@ -1,38 +1,29 @@ getDoctrine()->getManager(); - - $entities = $em->getRepository('ChillMainBundle:Scope')->findAll(); - - return $this->render('@ChillMain/Scope/index.html.twig', array( - 'entities' => $entities, - )); - } /** * Creates a new Scope entity. - * */ public function createAction(Request $request) { @@ -45,71 +36,19 @@ class ScopeController extends AbstractController $em->persist($scope); $em->flush(); - return $this->redirect($this->generateUrl('admin_scope_show', array('id' => $scope->getId()))); + return $this->redirect($this->generateUrl('admin_scope_show', ['id' => $scope->getId()])); } - return $this->render('@ChillMain/Scope/new.html.twig', array( + return $this->render('@ChillMain/Scope/new.html.twig', [ 'entity' => $scope, - 'form' => $form->createView(), - )); - } - - /** - * Creates a form to create a Scope entity. - * - * @param Scope $scope The entity - * - * @return \Symfony\Component\Form\Form The form - */ - private function createCreateForm(Scope $scope) - { - $form = $this->createForm(ScopeType::class, $scope, array( - 'action' => $this->generateUrl('admin_scope_create'), - 'method' => 'POST', - )); - - $form->add('submit', SubmitType::class, array('label' => 'Create')); - - return $form; - } - - /** - * Displays a form to create a new Scope entity. - * - */ - public function newAction() - { - $scope = new Scope(); - $form = $this->createCreateForm($scope); - - return $this->render('@ChillMain/Scope/new.html.twig', array( - 'entity' => $scope, - 'form' => $form->createView(), - )); - } - - /** - * Finds and displays a Scope entity. - * - */ - public function showAction($id) - { - $em = $this->getDoctrine()->getManager(); - - $scope = $em->getRepository('ChillMainBundle:Scope')->find($id); - - if (!$scope) { - throw $this->createNotFoundException('Unable to find Scope entity.'); - } - - return $this->render('@ChillMain/Scope/show.html.twig', array( - 'entity' => $scope - )); + 'form' => $form->createView(), + ]); } /** * Displays a form to edit an existing Scope entity. * + * @param mixed $id */ public function editAction($id) { @@ -123,33 +62,64 @@ class ScopeController extends AbstractController $editForm = $this->createEditForm($scope); - return $this->render('@ChillMain/Scope/edit.html.twig', array( - 'entity' => $scope, - 'edit_form' => $editForm->createView(), - )); + return $this->render('@ChillMain/Scope/edit.html.twig', [ + 'entity' => $scope, + 'edit_form' => $editForm->createView(), + ]); } /** - * Creates a form to edit a Scope entity. - * - * @param Scope $scope The entity - * - * @return \Symfony\Component\Form\Form The form - */ - private function createEditForm(Scope $scope) + * Lists all Scope entities. + */ + public function indexAction() { - $form = $this->createForm(ScopeType::class, $scope, array( - 'action' => $this->generateUrl('admin_scope_update', array('id' => $scope->getId())), - 'method' => 'PUT', - )); + $em = $this->getDoctrine()->getManager(); - $form->add('submit', SubmitType::class, array('label' => 'Update')); + $entities = $em->getRepository('ChillMainBundle:Scope')->findAll(); - return $form; + return $this->render('@ChillMain/Scope/index.html.twig', [ + 'entities' => $entities, + ]); } + + /** + * Displays a form to create a new Scope entity. + */ + public function newAction() + { + $scope = new Scope(); + $form = $this->createCreateForm($scope); + + return $this->render('@ChillMain/Scope/new.html.twig', [ + 'entity' => $scope, + 'form' => $form->createView(), + ]); + } + + /** + * Finds and displays a Scope entity. + * + * @param mixed $id + */ + public function showAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $scope = $em->getRepository('ChillMainBundle:Scope')->find($id); + + if (!$scope) { + throw $this->createNotFoundException('Unable to find Scope entity.'); + } + + return $this->render('@ChillMain/Scope/show.html.twig', [ + 'entity' => $scope, + ]); + } + /** * Edits an existing Scope entity. * + * @param mixed $id */ public function updateAction(Request $request, $id) { @@ -167,12 +137,50 @@ class ScopeController extends AbstractController if ($editForm->isValid()) { $em->flush(); - return $this->redirect($this->generateUrl('admin_scope_edit', array('id' => $id))); + return $this->redirect($this->generateUrl('admin_scope_edit', ['id' => $id])); } - return $this->render('@ChillMain/Scope/edit.html.twig', array( - 'entity' => $scope, - 'edit_form' => $editForm->createView() - )); + return $this->render('@ChillMain/Scope/edit.html.twig', [ + 'entity' => $scope, + 'edit_form' => $editForm->createView(), + ]); + } + + /** + * Creates a form to create a Scope entity. + * + * @param Scope $scope The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createCreateForm(Scope $scope) + { + $form = $this->createForm(ScopeType::class, $scope, [ + 'action' => $this->generateUrl('admin_scope_create'), + 'method' => 'POST', + ]); + + $form->add('submit', SubmitType::class, ['label' => 'Create']); + + return $form; + } + + /** + * Creates a form to edit a Scope entity. + * + * @param Scope $scope The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createEditForm(Scope $scope) + { + $form = $this->createForm(ScopeType::class, $scope, [ + 'action' => $this->generateUrl('admin_scope_update', ['id' => $scope->getId()]), + 'method' => 'PUT', + ]); + + $form->add('submit', SubmitType::class, ['label' => 'Update']); + + return $form; } } diff --git a/src/Bundle/ChillMainBundle/Controller/SearchController.php b/src/Bundle/ChillMainBundle/Controller/SearchController.php index 4ecc215f7..c231bf162 100644 --- a/src/Bundle/ChillMainBundle/Controller/SearchController.php +++ b/src/Bundle/ChillMainBundle/Controller/SearchController.php @@ -1,62 +1,50 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Controller; +use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\MainBundle\Search\ParsingException; +use Chill\MainBundle\Search\SearchApi; use Chill\MainBundle\Search\SearchApiNoQueryException; -use Chill\MainBundle\Serializer\Model\Collection; -use GuzzleHttp\Psr7\Response; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Request; +use Chill\MainBundle\Search\SearchInterface; +use Chill\MainBundle\Search\SearchProvider; use Chill\MainBundle\Search\UnknowSearchDomainException; use Chill\MainBundle\Search\UnknowSearchNameException; -use Chill\MainBundle\Search\ParsingException; -use Chill\MainBundle\Search\SearchInterface; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\JsonResponse; -use Chill\MainBundle\Search\SearchProvider; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Contracts\Translation\TranslatorInterface; -use Chill\MainBundle\Pagination\PaginatorFactory; -use Chill\MainBundle\Search\SearchApi; -use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use function count; +use function key; +use function reset; /** - * Class SearchController - * - * @package Chill\MainBundle\Controller + * Class SearchController. */ class SearchController extends AbstractController { - protected SearchProvider $searchProvider; - - protected TranslatorInterface $translator; - protected PaginatorFactory $paginatorFactory; protected SearchApi $searchApi; - function __construct( + protected SearchProvider $searchProvider; + + protected TranslatorInterface $translator; + + public function __construct( SearchProvider $searchProvider, TranslatorInterface $translator, PaginatorFactory $paginatorFactory, @@ -68,144 +56,23 @@ class SearchController extends AbstractController $this->searchApi = $searchApi; } - - public function searchAction(Request $request, $_format) - { - $pattern = $request->query->get('q', ''); - - if ($pattern === ''){ - switch($_format) { - case 'html': - return $this->render('@ChillMain/Search/error.html.twig', - array( - 'message' => $this->translator->trans("Your search is empty. " - . "Please provide search terms."), - 'pattern' => $pattern - )); - case 'json': - return new JsonResponse([ - 'results' => [], - 'pagination' => [ 'more' => false ] - ]); - } - } - - $name = $request->query->get('name', NULL); - - try { - if ($name === NULL) { - if ($_format === 'json') { - return new JsonResponse('Currently, we still do not aggregate results ' - . 'from different providers', JsonResponse::HTTP_BAD_REQUEST); - } - - // no specific search selected. Rendering result in "preview" mode - $results = $this->searchProvider - ->getSearchResults( - $pattern, - 0, - 5, - array(SearchInterface::SEARCH_PREVIEW_OPTION => true) - ); - } else { - // we want search on a specific search provider. Display full results. - $results = [$this->searchProvider - ->getResultByName( - $pattern, - $name, - $this->paginatorFactory->getCurrentPageFirstItemNumber(), - $this->paginatorFactory->getCurrentItemsPerPage(), - array( - SearchInterface::SEARCH_PREVIEW_OPTION => false, - SearchInterface::REQUEST_QUERY_PARAMETERS => $request - ->get(SearchInterface::REQUEST_QUERY_KEY_ADD_PARAMETERS, []) - ), - $_format - )]; - - if ($_format === 'json') { - return new JsonResponse(\reset($results)); - } - } - } catch (UnknowSearchDomainException $ex) { - return $this->render('@ChillMain/Search/error.html.twig', - array( - "message" => $this->translator->trans("The domain %domain% " - . "is unknow. Please check your search.", array('%domain%' => $ex->getDomain())), - 'pattern' => $pattern - )); - } catch (UnknowSearchNameException $ex) { - throw $this->createNotFoundException("The name ".$ex->getName()." is not found"); - } catch (ParsingException $ex) { - return $this->render('@ChillMain/Search/error.html.twig', - array( - "message" => $this->translator->trans('Invalid terms'). - ": ".$this->translator->trans($ex->getMessage()), - 'pattern' => $pattern - )); - } - - - return $this->render('@ChillMain/Search/list.html.twig', - array('results' => $results, 'pattern' => $pattern) - ); - } - - public function searchApi(Request $request, $_format): JsonResponse - { - //TODO this is an incomplete implementation - $query = $request->query->get('q', ''); - $types = $request->query->get('type', []); - - if (count($types) === 0) { - throw new BadRequestException("The request must contains at " - ." one type"); - } - - try { - $collection = $this->searchApi->getResults($query, $types, []); - } catch (SearchApiNoQueryException $e) { - throw new BadRequestHttpException($e->getMessage(), $e); - } - - return $this->json($collection, \Symfony\Component\HttpFoundation\Response::HTTP_OK, [], [ "groups" => ["read"]]); - } - - public function advancedSearchListAction(Request $request) - { - /* @var $variable Chill\MainBundle\Search\SearchProvider */ - $searchProvider = $this->searchProvider; - $advancedSearchProviders = $searchProvider - ->getHasAdvancedFormSearchServices(); - - if(\count($advancedSearchProviders) === 1) { - \reset($advancedSearchProviders); - - return $this->redirectToRoute('chill_main_advanced_search', [ - 'name' => \key($advancedSearchProviders) - ]); - } - - return $this->render('@ChillMain/Search/choose_list.html.twig'); - } - public function advancedSearchAction($name, Request $request) { try { - /* @var $variable Chill\MainBundle\Search\SearchProvider */ + /** @var Chill\MainBundle\Search\SearchProvider $variable */ $searchProvider = $this->searchProvider; - /* @var $variable Chill\MainBundle\Search\HasAdvancedSearchFormInterface */ + /** @var Chill\MainBundle\Search\HasAdvancedSearchFormInterface $variable */ $search = $this->searchProvider - ->getHasAdvancedFormByName($name); - + ->getHasAdvancedFormByName($name); } catch (\Chill\MainBundle\Search\UnknowSearchNameException $e) { - throw $this->createNotFoundException("no advanced search for " - . "$name"); + throw $this->createNotFoundException('no advanced search for ' + . "{$name}"); } if ($request->query->has('q')) { $data = $search->convertTermsToFormData($searchProvider->parse( - $request->query->get('q'))); + $request->query->get('q') + )); } $form = $this->createAdvancedSearchForm($name, $data ?? []); @@ -219,17 +86,146 @@ class SearchController extends AbstractController ->convertFormDataToQuery($form->getData()); return $this->redirectToRoute('chill_main_search', [ - 'q' => $pattern, 'name' => $name + 'q' => $pattern, 'name' => $name, ]); } } - return $this->render('@ChillMain/Search/advanced_search.html.twig', + return $this->render( + '@ChillMain/Search/advanced_search.html.twig', [ 'form' => $form->createView(), 'name' => $name, - 'title' => $search->getAdvancedSearchTitle() + 'title' => $search->getAdvancedSearchTitle(), + ] + ); + } + + public function advancedSearchListAction(Request $request) + { + /** @var Chill\MainBundle\Search\SearchProvider $variable */ + $searchProvider = $this->searchProvider; + $advancedSearchProviders = $searchProvider + ->getHasAdvancedFormSearchServices(); + + if (count($advancedSearchProviders) === 1) { + reset($advancedSearchProviders); + + return $this->redirectToRoute('chill_main_advanced_search', [ + 'name' => key($advancedSearchProviders), ]); + } + + return $this->render('@ChillMain/Search/choose_list.html.twig'); + } + + public function searchAction(Request $request, $_format) + { + $pattern = $request->query->get('q', ''); + + if ('' === $pattern) { + switch ($_format) { + case 'html': + return $this->render( + '@ChillMain/Search/error.html.twig', + [ + 'message' => $this->translator->trans('Your search is empty. ' + . 'Please provide search terms.'), + 'pattern' => $pattern, + ] + ); + + case 'json': + return new JsonResponse([ + 'results' => [], + 'pagination' => ['more' => false], + ]); + } + } + + $name = $request->query->get('name', null); + + try { + if (null === $name) { + if ('json' === $_format) { + return new JsonResponse('Currently, we still do not aggregate results ' + . 'from different providers', JsonResponse::HTTP_BAD_REQUEST); + } + + // no specific search selected. Rendering result in "preview" mode + $results = $this->searchProvider + ->getSearchResults( + $pattern, + 0, + 5, + [SearchInterface::SEARCH_PREVIEW_OPTION => true] + ); + } else { + // we want search on a specific search provider. Display full results. + $results = [$this->searchProvider + ->getResultByName( + $pattern, + $name, + $this->paginatorFactory->getCurrentPageFirstItemNumber(), + $this->paginatorFactory->getCurrentItemsPerPage(), + [ + SearchInterface::SEARCH_PREVIEW_OPTION => false, + SearchInterface::REQUEST_QUERY_PARAMETERS => $request + ->get(SearchInterface::REQUEST_QUERY_KEY_ADD_PARAMETERS, []), + ], + $_format + ), ]; + + if ('json' === $_format) { + return new JsonResponse(reset($results)); + } + } + } catch (UnknowSearchDomainException $ex) { + return $this->render( + '@ChillMain/Search/error.html.twig', + [ + 'message' => $this->translator->trans('The domain %domain% ' + . 'is unknow. Please check your search.', ['%domain%' => $ex->getDomain()]), + 'pattern' => $pattern, + ] + ); + } catch (UnknowSearchNameException $ex) { + throw $this->createNotFoundException('The name ' . $ex->getName() . ' is not found'); + } catch (ParsingException $ex) { + return $this->render( + '@ChillMain/Search/error.html.twig', + [ + 'message' => $this->translator->trans('Invalid terms') . + ': ' . $this->translator->trans($ex->getMessage()), + 'pattern' => $pattern, + ] + ); + } + + return $this->render( + '@ChillMain/Search/list.html.twig', + ['results' => $results, 'pattern' => $pattern] + ); + } + + public function searchApi(Request $request, $_format): JsonResponse + { + //TODO this is an incomplete implementation + $query = $request->query->get('q', ''); + $types = $request->query->get('type', []); + + if (count($types) === 0) { + throw new BadRequestException('The request must contains at ' + . ' one type'); + } + + try { + $collection = $this->searchApi->getResults($query, $types, []); + } catch (SearchApiNoQueryException $e) { + throw new BadRequestHttpException($e->getMessage(), $e); + } + + return $this->json($collection, \Symfony\Component\HttpFoundation\Response::HTTP_OK, [], ['groups' => ['read']]); } protected function createAdvancedSearchForm($name, array $data = []) @@ -240,19 +236,17 @@ class SearchController extends AbstractController null, FormType::class, $data, - [ 'method' => Request::METHOD_POST ] + ['method' => Request::METHOD_POST] ); $this->searchProvider ->getHasAdvancedFormByName($name) - ->buildForm($builder) - ; + ->buildForm($builder); $builder->add('submit', SubmitType::class, [ - 'label' => 'Search' + 'label' => 'Search', ]); return $builder->getForm(); } - } diff --git a/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php b/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php index af17993ed..8cf5f8782 100644 --- a/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php +++ b/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php @@ -1,45 +1,32 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\Request; -use Chill\MainBundle\Timeline\TimelineBuilder; use Chill\MainBundle\Pagination\PaginatorFactory; -use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Symfony\Component\Security\Core\Role\Role; +use Chill\MainBundle\Timeline\TimelineBuilder; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; +use function count; class TimelineCenterController extends AbstractController { - - protected TimelineBuilder $timelineBuilder; - protected PaginatorFactory $paginatorFactory; + protected TimelineBuilder $timelineBuilder; + private Security $security; - + public function __construct( TimelineBuilder $timelineBuilder, PaginatorFactory $paginatorFactory, @@ -52,15 +39,16 @@ class TimelineCenterController extends AbstractController /** * @Route("/{_locale}/center/timeline", - * name="chill_center_timeline", - * methods={"GET"} - * ) - */ + * name="chill_center_timeline", + * methods={"GET"} + * ) + */ public function centerAction(Request $request) { // collect reachable center for each group $user = $this->security->getUser(); $centers = []; + foreach ($user->getGroupCenters() as $group) { $centers[] = $group->getCenter(); } @@ -68,24 +56,26 @@ class TimelineCenterController extends AbstractController if (0 === count($centers)) { throw $this->createNotFoundException(); } - - $nbItems = $this->timelineBuilder->countItems('center', - [ 'centers' => $centers ] - ); - + + $nbItems = $this->timelineBuilder->countItems( + 'center', + ['centers' => $centers] + ); + $paginator = $this->paginatorFactory->create($nbItems); - - return $this->render('@ChillMain/Timeline/index.html.twig', array - ( + + return $this->render( + '@ChillMain/Timeline/index.html.twig', + [ 'timeline' => $this->timelineBuilder->getTimelineHTML( - 'center', - [ 'centers' => $centers ], + 'center', + ['centers' => $centers], $paginator->getCurrentPage()->getFirstItemNumber(), $paginator->getItemsPerPage() - ), + ), 'nb_items' => $nbItems, - 'paginator' => $paginator - ) + 'paginator' => $paginator, + ] ); } } diff --git a/src/Bundle/ChillMainBundle/Controller/UIController.php b/src/Bundle/ChillMainBundle/Controller/UIController.php index 75992bf15..97be4ca3c 100644 --- a/src/Bundle/ChillMainBundle/Controller/UIController.php +++ b/src/Bundle/ChillMainBundle/Controller/UIController.php @@ -1,30 +1,21 @@ - * - * 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\MainBundle\Controller; - -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Chill\MainBundle\Templating\UI\CountNotificationUser; /** - * Class UIController + * Chill is a software for social workers * - * @package Chill\MainBundle\Controller - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Controller; + +use Chill\MainBundle\Templating\UI\CountNotificationUser; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + +/** + * Class UIController. */ class UIController extends AbstractController { @@ -32,9 +23,9 @@ class UIController extends AbstractController CountNotificationUser $counter ) { $nb = $counter->getSumNotification($this->getUser()); - + return $this->render('@ChillMain/UI/notification_user_counter.html.twig', [ - 'nb' => $nb + 'nb' => $nb, ]); } } diff --git a/src/Bundle/ChillMainBundle/Controller/UserApiController.php b/src/Bundle/ChillMainBundle/Controller/UserApiController.php index 3742a7ded..75e566048 100644 --- a/src/Bundle/ChillMainBundle/Controller/UserApiController.php +++ b/src/Bundle/ChillMainBundle/Controller/UserApiController.php @@ -1,9 +1,17 @@ json($this->getUser(), JsonResponse::HTTP_OK, [], - [ "groups" => [ "read" ] ]); + return $this->json( + $this->getUser(), + JsonResponse::HTTP_OK, + [], + ['groups' => ['read']] + ); } - } diff --git a/src/Bundle/ChillMainBundle/Controller/UserController.php b/src/Bundle/ChillMainBundle/Controller/UserController.php index 735546eba..5b72115b8 100644 --- a/src/Bundle/ChillMainBundle/Controller/UserController.php +++ b/src/Bundle/ChillMainBundle/Controller/UserController.php @@ -1,55 +1,46 @@ passwordEncoder = $passwordEncoder; } - protected function createFormFor(string $action, $entity, string $formClass = null, array $formOptions = []): FormInterface + /** + * @Route("/{_locale}/admin/main/user/{uid}/add_link_groupcenter", + * name="admin_user_add_groupcenter") + * + * @param mixed $uid + */ + public function addLinkGroupCenterAction(Request $request, $uid): Response + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($uid); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + $form = $this->createAddLinkGroupCenterForm($user, $request); + $form->handleRequest($request); + + if ($form->isValid()) { + $groupCenter = $this->getPersistedGroupCenter( + $form[self::FORM_GROUP_CENTER_COMPOSED]->getData() + ); + $user->addGroupCenter($groupCenter); + + if ($this->validator->validate($user)->count() === 0) { + $em->flush(); + + $this->addFlash('success', $this->get('translator')->trans('The ' + . 'permissions have been successfully added to the user')); + + $returnPathParams = $request->query->has('returnPath') ? + ['returnPath' => $request->query->get('returnPath')] : []; + + return $this->redirect($this->generateUrl( + 'chill_crud_admin_user_edit', + array_merge(['id' => $uid], $returnPathParams) + )); + } + + foreach ($this->validator->validate($user) as $error) { + $this->addFlash('error', $error->getMessage()); + } + } + + return $this->render('@ChillMain/User/edit.html.twig', [ + 'entity' => $user, + 'edit_form' => $this->createEditForm($user)->createView(), + 'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user, $request)->createView(), + 'delete_groupcenter_form' => array_map( + static fn (Form $form) => $form->createView(), + iterator_to_array($this->getDeleteLinkGroupCenterByUser($user, $request), true) + ), + ]); + } + + /** + * @Route("/{_locale}/admin/main/user/{uid}/delete_link_groupcenter/{gcid}", + * name="admin_user_delete_groupcenter") + * + * @param mixed $uid + * @param mixed $gcid + */ + public function deleteLinkGroupCenterAction($uid, $gcid, Request $request): RedirectResponse + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($uid); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + $groupCenter = $em->getRepository('ChillMainBundle:GroupCenter') + ->find($gcid); + + if (!$groupCenter) { + throw $this->createNotFoundException('Unable to find groupCenter entity'); + } + + try { + $user->removeGroupCenter($groupCenter); + } catch (RuntimeException $ex) { + $this->addFlash('error', $this->get('translator')->trans($ex->getMessage())); + + return $this->redirect($this->generateUrl('chill_crud_admin_user_edit', ['id' => $uid])); + } + + $em->flush(); + + $this->addFlash('success', $this->get('translator') + ->trans('The permissions where removed.')); + + return $this->redirect($this->generateUrl('chill_crud_admin_user_edit', ['id' => $uid])); + } + + /** + * Displays a form to edit the user current location. + * + * @Route("/{_locale}/main/user/current-location/edit", name="chill_main_user_currentlocation_edit") + */ + public function editCurrentLocationAction(Request $request) + { + $user = $this->getUser(); + $form = $this->createForm(UserCurrentLocationType::class, $user) + ->add('submit', SubmitType::class, ['label' => 'Save']) + ->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $currentLocation = $form->get('currentLocation')->getData(); + + $user->setCurrentLocation($currentLocation); + + $this->getDoctrine()->getManager()->flush(); + $this->addFlash('success', $this->get('translator')->trans('Current location successfully updated')); + + return $this->redirect( + $request->query->has('returnPath') ? $request->query->get('returnPath') : + $this->generateUrl('chill_main_homepage') + ); + } + + return $this->render('@ChillMain/User/edit_current_location.html.twig', [ + 'entity' => $user, + 'edit_form' => $form->createView(), + ]); + } + + /** + * Displays a form to edit the user password. + * + * @Route("/{_locale}/admin/user/{id}/edit_password", name="admin_user_edit_password") + */ + public function editPasswordAction(User $user, Request $request) + { + $editForm = $this->createEditPasswordForm($user); + $editForm->handleRequest($request); + + if ($editForm->isSubmitted() && $editForm->isValid()) { + $password = $editForm->get('new_password')->getData(); + + // logging for prod + $this->logger->info('update password for an user', [ + 'by' => $this->getUser()->getUsername(), + 'user' => $user->getUsername(), + ]); + + $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); + + $this->getDoctrine()->getManager()->flush(); + $this->addFlash('success', $this->get('translator')->trans('Password successfully updated!')); + + return $this->redirect( + $request->query->has('returnPath') ? $request->query->get('returnPath') : + $this->generateUrl('chill_crud_admin_user_edit', ['id' => $user->getId()]) + ); + } + + return $this->render('@ChillMain/User/edit_password.html.twig', [ + 'entity' => $user, + 'edit_form' => $editForm->createView(), + ]); + } + + protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface { // for "new", add special config if ('new' === $action) { - return $this->createForm(UserType::class, $entity, array( - 'is_creation' => true - )); + return $this->createForm(UserType::class, $entity, [ + 'is_creation' => true, + ]); } // default behaviour return parent::createFormFor($action, $entity, $formClass, $formOptions); } + protected function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = []) + { + // add mini-forms for edit action + if ('edit' === $action) { + return array_merge( + $defaultTemplateParameters, + [ + 'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($entity, $request)->createView(), + 'delete_groupcenter_form' => array_map( + static function (Form $form) { + return $form->createView(); + }, + iterator_to_array($this->getDeleteLinkGroupCenterByUser($entity, $request), true) + ), + ] + ); + } + + // default behaviour + return parent::generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters); + } + protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) { // for "new", encode the password @@ -92,169 +269,61 @@ class UserController extends CRUDController return parent::orderQuery($action, $query, $request, $paginator); } - protected function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = []) + /** + * Create a form to add a link to a groupcenter. + */ + private function createAddLinkGroupCenterForm(User $user, Request $request): FormInterface { - // add mini-forms for edit action - if ("edit" === $action) { - return array_merge( - $defaultTemplateParameters, - [ - 'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($entity, $request)->createView(), - 'delete_groupcenter_form' => array_map( - function(\Symfony\Component\Form\Form $form) { - return $form->createView(); - }, - iterator_to_array($this->getDeleteLinkGroupCenterByUser($entity, $request), true) - ) - ] - ); - } + $returnPathParams = $request->query->has('returnPath') ? ['returnPath' => $request->query->get('returnPath')] : []; - // default behaviour - return parent::generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters); + return $this->createFormBuilder() + ->setAction($this->generateUrl( + 'admin_user_add_groupcenter', + array_merge($returnPathParams, ['uid' => $user->getId()]) + )) + ->setMethod('POST') + ->add(self::FORM_GROUP_CENTER_COMPOSED, ComposedGroupCenterType::class) + ->add('submit', SubmitType::class, ['label' => 'Add a new groupCenter']) + ->getForm(); } /** - * Displays a form to edit the user password. + * Creates a form to delete a link to a GroupCenter. * - * @Route("/{_locale}/admin/user/{id}/edit_password", name="admin_user_edit_password") + * @param mixed $request */ - public function editPasswordAction(User $user, Request $request) + private function createDeleteLinkGroupCenterForm(User $user, GroupCenter $groupCenter, $request): FormInterface { - $editForm = $this->createEditPasswordForm($user, $request); - $editForm->handleRequest($request); + $returnPathParams = $request->query->has('returnPath') ? ['returnPath' => $request->query->get('returnPath')] : []; - if ($editForm->isSubmitted() && $editForm->isValid()) { - $password = $editForm->get('new_password')->getData(); - - // logging for prod - $this->logger->info('update password for an user', [ - 'by' => $this->getUser()->getUsername(), - 'user' => $user->getUsername() - ]); - - $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); - - $this->getDoctrine()->getManager()->flush(); - $this->addFlash('success', $this->get('translator')->trans('Password successfully updated!')); - - return $this->redirect( - $request->query->has('returnPath') ? $request->query->get('returnPath') : - $this->generateUrl('chill_crud_admin_user_edit', ['id' => $user->getId()]) - ); - } - - return $this->render('@ChillMain/User/edit_password.html.twig', [ - 'entity' => $user, - 'edit_form' => $editForm->createView() - ]); + return $this->createFormBuilder() + ->setAction($this->generateUrl( + 'admin_user_delete_groupcenter', + array_merge($returnPathParams, ['uid' => $user->getId(), 'gcid' => $groupCenter->getId()]) + )) + ->setMethod('DELETE') + ->add('submit', SubmitType::class, ['label' => 'Delete']) + ->getForm(); } - /** - * - * - * @param User $user - * @return \Symfony\Component\Form\Form - */ - private function createEditPasswordForm(User $user) + private function createEditPasswordForm(User $user): FormInterface { - return $this->createForm(UserPasswordType::class, null, array( - 'user' => $user - )) - ->add('submit', SubmitType::class, array('label' => 'Change password')) - ->remove('actual_password') - ; + return $this->createForm( + UserPasswordType::class, + null, + [ + 'user' => $user, + ] + ) + ->add('submit', SubmitType::class, ['label' => 'Change password']) + ->remove('actual_password'); } - /** - * @Route("/{_locale}/admin/main/user/{uid}/delete_link_groupcenter/{gcid}", - * name="admin_user_delete_groupcenter") - */ - public function deleteLinkGroupCenterAction($uid, $gcid, Request $request): RedirectResponse + private function getDeleteLinkGroupCenterByUser(User $user, Request $request) { - $em = $this->getDoctrine()->getManager(); - - $user = $em->getRepository('ChillMainBundle:User')->find($uid); - - if (!$user) { - throw $this->createNotFoundException('Unable to find User entity.'); + foreach ($user->getGroupCenters() as $groupCenter) { + yield $groupCenter->getId() => $this->createDeleteLinkGroupCenterForm($user, $groupCenter, $request); } - - $groupCenter = $em->getRepository('ChillMainBundle:GroupCenter') - ->find($gcid); - - if (!$groupCenter) { - throw $this->createNotFoundException('Unable to find groupCenter entity'); - } - - try { - $user->removeGroupCenter($groupCenter); - } catch (\RuntimeException $ex) { - $this->addFlash('error', $this->get('translator')->trans($ex->getMessage())); - - return $this->redirect($this->generateUrl('chill_crud_admin_user_edit', array('id' => $uid))); - } - - $em->flush(); - - $this->addFlash('success', $this->get('translator') - ->trans('The permissions where removed.')); - - return $this->redirect($this->generateUrl('chill_crud_admin_user_edit', array('id' => $uid))); - - } - - /** - * @Route("/{_locale}/admin/main/user/{uid}/add_link_groupcenter", - * name="admin_user_add_groupcenter") - */ - public function addLinkGroupCenterAction(Request $request, $uid): RedirectResponse - { - $em = $this->getDoctrine()->getManager(); - - $user = $em->getRepository('ChillMainBundle:User')->find($uid); - - if (!$user) { - throw $this->createNotFoundException('Unable to find User entity.'); - } - - $form = $this->createAddLinkGroupCenterForm($user, $request); - $form->handleRequest($request); - - if ($form->isValid()) { - $groupCenter = $this->getPersistedGroupCenter( - $form[self::FORM_GROUP_CENTER_COMPOSED]->getData()); - $user->addGroupCenter($groupCenter); - - if ($this->validator->validate($user)->count() === 0) { - $em->flush(); - - $this->addFlash('success', $this->get('translator')->trans('The ' - . 'permissions have been successfully added to the user')); - - $returnPathParams = $request->query->has('returnPath') ? - ['returnPath' => $request->query->get('returnPath')] : []; - - return $this->redirect($this->generateUrl('chill_crud_admin_user_edit', - \array_merge(['id' => $uid], $returnPathParams))); - - } else { - foreach($this->validator->validate($user) as $error) - $this->addFlash('error', $error->getMessage()); - } - } - - return $this->render('@ChillMain/User/edit.html.twig', array( - 'entity' => $user, - 'edit_form' => $this->createEditForm($user)->createView(), - 'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user)->createView(), - 'delete_groupcenter_form' => array_map( - function(\Symfony\Component\Form\Form $form) { - return $form->createView(); - - }, - iterator_to_array($this->getDeleteLinkGroupCenterByUser($user), true)) - )); } private function getPersistedGroupCenter(GroupCenter $groupCenter) @@ -262,68 +331,17 @@ class UserController extends CRUDController $em = $this->getDoctrine()->getManager(); $groupCenterManaged = $em->getRepository('ChillMainBundle:GroupCenter') - ->findOneBy(array( - 'center' => $groupCenter->getCenter(), - 'permissionsGroup' => $groupCenter->getPermissionsGroup() - )); + ->findOneBy([ + 'center' => $groupCenter->getCenter(), + 'permissionsGroup' => $groupCenter->getPermissionsGroup(), + ]); if (!$groupCenterManaged) { $em->persist($groupCenter); + return $groupCenter; } return $groupCenterManaged; } - - /** - * Creates a form to delete a link to a GroupCenter - * - * @param mixed $permissionsGroup The entity id - * - * @return \Symfony\Component\Form\Form The form - */ - private function createDeleteLinkGroupCenterForm(User $user, GroupCenter $groupCenter, $request) - { - $returnPathParams = $request->query->has('returnPath') ? ['returnPath' => $request->query->get('returnPath')] : []; - - return $this->createFormBuilder() - ->setAction($this->generateUrl('admin_user_delete_groupcenter', - array_merge($returnPathParams, ['uid' => $user->getId(), 'gcid' => $groupCenter->getId()]))) - ->setMethod('DELETE') - ->add('submit', SubmitType::class, array('label' => 'Delete')) - ->getForm() - ; - } - - /** - * create a form to add a link to a groupcenter - * - * @param User $user - * @return \Symfony\Component\Form\Form - */ - private function createAddLinkGroupCenterForm(User $user, Request $request) - { - $returnPathParams = $request->query->has('returnPath') ? ['returnPath' => $request->query->get('returnPath')] : []; - - return $this->createFormBuilder() - ->setAction($this->generateUrl('admin_user_add_groupcenter', - array_merge($returnPathParams, ['uid' => $user->getId()]))) - ->setMethod('POST') - ->add(self::FORM_GROUP_CENTER_COMPOSED, ComposedGroupCenterType::class) - ->add('submit', SubmitType::class, array('label' => 'Add a new groupCenter')) - ->getForm() - ; - } - - /** - * - * @param User $user - */ - private function getDeleteLinkGroupCenterByUser(User $user, Request $request) - { - foreach ($user->getGroupCenters() as $groupCenter) { - yield $groupCenter->getId() => $this - ->createDeleteLinkGroupCenterForm($user, $groupCenter, $request); - } - } } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAbstractNotificationsTrait.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAbstractNotificationsTrait.php index 629b6657e..bbeab94b3 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAbstractNotificationsTrait.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAbstractNotificationsTrait.php @@ -1,34 +1,41 @@ notifs as $notif) { $entityId = $this->getReference($notif['entityRef'])->getId(); - print('Adding notification for '.$notif['entityClass'].'(entity id:'.$entityId.")\n"); + echo 'Adding notification for ' . $notif['entityClass'] . '(entity id:' . $entityId . ")\n"; $newNotif = (new Notification()) ->setMessage($notif['message']) ->setSender($this->getReference($notif['sender'])) ->setRelatedEntityClass($notif['entityClass']) ->setRelatedEntityId($entityId) - ->setDate(new \DateTimeImmutable('now')) - ->setRead([]) - ; + ->setDate(new DateTimeImmutable('now')) + ->setRead([]); foreach ($notif['addressees'] as $addressee) { $newNotif->addAddressee($this->getReference($addressee)); diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAddressReferences.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAddressReferences.php index 81bd1d909..a4afefa2b 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAddressReferences.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAddressReferences.php @@ -1,89 +1,51 @@ faker = \Faker\Factory::create('fr_FR'); } - /** - * - * @var ContainerInterface - */ - private $container; - - public function setContainer(ContainerInterface $container = null) + public function getOrder() { - $this->container = $container; - } - - public function getOrder() { return 51; } - - /** - * Create a random point - * - * @return Point - */ - private function getRandomPoint() + public function load(ObjectManager $manager) { - $lonBrussels = 4.35243; - $latBrussels = 50.84676; - $lon = $lonBrussels + 0.01 * rand(-5, 5); - $lat = $latBrussels + 0.01 * rand(-5, 5); - return Point::fromLonLat($lon, $lat); - } - - /** - * Create a random reference address - * - * @return AddressReference - */ - private function getRandomAddressReference() - { - $ar= new AddressReference(); - - $ar->setRefId($this->faker->numerify('ref-id-######')); - $ar->setStreet($this->faker->streetName); - $ar->setStreetNumber(rand(0,199)); - $ar ->setPoint($this->getRandomPoint()); - $ar->setPostcode($this->getReference( - LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] - )); - - $ar->setMunicipalityCode($ar->getPostcode()->getCode()); - - return $ar - ; - } - - public function load(ObjectManager $manager) { - echo "loading some reference address... \n"; - for ($i=0; $i<10; $i++) { + for ($i = 0; 10 > $i; ++$i) { $ar = $this->getRandomAddressReference(); $manager->persist($ar); } @@ -91,5 +53,45 @@ class LoadAddressReferences extends AbstractFixture implements ContainerAwareInt $manager->flush(); } + public function setContainer(?ContainerInterface $container = null) + { + $this->container = $container; + } + /** + * Create a random reference address. + * + * @return AddressReference + */ + private function getRandomAddressReference() + { + $ar = new AddressReference(); + + $ar->setRefId($this->faker->numerify('ref-id-######')); + $ar->setStreet($this->faker->streetName); + $ar->setStreetNumber((string) mt_rand(0, 199)); + $ar->setPoint($this->getRandomPoint()); + $ar->setPostcode($this->getReference( + LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] + )); + + $ar->setMunicipalityCode($ar->getPostcode()->getCode()); + + return $ar; + } + + /** + * Create a random point. + * + * @return Point + */ + private function getRandomPoint() + { + $lonBrussels = 4.35243; + $latBrussels = 50.84676; + $lon = $lonBrussels + 0.01 * mt_rand(-5, 5); + $lat = $latBrussels + 0.01 * mt_rand(-5, 5); + + return Point::fromLonLat($lon, $lat); + } } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php index a3c01fdf6..e4cd7d565 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCenters.php @@ -1,57 +1,41 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DataFixtures\ORM; +use Chill\MainBundle\Entity\Center; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Chill\MainBundle\Entity\Center; -/** - * - * - * @author Julien Fastré - */ class LoadCenters extends AbstractFixture implements OrderedFixtureInterface { + public static $centers = [ + [ + 'name' => 'Center A', + 'ref' => 'centerA', + ], + [ + 'name' => 'Center B', + 'ref' => 'centerB', + ], + ]; + + public static $refs = []; + public function getOrder() { return 100; } - - public static $centers = array( - array( - 'name' => 'Center A', - 'ref' => 'centerA' - ), - array( - 'name' => 'Center B', - 'ref' => 'centerB' - ) - ); - - public static $refs = array(); - + public function load(ObjectManager $manager) { foreach (static::$centers as $new) { @@ -62,7 +46,7 @@ class LoadCenters extends AbstractFixture implements OrderedFixtureInterface $this->addReference($new['ref'], $center); static::$refs[] = $new['ref']; } - + $manager->flush(); } } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php index b26b5b991..07cf976eb 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php @@ -1,5 +1,14 @@ ['fr' => "Monsieur" ], 'abbrev' => ['fr' => 'M.']], - ['name' => ['fr' => "Madame" ], 'abbrev' => ['fr' => 'Mme']], - ['name' => ['fr' => "Docteur" ], 'abbrev' => ['fr' => 'Dr']], - ['name' => ['fr' => "Professeur" ], 'abbrev' => ['fr' => 'Pr']], - ['name' => ['fr' => "Madame la Directrice" ], 'abbrev' => ['fr' => 'Mme']], - ['name' => ['fr' => "Monsieur le Directeur" ], 'abbrev' => ['fr' => 'M.']], - ['name' => ['fr' => "Madame la Maire" ]], - ['name' => ['fr' => "Monsieur le Maire" ]], - ['name' => ['fr' => "Maître" ], 'abbrev' => ['fr' => 'Me']], + ['name' => ['fr' => 'Madame'], 'abbrev' => ['fr' => 'Mme']], + ['name' => ['fr' => 'Monsieur'], 'abbrev' => ['fr' => 'M.']], + ['name' => ['fr' => 'Docteur'], 'abbrev' => ['fr' => 'Dr']], + ['name' => ['fr' => 'Professeur'], 'abbrev' => ['fr' => 'Pr']], + ['name' => ['fr' => 'Madame la Directrice'], 'abbrev' => ['fr' => 'Mme']], + ['name' => ['fr' => 'Monsieur le Directeur'], 'abbrev' => ['fr' => 'M.']], + ['name' => ['fr' => 'Madame la Maire']], + ['name' => ['fr' => 'Monsieur le Maire']], + ['name' => ['fr' => 'Maître'], 'abbrev' => ['fr' => 'Me']], ]; - foreach ( $civilities as $val) { + foreach ($civilities as $val) { $civility = (new Civility()) ->setName($val['name']) ->setAbbreviation($val['abbrev'] ?? []) diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCountries.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCountries.php index 2405b4a98..88750b340 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCountries.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCountries.php @@ -1,48 +1,53 @@ + * Load countries into database. */ -class LoadCountries extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface { - +class LoadCountries extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface +{ /** - * * @var ContainerInterface */ private $container; - - public function setContainer(ContainerInterface $container = null) + + public function getOrder() + { + return 20; + } + + public function load(ObjectManager $manager) + { + echo "loading countries... \n"; + + $languages = $this->container->getParameter('chill_main.available_languages'); + + foreach (LoadCountriesCommand::prepareCountryList($languages) as $country) { + $manager->persist($country); + } + + $manager->flush(); + } + + public function setContainer(?ContainerInterface $container = null) { $this->container = $container; } - - public function getOrder() { - return 20; - } - - public function load(ObjectManager $manager) { - - echo "loading countries... \n"; - - $languages = $this->container->getParameter('chill_main.available_languages'); - - foreach (LoadCountriesCommand::prepareCountryList($languages) as $country){ - $manager->persist($country); - } - - $manager->flush(); - } - - } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadGroupCenters.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadGroupCenters.php index d44a3bbbf..ad04709ab 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadGroupCenters.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadGroupCenters.php @@ -1,45 +1,29 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DataFixtures\ORM; +use Chill\MainBundle\Entity\GroupCenter; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Chill\MainBundle\Entity\GroupCenter; -use Chill\MainBundle\DataFixtures\ORM\LoadCenters; -use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup; -/** - * - * - * @author Julien Fastré - */ class LoadGroupCenters extends AbstractFixture implements OrderedFixtureInterface { + public static $refs = []; + public function getOrder() { return 500; } - - public static $refs = array(); public function load(ObjectManager $manager) { @@ -48,16 +32,16 @@ class LoadGroupCenters extends AbstractFixture implements OrderedFixtureInterfac $GroupCenter = new GroupCenter(); $GroupCenter->setCenter($this->getReference($centerRef)); $GroupCenter->setPermissionsGroup($this->getReference($permissionGroupRef)); - + $manager->persist($GroupCenter); - - $reference = $centerRef.'_'.$permissionGroupRef; + + $reference = $centerRef . '_' . $permissionGroupRef; $this->addReference($reference, $GroupCenter); static::$refs[] = $reference; - echo "Creating $reference... \n"; + echo "Creating {$reference}... \n"; } } - + $manager->flush(); } } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLanguages.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLanguages.php index a048b6b9f..47d63efdf 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLanguages.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLanguages.php @@ -1,80 +1,85 @@ + * Load languages into database. */ class LoadLanguages extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface { - // The regional version of language are language with _ in the code - // This array contains regional code to not exclude - private $regionalVersionToInclude = ["ro_MD"]; - // Array of ancien languages (to exclude) - private $ancientToExclude = ["ang", "egy", "fro", "goh", "grc", "la", "non", "peo", "pro", "sga", - "dum", "enm", "frm", "gmh", "mga", "akk", "phn", "zxx", "got", "und"]; - + private $ancientToExclude = ['ang', 'egy', 'fro', 'goh', 'grc', 'la', 'non', 'peo', 'pro', 'sga', + 'dum', 'enm', 'frm', 'gmh', 'mga', 'akk', 'phn', 'zxx', 'got', 'und', ]; + /** - * * @var ContainerInterface */ private $container; - - public function setContainer(ContainerInterface $container = null) + + // The regional version of language are language with _ in the code + // This array contains regional code to not exclude + private $regionalVersionToInclude = ['ro_MD']; + + public function getOrder() { - $this->container = $container; - } - - public function getOrder() { return 10; } - - public function load(ObjectManager $manager) { - + + public function load(ObjectManager $manager) + { echo "loading languages... \n"; - + foreach (Intl::getLanguageBundle()->getLanguageNames() as $code => $language) { if ( - !in_array($code, $this->regionalVersionToInclude) - && - !in_array($code, $this->ancientToExclude) + !in_array($code, $this->regionalVersionToInclude, true) + && !in_array($code, $this->ancientToExclude, true) ) { - $lang = (new Language()) - ->setId($code) - ->setName($this->prepareName($code)) - ; + ->setId($code) + ->setName($this->prepareName($code)); $manager->persist($lang); } } - + $manager->flush(); } - + + public function setContainer(?ContainerInterface $container = null) + { + $this->container = $container; + } + /** - * prepare names for languages - * - * @param string $languageCode + * Prepare names for languages. + * * @return string[] languages name indexed by available language code */ - private function prepareName($languageCode) { + private function prepareName(string $languageCode): array + { + $names = []; + foreach ($this->container->getParameter('chill_main.available_languages') as $lang) { $names[$lang] = Intl::getLanguageBundle()->getLanguageName($languageCode); } - + return $names; } - - } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php index 4e103cc8e..3e24d034c 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadLocationType.php @@ -1,57 +1,58 @@ container = $container; - } - - public function getOrder() { return 52; } - public function load(ObjectManager $manager): void { - + public function load(ObjectManager $manager): void + { echo "loading some location type... \n"; $arr = [ [ 'name' => ['fr' => 'Mairie'], - 'address_required' => LocationType::STATUS_OPTIONAL + 'address_required' => LocationType::STATUS_OPTIONAL, ], [ 'name' => ['fr' => 'Guichet d\'accueil'], - 'address_required' => LocationType::STATUS_OPTIONAL + 'address_required' => LocationType::STATUS_OPTIONAL, ], [ 'name' => ['fr' => 'Domicile de l\'usager'], - 'address_required' => LocationType::STATUS_REQUIRED + 'address_required' => LocationType::STATUS_REQUIRED, ], [ 'name' => ['fr' => 'Centre d\'aide sociale'], - 'address_required' => LocationType::STATUS_OPTIONAL + 'address_required' => LocationType::STATUS_OPTIONAL, ], ]; @@ -66,4 +67,9 @@ class LoadLocationType extends AbstractFixture implements ContainerAwareInterfac $manager->flush(); } -} \ No newline at end of file + + public function setContainer(?ContainerInterface $container = null) + { + $this->container = $container; + } +} diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPermissionsGroup.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPermissionsGroup.php index 92d459aac..dc7773765 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPermissionsGroup.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPermissionsGroup.php @@ -1,87 +1,74 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DataFixtures\ORM; +use Chill\MainBundle\Entity\PermissionsGroup; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Chill\MainBundle\Entity\PermissionsGroup; -/** - * - * - * @author Julien Fastré - */ class LoadPermissionsGroup extends AbstractFixture implements OrderedFixtureInterface { + public static $permissionGroup = [ + [ + 'name' => 'social', + 'role_scopes' => [ + 'role_scope_CHILL_FOO_EDIT_social', + 'role_scope_CHILL_FOO_SEE_administrative', + 'role_scope_CHILL_FOO_EDIT_all', + ], + ], + [ + 'name' => 'administrative', + 'role_scopes' => [ + 'role_scope_CHILL_FOO_SEE_social', + 'role_scope_CHILL_FOO_EDIT_administrative', + 'role_scope_CHILL_FOO_EDIT_all', + ], + ], + [ + 'name' => 'direction', + 'role_scopes' => [ + 'role_scope_CHILL_FOO_EDIT_all', + 'role_scope_CHILL_FOO_SEE_DETAILS_social', + 'role_scope_CHILL_FOO_SEE_DETAILS_administrative', + ], + ], + ]; + + public static $refs = []; + public function getOrder() { return 400; } - - public static $permissionGroup = array( - array( - 'name' => 'social', - 'role_scopes' => array( - 'role_scope_CHILL_FOO_EDIT_social', - 'role_scope_CHILL_FOO_SEE_administrative', - "role_scope_CHILL_FOO_EDIT_all" - ) - ), - array( - 'name' => 'administrative', - 'role_scopes' => array( - "role_scope_CHILL_FOO_SEE_social", - "role_scope_CHILL_FOO_EDIT_administrative", - "role_scope_CHILL_FOO_EDIT_all" - ) - ), - array( - 'name' => 'direction', - 'role_scopes' => array( - "role_scope_CHILL_FOO_EDIT_all", - "role_scope_CHILL_FOO_SEE_DETAILS_social", - "role_scope_CHILL_FOO_SEE_DETAILS_administrative" - ) - ) - ); - - public static $refs = array(); public function load(ObjectManager $manager) { foreach (static::$permissionGroup as $new) { $permissionGroup = new PermissionsGroup(); $permissionGroup->setName($new['name']); + foreach ($new['role_scopes'] as $roleScopeRef) { $permissionGroup->addRoleScope($this->getReference($roleScopeRef)); } - + $manager->persist($permissionGroup); - $reference = 'permission_group_'.$new['name']; - echo "Creating $reference \n"; + $reference = 'permission_group_' . $new['name']; + echo "Creating {$reference} \n"; $this->setReference($reference, $permissionGroup); static::$refs[] = $reference; } - + $manager->flush(); } } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php index 947243f55..ecf839450 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadPostalCodes.php @@ -1,48 +1,336 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DataFixtures\ORM; use Chill\MainBundle\Doctrine\Model\Point; use Chill\MainBundle\Entity\Country; +use Chill\MainBundle\Entity\PostalCode; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Chill\MainBundle\Entity\PostalCode; +use function strtolower; +use function ucwords; /** - * Description of LoadPostalCodes - * - * @author Julien Fastré - * @author Champs Libres + * Description of LoadPostalCodes. */ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface { + public static $refs = []; + + private static $postalCodeBelgium = <<<'EOF' + 1000,BRUXELLES,BE + 1020,BRUXELLES,BE + 1030,SCHAERBEEK,BE + 1040,ETTERBEEK,BE + 1050,IXELLES,BE + 1060,SAINT-GILLES,BE + 1070,ANDERLECHT,BE + 1080,MOLENBEEK-SAINT-JEAN,BE + 1081,KOEKELBERG,BE + 1082,BERCHEM-SAINTE-AGATHE,BE + 1083,GANSHOREN,BE + 1090,JETTE,BE + 1120,BRUXELLES,BE + 1130,BRUXELLES,BE + 1140,EVERE,BE + 1150,WOLUWE-SAINT-PIERRE,BE + 1160,AUDERGHEM,BE + 1170,WATERMAEL-BOITSFORT,BE + 1180,UCCLE,BE + 1190,FOREST,BE + 1200,WOLUWE-SAINT-LAMBERT,BE + 1210,SAINT-JOSSE-TEN-NOODE,BE + 1300,WAVRE,BE + 1300,WAVRE,BE + 1301,WAVRE,BE + 1310,LA HULPE,BE + 1315,INCOURT,BE + 1315,INCOURT,BE + 1315,INCOURT,BE + 1315,INCOURT,BE + 1315,INCOURT,BE + 1320,BEAUVECHAIN,BE + EOF; + + private static $postalCodeFrance = <<<'EOF' + 85000,LA ROCHE SUR YON,FR,85191,46.6675261644,-1.4077954093,INSEE + 85000,MOUILLERON LE CAPTIF,FR,85155,46.7104764993,-1.46129661418,INSEE + 85100,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE + 85110,STE CECILE,FR,85202,46.7309688326,-1.12107316048,INSEE + 85110,LA JAUDONNIERE,FR,85115,46.6488761831,-0.962477574588,INSEE + 85110,ST GERMAIN DE PRINCAY,FR,85220,46.7356314659,-1.04299885081,INSEE + 85110,MONSIREIGNE,FR,85145,46.7383480028,-0.931953130855,INSEE + 85110,ST VINCENT STERLANGES,FR,85276,46.7397220689,-1.08371759277,INSEE + 85110,SIGOURNAIS,FR,85282,46.7140097406,-0.98747730882,INSEE + 85110,CHANTONNAY,FR,85051,46.6690167793,-1.04372588019,INSEE + 85110,ST PROUANT,FR,85266,46.7502017421,-0.974504061491,INSEE + 85120,LA CHAPELLE AUX LYS,FR,85053,46.6221916887,-0.642706103195,INSEE + 85120,VOUVANT,FR,85305,46.5626835135,-0.764380170382,INSEE + 85120,ANTIGNY,FR,85005,46.6191770822,-0.767030058653,INSEE + 85120,ST MAURICE DES NOUES,FR,85251,46.5955876221,-0.724327725888,INSEE + 85120,LOGE FOUGEREUSE,FR,85125,46.6180854641,-0.6899276733,INSEE + 85120,LA TARDIERE,FR,85289,46.663737434,-0.727562430214,INSEE + 85120,LA CHATAIGNERAIE,FR,85059,46.6416143401,-0.739561966419,INSEE + 85120,ST HILAIRE DE VOUST,FR,85229,46.5914988312,-0.651486245674,INSEE + 85120,BREUIL BARRET,FR,85037,46.6503266235,-0.671948654426,INSEE + 85120,ST PIERRE DU CHEMIN,FR,85264,46.6957771744,-0.701777715154,INSEE + 85130,LA GAUBRETIERE,FR,85097,46.9345007303,-1.05578200702,INSEE + 85130,ST MARTIN DES TILLEULS,FR,85247,46.9711539531,-1.06282621567,INSEE + 85130,TIFFAUGES,FR,85293,47.0020573556,-1.09858009203,INSEE + 85130,CHANVERRIE,FR,85302,46.9634774521,-0.985340006089,INSEE + 85130,BAZOGES EN PAILLERS,FR,85013,46.9213757643,-1.14415666313,INSEE + 85130,LES LANDES GENUSSON,FR,85119,46.9663828627,-1.12900644447,INSEE + 85130,ST AUBIN DES ORMEAUX,FR,85198,46.9958175597,-1.04216568722,INSEE + 85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE + 85140,LA MERLATIERE,FR,85142,46.7557703112,-1.29794577,INSEE + 85140,ST MARTIN DES NOYERS,FR,85246,46.7239461187,-1.20379080965,INSEE + 85140,CHAUCHE,FR,85064,46.8282791899,-1.27090860656,INSEE + 85150,ST MATHURIN,FR,85250,46.5686332748,-1.70787622288,INSEE + 85150,MARTINET,FR,85138,46.6620680463,-1.6772013304,INSEE + 85150,STE FLAIVE DES LOUPS,FR,85211,46.611019489,-1.58031627863,INSEE + 85150,STE FOY,FR,85214,46.5327600427,-1.69243074733,INSEE + 85150,ST JULIEN DES LANDES,FR,85236,46.6395925444,-1.7159724914,INSEE + 85150,ST GEORGES DE POINTINDOUX,FR,85218,46.6423470977,-1.62881823574,INSEE + 85150,LE GIROUARD,FR,85099,46.5726064909,-1.58872487716,INSEE + 85150,LANDERONDE,FR,85118,46.6549237031,-1.57351777893,INSEE + 85150,LES ACHARDS,FR,85152,46.6163645636,-1.65038156849,INSEE + 85150,VAIRE,FR,85298,46.6055340621,-1.74863672042,INSEE + 85160,ST JEAN DE MONTS,FR,85234,46.8021968737,-2.04839789308,INSEE + 85170,BELLEVIGNY,FR,85019,46.7756383534,-1.43313700054,INSEE + 85170,LE POIRE SUR VIE,FR,85178,46.769919754,-1.50488626452,INSEE + 85170,BEAUFOU,FR,85015,46.8191122027,-1.52479250801,INSEE + 85170,DOMPIERRE SUR YON,FR,85081,46.7599858068,-1.37275519417,INSEE + 85170,LES LUCS SUR BOULOGNE,FR,85129,46.8527299002,-1.48398928084,INSEE + 85170,ST DENIS LA CHEVASSE,FR,85208,46.8325959261,-1.3830312677,INSEE + 85180,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE + 85190,AIZENAY,FR,85003,46.7384516809,-1.62702889721,INSEE + 85190,VENANSAULT,FR,85300,46.685677363,-1.54112129191,INSEE + 85190,MACHE,FR,85130,46.771364944,-1.69526445062,INSEE + 85190,BEAULIEU SOUS LA ROCHE,FR,85016,46.6872087211,-1.62355064963,INSEE + 85190,LA GENETOUZE,FR,85098,46.7244524541,-1.50410719693,INSEE + 85200,PISSOTTE,FR,85176,46.5010870694,-0.808352236192,INSEE + 85200,AUCHAY SUR VENDEE,FR,85009,46.4474386161,-0.876574265149,INSEE + 85200,FONTENAY LE COMTE,FR,85092,46.4563186117,-0.793449510859,INSEE + 85200,MERVENT,FR,85143,46.5325327351,-0.748519927998,INSEE + 85200,DOIX LES FONTAINES,FR,85080,46.3849492327,-0.806840287485,INSEE + 85200,LONGEVES,FR,85126,46.4722105292,-0.858917690239,INSEE + 85200,ST MARTIN DE FRAIGNEAU,FR,85244,46.4289052087,-0.758948963227,INSEE + 85200,SERIGNE,FR,85281,46.5054321828,-0.848819460581,INSEE + 85200,BOURNEAU,FR,85033,46.5476882922,-0.813838020265,INSEE + 85200,ST MICHEL LE CLOUCQ,FR,85256,46.4861591475,-0.743056336646,INSEE + 85200,MONTREUIL,FR,85148,46.3973419593,-0.840846860992,INSEE + 85200,L ORBRIE,FR,85167,46.4997145725,-0.77427886573,INSEE + 85210,ST JEAN DE BEUGNE,FR,85233,46.5196817523,-1.10826075013,INSEE + 85210,ST MARTIN LARS EN STE HERMINE,FR,85248,46.5970244335,-0.976384286709,INSEE + 85210,LA REORTHE,FR,85188,46.6113748938,-1.04881036553,INSEE + 85210,ST AUBIN LA PLAINE,FR,85199,46.5040293195,-1.06506577005,INSEE + 85210,ST JUIRE CHAMPGILLON,FR,85235,46.5882648491,-1.00959676911,INSEE + 85210,LA CHAPELLE THEMER,FR,85056,46.5639307793,-0.960376685588,INSEE + 85210,ST ETIENNE DE BRILLOUET,FR,85209,46.5138850327,-1.01157660374,INSEE + 85210,STE HERMINE,FR,85223,46.5572659953,-1.07210861039,INSEE + 85210,THIRE,FR,85290,46.5433098199,-1.00699777534,INSEE + 85220,ST MAIXENT SUR VIE,FR,85239,46.7329496925,-1.82595152879,INSEE + 85220,LA CHAIZE GIRAUD,FR,85045,46.6476375058,-1.81865076161,INSEE + 85220,LANDEVIEILLE,FR,85120,46.6444349925,-1.7854367847,INSEE + 85220,L AIGUILLON SUR VIE,FR,85002,46.6706426618,-1.82599992318,INSEE + 85220,COEX,FR,85070,46.7078707764,-1.75788339462,INSEE + 85220,ST REVEREND,FR,85268,46.7057741864,-1.83155480996,INSEE + 85220,APREMONT,FR,85006,46.7572682339,-1.74841313647,INSEE + 85220,LA CHAPELLE HERMIER,FR,85054,46.6826679204,-1.72083372442,INSEE + 85220,COMMEQUIERS,FR,85071,46.7674752232,-1.82534079642,INSEE + 85230,BEAUVOIR SUR MER,FR,85018,46.9086155426,-2.06349351302,INSEE + 85230,BOUIN,FR,85029,46.9815930867,-2.00423808381,INSEE + 85230,ST URBAIN,FR,85273,46.8818371328,-2.01607828912,INSEE + 85230,ST GERVAIS,FR,85221,46.9285711589,-1.98059327519,INSEE + 85240,MARILLET,FR,85136,46.5667525381,-0.634287713939,INSEE + 85240,ST HILAIRE DES LOGES,FR,85227,46.4747117878,-0.650611151998,INSEE + 85240,FAYMOREAU,FR,85087,46.5427361252,-0.624271378946,INSEE + 85240,XANTON CHASSENON,FR,85306,46.4519408659,-0.706316598666,INSEE + 85240,FOUSSAIS PAYRE,FR,85094,46.5230750581,-0.687135962627,INSEE + 85240,RIVES D AUTISE,FR,85162,46.424726987,-0.665995249042,INSEE + 85240,PUY DE SERRE,FR,85184,46.5650384637,-0.680144631346,INSEE + 85250,ST ANDRE GOULE D OIE,FR,85196,46.8410224478,-1.19644211396,INSEE + 85250,LA RABATELIERE,FR,85186,46.8584147661,-1.2569733759,INSEE + 85250,CHAVAGNES EN PAILLERS,FR,85065,46.8951394423,-1.24054768713,INSEE + 85250,ST FULGENT,FR,85215,46.8705618525,-1.16246465678,INSEE + 85250,VENDRENNES,FR,85301,46.8226741756,-1.11650982164,INSEE + 85260,LA COPECHAGNIERE,FR,85072,46.8523980181,-1.34349746898,INSEE + 85260,L HERBERGEMENT,FR,85108,46.9166207979,-1.37033557148,INSEE + 85260,MONTREVERD,FR,85197,46.9277672307,-1.4126154924,INSEE + 85260,LES BROUZILS,FR,85038,46.8854235235,-1.33186892233,INSEE + 85270,NOTRE DAME DE RIEZ,FR,85189,46.7532179022,-1.8935292542,INSEE + 85270,ST HILAIRE DE RIEZ,FR,85226,46.7432732188,-1.96439228965,INSEE + 85280,LA FERRIERE,FR,85089,46.7215872927,-1.33469332327,INSEE + 85290,MORTAGNE SUR SEVRE,FR,85151,46.9910941319,-0.946500033344,INSEE + 85290,ST LAURENT SUR SEVRE,FR,85238,46.9506837971,-0.901123752328,INSEE + 85300,CHALLANS,FR,85047,46.8354416653,-1.84036683139,INSEE + 85300,FROIDFOND,FR,85095,46.8789464367,-1.75511438567,INSEE + 85300,SOULLANS,FR,85284,46.7951288466,-1.91392699457,INSEE + 85300,LE PERRIER,FR,85172,46.8196487652,-1.97926629071,INSEE + 85300,SALLERTAINE,FR,85280,46.8659054157,-1.94894081389,INSEE + 85310,LA CHAIZE LE VICOMTE,FR,85046,46.6729533879,-1.29188591019,INSEE + 85310,NESMY,FR,85160,46.5921936479,-1.40947698594,INSEE + 85310,RIVES DE L YON,FR,85213,46.605637391,-1.3354497172,INSEE + 85310,LE TABLIER,FR,85285,46.5596307281,-1.32788759657,INSEE + 85320,CHATEAU GUIBERT,FR,85061,46.5741109302,-1.25524886228,INSEE + 85320,LES PINEAUX,FR,85175,46.599225902,-1.17865799724,INSEE + 85320,ROSNAY,FR,85193,46.5324344973,-1.3007139449,INSEE + 85320,BESSAY,FR,85023,46.5397253861,-1.17028433093,INSEE + 85320,LA BRETONNIERE LA CLAYE,FR,85036,46.4879459421,-1.26773426545,INSEE + 85320,CORPE,FR,85073,46.5050234767,-1.17034425311,INSEE + 85320,MAREUIL SUR LAY DISSAIS,FR,85135,46.5335825488,-1.22688907859,INSEE + 85320,PEAULT,FR,85171,46.502029199,-1.22708559855,INSEE + 85320,LA COUTURE,FR,85074,46.523938732,-1.26493227292,INSEE + 85320,MOUTIERS SUR LE LAY,FR,85157,46.5651677306,-1.16826489836,INSEE + 85320,STE PEXINE,FR,85261,46.5596018797,-1.12406235901,INSEE + 85330,NOIRMOUTIER EN L ILE,FR,85163,47.0086655085,-2.26243620205,INSEE + 85340,L ILE D OLONNE,FR,85112,46.570163703,-1.7737502368,INSEE + 85340,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE + 85350,L ILE D YEU,FR,85113,46.7093514816,-2.34712702345,INSEE + 85360,LA TRANCHE SUR MER,FR,85294,46.3564601605,-1.43136322126,INSEE + 85370,MOUZEUIL ST MARTIN,FR,85158,46.4591118412,-0.984449849889,INSEE + 85370,NALLIERS,FR,85159,46.4658962703,-1.03958611312,INSEE + 85370,LE LANGON,FR,85121,46.4393119039,-0.947017086151,INSEE + 85390,BAZOGES EN PAREDS,FR,85014,46.6602005512,-0.914053446792,INSEE + 85390,ST MAURICE LE GIRARD,FR,85252,46.6398624578,-0.810875649028,INSEE + 85390,TALLUD STE GEMME,FR,85287,46.6949386862,-0.886169517112,INSEE + 85390,CHAVAGNES LES REDOUX,FR,85066,46.7101499475,-0.915900131393,INSEE + 85390,CHEFFOIS,FR,85067,46.6786935635,-0.782949851125,INSEE + 85390,MOUILLERON ST GERMAIN,FR,85154,46.6612700667,-0.846784201071,INSEE + 85400,STE GEMME LA PLAINE,FR,85216,46.4732196212,-1.11103084694,INSEE + 85400,LAIROUX,FR,85117,46.4496842668,-1.27114022202,INSEE + 85400,LUCON,FR,85128,46.4510564854,-1.16449285012,INSEE + 85400,LES MAGNILS REIGNIERS,FR,85131,46.4635045649,-1.210635375,INSEE + 85400,CHASNAIS,FR,85058,46.4459908481,-1.2385924923,INSEE + 85410,ST LAURENT DE LA SALLE,FR,85237,46.5854041653,-0.922177315485,INSEE + 85410,LA CAILLERE ST HILAIRE,FR,85040,46.6293907412,-0.933153931505,INSEE + 85410,ST CYR DES GATS,FR,85205,46.572397925,-0.86344873853,INSEE + 85410,THOUARSAIS BOUILDROUX,FR,85292,46.6062740621,-0.873461023865,INSEE + 85410,CEZAIS,FR,85041,46.5917363748,-0.802618133558,INSEE + 85410,ST SULPICE EN PAREDS,FR,85271,46.6130038733,-0.8310839288,INSEE + 85420,LE MAZEAU,FR,85139,46.3298580373,-0.672957405035,INSEE + 85420,LIEZ,FR,85123,46.3698532376,-0.70502476758,INSEE + 85420,BOUILLE COURDAULT,FR,85028,46.3847932448,-0.684917815779,INSEE + 85420,DAMVIX,FR,85078,46.32063079,-0.743504259797,INSEE + 85420,MAILLE,FR,85132,46.3417503082,-0.787487297301,INSEE + 85420,ST PIERRE LE VIEUX,FR,85265,46.4009643491,-0.742816267425,INSEE + 85420,ST SIGISMOND,FR,85269,46.3368577973,-0.707293731101,INSEE + 85420,MAILLEZAIS,FR,85133,46.3642178261,-0.750260780443,INSEE + 85420,RIVES D AUTISE,FR,85162,46.424726987,-0.665995249042,INSEE + 85430,AUBIGNY LES CLOUZEAUX,FR,85008,46.6028241769,-1.46743549114,INSEE + 85430,NIEUL LE DOLENT,FR,85161,46.5676509922,-1.51560194548,INSEE + 85430,LA BOISSIERE DES LANDES,FR,85026,46.5581861734,-1.44371985689,INSEE + 85440,ST HILAIRE LA FORET,FR,85231,46.4551155186,-1.53048160541,INSEE + 85440,TALMONT ST HILAIRE,FR,85288,46.475786445,-1.62751498166,INSEE + 85440,POIROUX,FR,85179,46.5107890457,-1.53929317556,INSEE + 85440,GROSBREUIL,FR,85103,46.5390090882,-1.6072005484,INSEE + 85440,AVRILLE,FR,85010,46.4744272125,-1.49524360118,INSEE + 85450,CHAMPAGNE LES MARAIS,FR,85049,46.3735020647,-1.13380723653,INSEE + 85450,LA TAILLEE,FR,85286,46.3852513569,-0.941017792066,INSEE + 85450,CHAILLE LES MARAIS,FR,85042,46.3853555319,-1.01044079362,INSEE + 85450,VOUILLE LES MARAIS,FR,85304,46.3891941167,-0.968106001439,INSEE + 85450,MOREILLES,FR,85149,46.4218721314,-1.09404530407,INSEE + 85450,PUYRAVAULT,FR,85185,46.3653834101,-1.09115660367,INSEE + 85450,STE RADEGONDE DES NOYERS,FR,85267,46.3694246909,-1.06671995264,INSEE + 85460,LA FAUTE SUR MER,FR,85307,46.3199919131,-1.31487049579,INSEE + 85460,L AIGUILLON SUR MER,FR,85001,46.304138479,-1.2623239198,INSEE + 85470,BRETIGNOLLES SUR MER,FR,85035,46.6374826705,-1.86324200464,INSEE + 85470,BREM SUR MER,FR,85243,46.6118566989,-1.81003917923,INSEE + 85480,BOURNEZEAU,FR,85034,46.6296975315,-1.14101742229,INSEE + 85480,FOUGERE,FR,85093,46.6617881911,-1.23612691916,INSEE + 85480,ST HILAIRE LE VOUHIS,FR,85232,46.6859198669,-1.15222590884,INSEE + 85480,THORIGNY,FR,85291,46.6179795025,-1.24888057642,INSEE + 85490,BENET,FR,85020,46.368873213,-0.613959918706,INSEE + 85500,BEAUREPAIRE,FR,85017,46.9050210355,-1.10144867013,INSEE + 85500,ST PAUL EN PAREDS,FR,85259,46.8303789022,-0.964515191283,INSEE + 85500,LES HERBIERS,FR,85109,46.8666125813,-1.02216086186,INSEE + 85500,MESNARD LA BAROTIERE,FR,85144,46.851716793,-1.10954466033,INSEE + 85500,CHANVERRIE,FR,85302,46.9634774521,-0.985340006089,INSEE + 85510,ROCHETREJOUX,FR,85192,46.7852546732,-0.996743019108,INSEE + 85510,LE BOUPERE,FR,85031,46.7877960262,-0.930897406714,INSEE + 85520,JARD SUR MER,FR,85114,46.4246376808,-1.60014921643,INSEE + 85520,ST VINCENT SUR JARD,FR,85278,46.4297504489,-1.54956205778,INSEE + 85530,LA BRUFFIERE,FR,85039,47.0148006973,-1.18329758318,INSEE + 85540,LE CHAMP ST PERE,FR,85050,46.5157210212,-1.33946630196,INSEE + 85540,LE GIVRE,FR,85101,46.4729146043,-1.40256149118,INSEE + 85540,MOUTIERS LES MAUXFAITS,FR,85156,46.489279204,-1.43622387207,INSEE + 85540,ST VINCENT SUR GRAON,FR,85277,46.5034756656,-1.382954206,INSEE + 85540,LA JONCHERE,FR,85116,46.4358647401,-1.38517843312,INSEE + 85540,ST BENOIST SUR MER,FR,85201,46.4266286403,-1.3399129604,INSEE + 85540,CURZON,FR,85077,46.4512376923,-1.30138059216,INSEE + 85540,ST AVAUGOURD DES LANDES,FR,85200,46.5136722903,-1.47528120789,INSEE + 85540,ST CYR EN TALMONDAIS,FR,85206,46.4597334032,-1.33722144355,INSEE + 85550,LA BARRE DE MONTS,FR,85012,46.8722784154,-2.09984018879,INSEE + 85560,LE BERNARD,FR,85022,46.44832528,-1.43979865314,INSEE + 85560,LONGEVILLE SUR MER,FR,85127,46.4091029013,-1.47711855345,INSEE + 85570,POUILLE,FR,85181,46.5022256779,-0.955223380119,INSEE + 85570,ST VALERIEN,FR,85274,46.5348508899,-0.944470507825,INSEE + 85570,L HERMENAULT,FR,85110,46.5156262822,-0.898430664265,INSEE + 85570,PETOSSE,FR,85174,46.4796848965,-0.911734176662,INSEE + 85570,ST MARTIN DES FONTAINES,FR,85245,46.5464637508,-0.907394581139,INSEE + 85570,MARSAIS STE RADEGONDE,FR,85137,46.5380790745,-0.868868309437,INSEE + 85580,ST DENIS DU PAYRE,FR,85207,46.4118936776,-1.27222282402,INSEE + 85580,ST MICHEL EN L HERM,FR,85255,46.3366903175,-1.2483968538,INSEE + 85580,TRIAIZE,FR,85297,46.3792685111,-1.19928351422,INSEE + 85580,GRUES,FR,85104,46.3813091348,-1.32364519268,INSEE + 85590,TREIZE VENTS,FR,85296,46.9168130123,-0.845959158017,INSEE + 85590,LES EPESSES,FR,85082,46.8917166066,-0.903422756546,INSEE + 85590,MALLIEVRE,FR,85134,46.9112847287,-0.864977836096,INSEE + 85590,ST MALO DU BOIS,FR,85240,46.9248120291,-0.914182961099,INSEE + 85590,ST MARS LA REORTHE,FR,85242,46.8597253005,-0.925777900202,INSEE + 85600,LA BOISSIERE DE MONTAIGU,FR,85025,46.9451858636,-1.1916392484,INSEE + 85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE + 85600,TREIZE SEPTIERS,FR,85295,46.9975586143,-1.23193361154,INSEE + 85610,CUGAND,FR,85076,47.0602388146,-1.25289811103,INSEE + 85610,LA BERNARDIERE,FR,85021,47.0361828072,-1.27390355206,INSEE + 85620,ROCHESERVIERE,FR,85190,46.9274273036,-1.50132208111,INSEE + 85630,BARBATRE,FR,85011,46.9335754783,-2.16743559847,INSEE + 85640,MOUCHAMPS,FR,85153,46.7870550926,-1.05454102867,INSEE + 85660,ST PHILBERT DE BOUAINE,FR,85262,46.9927907526,-1.5073882242,INSEE + 85670,LA CHAPELLE PALLUAU,FR,85055,46.7873638997,-1.62492863273,INSEE + 85670,FALLERON,FR,85086,46.8623928354,-1.70108938038,INSEE + 85670,ST ETIENNE DU BOIS,FR,85210,46.8418481774,-1.59617737479,INSEE + 85670,ST PAUL MONT PENIT,FR,85260,46.8070059547,-1.66964833149,INSEE + 85670,ST CHRISTOPHE DU LIGNERON,FR,85204,46.8151386519,-1.74035413493,INSEE + 85670,GRAND LANDES,FR,85102,46.8483283063,-1.64453002578,INSEE + 85670,PALLUAU,FR,85169,46.8063002019,-1.60225256402,INSEE + 85680,LA GUERINIERE,FR,85106,46.9669962053,-2.2302799245,INSEE + 85690,NOTRE DAME DE MONTS,FR,85164,46.8424284611,-2.10928732775,INSEE + 85700,SEVREMONT,FR,85090,46.8211105864,-0.854584153953,INSEE + 85700,LA MEILLERAIE TILLAY,FR,85140,46.742582825,-0.85478763606,INSEE + 85700,MONTOURNAIS,FR,85147,46.7502937556,-0.770013941158,INSEE + 85700,POUZAUGES,FR,85182,46.7812581702,-0.828778359084,INSEE + 85700,REAUMUR,FR,85187,46.7145137269,-0.816742537248,INSEE + 85700,MENOMBLET,FR,85141,46.7301338667,-0.728654955878,INSEE + 85700,ST MESMIN,FR,85254,46.8005779435,-0.748892533741,INSEE + 85710,BOIS DE CENE,FR,85024,46.9479643351,-1.89668693466,INSEE + 85710,CHATEAUNEUF,FR,85062,46.916944435,-1.9261131832,INSEE + 85710,LA GARNACHE,FR,85096,46.8977541296,-1.82443040539,INSEE + 85740,L EPINE,FR,85083,46.9843405667,-2.26449527608,INSEE + 85750,ANGLES,FR,85004,46.3870511077,-1.40049386944,INSEE + 85770,LES VELLUIRE SUR VENDEE,FR,85177,46.4190919441,-0.910475769222,INSEE + 85770,VIX,FR,85303,46.3543018169,-0.856628326667,INSEE + 85770,LE GUE DE VELLUIRE,FR,85105,46.3675950645,-0.905432724485,INSEE + 85770,L ILE D ELLE,FR,85111,46.3334258655,-0.919100677098,INSEE + 85800,ST GILLES CROIX DE VIE,FR,85222,46.6904708814,-1.91946363327,INSEE + 85800,LE FENOUILLER,FR,85088,46.7161264566,-1.89206667498,INSEE + 85800,GIVRAND,FR,85100,46.6822701061,-1.8787272243,INSEE + EOF; + public function getOrder() { return 50; } - - public static $refs = []; public function load(ObjectManager $manager) { @@ -51,342 +339,40 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface $this->loadPostalCodeCSV($manager, self::$postalCodeFrance, 'FR'); } - private function loadPostalCodeCSV(ObjectManager $manager, string $csv, string $countryCode) { - + private function loadPostalCodeCSV(ObjectManager $manager, string $csv, string $countryCode) + { $lines = str_getcsv($csv, "\n"); $country = $manager->getRepository(Country::class) - ->findOneBy(['countryCode' => $countryCode]); - - foreach($lines as $line) { - $code = str_getcsv($line); + ->findOneBy(['countryCode' => $countryCode]); + + foreach ($lines as $line) { + $code = str_getcsv($line); $c = new PostalCode(); $c->setCountry($country) - ->setCode($code[0]) - ->setName(\ucwords(\strtolower($code[1]))) - ; + ->setCode($code[0]) + ->setName(ucwords(strtolower($code[1]))); - if (NULL != $code[3]){ + if (null !== $code[3]) { $c->setRefPostalCodeId($code[3]); } - if (NULL != $code[4] & NULL != $code[5]){ + if (null !== $code[4] & null !== $code[5]) { $c->setCenter(Point::fromLonLat((float) $code[5], (float) $code[4])); } - if (NULL != $code[6]){ + if (null !== $code[6]) { $c->setPostalCodeSource($code[6]); } $manager->persist($c); - $ref = 'postal_code_'.$code[0]; - - if (! $this->hasReference($ref)) { + $ref = 'postal_code_' . $code[0]; + + if (!$this->hasReference($ref)) { $this->addReference($ref, $c); self::$refs[] = $ref; } } - + $manager->flush(); } - - private static $postalCodeBelgium = << - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DataFixtures\ORM; +use Chill\MainBundle\Entity\RoleScope; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Chill\MainBundle\Entity\RoleScope; -use Chill\MainBundle\DataFixtures\ORM\LoadScopes; -/** - * - * - * @author Julien Fastré - */ class LoadRoleScopes extends AbstractFixture implements OrderedFixtureInterface { + public static $permissions = [ + 'CHILL_FOO_SEE' => [ + 'names' => [ + 'fr' => 'voir foo', + 'en' => 'see foo', + 'nl' => 'zie foo', + ], + ], + 'CHILL_FOO_SEE_DETAILS' => [ + 'names' => [ + 'fr' => 'voir foo avec détails', + 'en' => 'see foo with details', + 'nl' => 'zie foo in details', + ], + ], + 'CHILL_FOO_EDIT' => [ + 'names' => [ + 'fr' => 'modifier foo', + 'en' => 'edit foo', + 'nl' => 'editie foo', + ], + ], + ]; + + public static $references = []; + public function getOrder() { return 300; } - - public static $permissions = array( - 'CHILL_FOO_SEE' => array( - 'names' => array( - 'fr' => 'voir foo', - 'en' => 'see foo', - 'nl' => 'zie foo' - ) - ), - 'CHILL_FOO_SEE_DETAILS' => array( - 'names' => array( - 'fr' => 'voir foo avec détails', - 'en' => 'see foo with details', - 'nl' => 'zie foo in details' - ) - ), - 'CHILL_FOO_EDIT' => array( - 'names' => array( - 'fr' => 'modifier foo', - 'en' => 'edit foo', - 'nl' => 'editie foo' - ) - ) - ); - - public static $references = array(); public function load(ObjectManager $manager) { foreach (static::$permissions as $key => $permission) { - foreach(LoadScopes::$references as $scopeReference) { + foreach (LoadScopes::$references as $scopeReference) { $roleScope = new RoleScope(); $roleScope->setRole($key) - ->setScope($this->getReference($scopeReference)) - ; - $reference = 'role_scope_'.$key.'_'.$this->getReference($scopeReference)->getName()['en']; - echo "Creating $reference \n"; + ->setScope($this->getReference($scopeReference)); + $reference = 'role_scope_' . $key . '_' . $this->getReference($scopeReference)->getName()['en']; + echo "Creating {$reference} \n"; $this->addReference($reference, $roleScope); $manager->persist($roleScope); static::$references[] = $reference; } } - + $manager->flush(); } - } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadScopes.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadScopes.php index 834de5e80..d3a66d887 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadScopes.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadScopes.php @@ -1,23 +1,14 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DataFixtures\ORM; use Doctrine\Common\DataFixtures\AbstractFixture; @@ -25,55 +16,53 @@ use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; /** - * Create scopes - * - * @author Julien Fastré + * Create scopes. */ class LoadScopes extends AbstractFixture implements OrderedFixtureInterface { + public static $references = []; + + public $scopes = [ + [ + 'names' => [ + 'fr' => 'tous', + 'en' => 'all', + 'nl' => 'algemeen', + ], + ], + [ + 'names' => [ + 'fr' => 'social', + 'en' => 'social', + 'nl' => 'sociaal', + ], + ], + [ + 'names' => [ + 'fr' => 'administratif', + 'en' => 'administrative', + 'nl' => 'administratief', + ], + ], + ]; + public function getOrder() { return 200; } - - public $scopes = array( - array( - 'names' => array( - 'fr' => 'tous', - 'en' => 'all', - 'nl' => 'algemeen' - ), - ), - array( - 'names' => array( - 'fr' => 'social', - 'en' => 'social', - 'nl' => 'sociaal' - ) - ), - array( - 'names' => array( - 'fr' => 'administratif', - 'en' => 'administrative', - 'nl' => 'administratief' - ) - ) - ); - - public static $references = array(); public function load(ObjectManager $manager) - { + { foreach ($this->scopes as $new) { $scope = new \Chill\MainBundle\Entity\Scope(); $scope->setName($new['names']); - + $manager->persist($scope); - $reference = 'scope_'.$new['names']['en']; + $reference = 'scope_' . $new['names']['en']; $this->addReference($reference, $scope); static::$references[] = $reference; } - + $manager->flush(); } } diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php index 02f1bfd18..b7d7cb941 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php @@ -1,30 +1,61 @@ */ -class LoadUsers extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface +class LoadUsers extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface { + public static $refs = [ + 'center a_social' => [ + 'groupCenterRefs' => ['centerA_permission_group_social'], + ], + 'center a_administrative' => [ + 'groupCenterRefs' => ['centerA_permission_group_administrative'], + ], + 'center a_direction' => [ + 'groupCenterRefs' => ['centerA_permission_group_direction'], + ], + 'center b_social' => [ + 'groupCenterRefs' => ['centerB_permission_group_social'], + ], + 'center b_administrative' => [ + 'groupCenterRefs' => ['centerB_permission_group_administrative'], + ], + 'center b_direction' => [ + 'groupCenterRefs' => ['centerB_permission_group_direction'], + ], + 'multi_center' => [ + 'groupCenterRefs' => ['centerA_permission_group_social', + 'centerB_permission_group_social', ], + ], + ]; + /** - * * @var ContainerInterface */ private $container; @@ -34,58 +65,31 @@ class LoadUsers extends AbstractFixture implements OrderedFixtureInterface, Cont return 1000; } - public static $refs = array( - 'center a_social' => array( - 'groupCenterRefs' => ['centerA_permission_group_social'] - ), - 'center a_administrative' => array( - 'groupCenterRefs' => ['centerA_permission_group_administrative'] - ), - 'center a_direction' => array( - 'groupCenterRefs' => ['centerA_permission_group_direction'] - ), - 'center b_social' => array( - 'groupCenterRefs' => ['centerB_permission_group_social'] - ), - 'center b_administrative' => array( - 'groupCenterRefs' => ['centerB_permission_group_administrative'] - ), - 'center b_direction' => array( - 'groupCenterRefs' => ['centerB_permission_group_direction'] - ), - 'multi_center' => array( - 'groupCenterRefs' => ['centerA_permission_group_social', - 'centerB_permission_group_social'] - ) - - ); - public function load(ObjectManager $manager) { foreach (self::$refs as $username => $params) { - $user = new User(); $defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000); $encoderFactory = new EncoderFactory([ - User::class => $defaultEncoder + User::class => $defaultEncoder, ]); $user ->setUsername($username) - ->setPassword($encoderFactory - ->getEncoder($user) - ->encodePassword('password', $user->getSalt()) - ) - ->setEmail(sprintf("%s@chill.social", \str_replace(' ', '', $username))) - ; + ->setPassword( + $encoderFactory + ->getEncoder($user) + ->encodePassword('password', $user->getSalt()) + ) + ->setEmail(sprintf('%s@chill.social', str_replace(' ', '', $username))); foreach ($params['groupCenterRefs'] as $groupCenterRef) { $user->addGroupCenter($this->getReference($groupCenterRef)); } - echo 'Creating user ' . $username ."... \n"; + echo 'Creating user ' . $username . "... \n"; $manager->persist($user); $this->addReference($username, $user); } @@ -93,13 +97,12 @@ class LoadUsers extends AbstractFixture implements OrderedFixtureInterface, Cont $manager->flush(); } - public function setContainer(ContainerInterface $container = null) + public function setContainer(?ContainerInterface $container = null) { - if (NULL === $container) { - throw new \LogicException('$container should not be null'); + if (null === $container) { + throw new LogicException('$container should not be null'); } $this->container = $container; } - } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index 972c046d9..012643aad 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -1,81 +1,79 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection; use Chill\MainBundle\Controller\AddressApiController; use Chill\MainBundle\Controller\LocationController; use Chill\MainBundle\Controller\LocationTypeController; use Chill\MainBundle\Controller\UserController; -use Chill\MainBundle\Doctrine\DQL\STContains; -use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS; -use Chill\MainBundle\Entity\User; -use Chill\MainBundle\Entity\UserJob; -use Chill\MainBundle\Form\UserJobType; -use Chill\MainBundle\Form\UserType; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\Config\FileLocator; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\DependencyInjection\Loader; -use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; -use Chill\MainBundle\DependencyInjection\Configuration; use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey; -use Chill\MainBundle\Doctrine\DQL\Unaccent; use Chill\MainBundle\Doctrine\DQL\JsonAggregate; +use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength; use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray; -use Chill\MainBundle\Doctrine\DQL\Similarity; use Chill\MainBundle\Doctrine\DQL\OverlapsI; use Chill\MainBundle\Doctrine\DQL\Replace; +use Chill\MainBundle\Doctrine\DQL\Similarity; +use Chill\MainBundle\Doctrine\DQL\STContains; +use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS; +use Chill\MainBundle\Doctrine\DQL\Unaccent; use Chill\MainBundle\Doctrine\ORM\Hydration\FlatHierarchyEntityHydrator; use Chill\MainBundle\Doctrine\Type\NativeDateIntervalType; use Chill\MainBundle\Doctrine\Type\PointType; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\LocationType; -use Chill\MainBundle\Form\LocationTypeType; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Form\LocationFormType; +use Chill\MainBundle\Form\LocationTypeType; +use Chill\MainBundle\Form\UserJobType; +use Chill\MainBundle\Form\UserType; +use Exception; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use function count; /** * Class ChillMainExtension * This class load config for chillMainExtension. - * - * @package Chill\MainBundle\DependencyInjection */ -class ChillMainExtension extends Extension implements PrependExtensionInterface, +class ChillMainExtension extends Extension implements + PrependExtensionInterface, Widget\HasWidgetFactoriesExtensionInterface { /** - * widget factory + * widget factory. * * @var WidgetFactoryInterface[] */ - protected $widgetFactories = array(); + protected $widgetFactories = []; - /** - * @param WidgetFactoryInterface $factory - */ public function addWidgetFactory(WidgetFactoryInterface $factory) { $this->widgetFactories[] = $factory; } + /** + * @return \Chill\MainBundle\DependencyInjection\Configuration|object|\Symfony\Component\Config\Definition\ConfigurationInterface|null + */ + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new Configuration($this->widgetFactories, $container); + } + /** * @return WidgetFactoryInterface[] */ @@ -85,10 +83,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, } /** - * {@inheritDoc} - * @param array $configs - * @param ContainerBuilder $container - * @throws \Exception + * @throws Exception */ public function load(array $configs, ContainerBuilder $container) { @@ -96,35 +91,59 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - $container->setParameter('chill_main.installation_name', - $config['installation_name']); + // replace all config with a main key: + $container->setParameter('chill_main', $config); - $container->setParameter('chill_main.available_languages', - $config['available_languages']); + // legacy config + $container->setParameter( + 'chill_main.installation_name', + $config['installation_name'] + ); - $container->setParameter('chill_main.routing.resources', - $config['routing']['resources']); + $container->setParameter( + 'chill_main.available_languages', + $config['available_languages'] + ); - $container->setParameter('chill_main.pagination.item_per_page', - $config['pagination']['item_per_page']); + $container->setParameter( + 'chill_main.available_countries', + $config['available_countries'] + ); - $container->setParameter('chill_main.notifications', - $config['notifications']); + $container->setParameter( + 'chill_main.routing.resources', + $config['routing']['resources'] + ); - $container->setParameter('chill_main.redis', - $config['redis']); + $container->setParameter( + 'chill_main.pagination.item_per_page', + $config['pagination']['item_per_page'] + ); - $container->setParameter('chill_main.phone_helper', - $config['phone_helper'] ?? []); + $container->setParameter( + 'chill_main.notifications', + $config['notifications'] + ); + + $container->setParameter( + 'chill_main.redis', + $config['redis'] + ); + + $container->setParameter( + 'chill_main.phone_helper', + $config['phone_helper'] ?? [] + ); // add the key 'widget' without the key 'enable' - $container->setParameter('chill_main.widgets', + $container->setParameter( + 'chill_main.widgets', isset($config['widgets']['homepage']) ? - array('homepage' => $config['widgets']['homepage']): - array() - ); + ['homepage' => $config['widgets']['homepage']] : + [] + ); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); $loader->load('services.yaml'); $loader->load('services/doctrine.yaml'); $loader->load('services/logger.yaml'); @@ -151,33 +170,20 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $this->configureCruds($container, $config['cruds'], $config['apis'], $loader); } - /** - * @param array $config - * @param ContainerBuilder $container - * @return \Chill\MainBundle\DependencyInjection\Configuration|null|object|\Symfony\Component\Config\Definition\ConfigurationInterface - */ - public function getConfiguration(array $config, ContainerBuilder $container) - { - return new Configuration($this->widgetFactories, $container); - } - - /** - * @param ContainerBuilder $container - */ public function prepend(ContainerBuilder $container) { //add installation_name and date_format to globals $chillMainConfig = $container->getExtensionConfig($this->getAlias()); $config = $this->processConfiguration($this - ->getConfiguration($chillMainConfig, $container), $chillMainConfig); - $twigConfig = array( - 'globals' => array( - 'installation' => array( - 'name' => $config['installation_name']), - 'available_languages' => $config['available_languages'] - ), - 'form_themes' => array('@ChillMain/Form/fields.html.twig') - ); + ->getConfiguration($chillMainConfig, $container), $chillMainConfig); + $twigConfig = [ + 'globals' => [ + 'installation' => [ + 'name' => $config['installation_name'], ], + 'available_languages' => $config['available_languages'], + ], + 'form_themes' => ['@ChillMain/Form/fields.html.twig'], + ]; $container->prependExtensionConfig('twig', $twigConfig); //add DQL function to ORM (default entity_manager) @@ -199,6 +205,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, 'OVERLAPSI' => OverlapsI::class, 'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class, 'ST_CONTAINS' => STContains::class, + 'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class, ], ], 'hydrators' => [ @@ -206,7 +213,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, ], ], ], - ); + ); //add dbal types (default entity_manager) $container @@ -222,45 +229,43 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, ], 'types' => [ 'dateinterval' => [ - 'class' => NativeDateIntervalType::class + 'class' => NativeDateIntervalType::class, ], 'point' => [ - 'class' => PointType::class - ] - ] - ] + 'class' => PointType::class, + ], + ], + ], ] - ); + ); //add current route to chill main - $container->prependExtensionConfig('chill_main', array( - 'routing' => array( - 'resources' => array( - '@ChillMainBundle/config/routes.yaml' - ) - - ) - )); + $container->prependExtensionConfig('chill_main', [ + 'routing' => [ + 'resources' => [ + '@ChillMainBundle/config/routes.yaml', + ], + ], + ]); //add a channel to log app events - $container->prependExtensionConfig('monolog', array( - 'channels' => array('chill') - )); + $container->prependExtensionConfig('monolog', [ + 'channels' => ['chill'], + ]); //add crud api $this->prependCruds($container); } /** - * Load parameter for configuration and set parameters for api + * Load parameter for configuration and set parameters for api. */ protected function configureCruds( ContainerBuilder $container, array $crudConfig, array $apiConfig, Loader\YamlFileLoader $loader - ): void - { + ): void { if (count($crudConfig) === 0) { return; } @@ -273,10 +278,6 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, // Note: the controller are loaded inside compiler pass } - - /** - * @param ContainerBuilder $container - */ protected function prependCruds(ContainerBuilder $container) { $container->prependExtensionConfig('chill_main', [ @@ -293,11 +294,11 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, 'template' => '@ChillMain/UserJob/index.html.twig', ], 'new' => [ - 'role' => 'ROLE_ADMIN' + 'role' => 'ROLE_ADMIN', ], 'edit' => [ - 'role' => 'ROLE_ADMIN' - ] + 'role' => 'ROLE_ADMIN', + ], ], ], [ @@ -310,17 +311,17 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, 'actions' => [ 'index' => [ 'role' => 'ROLE_ADMIN', - 'template' => '@ChillMain/User/index.html.twig' + 'template' => '@ChillMain/User/index.html.twig', ], 'new' => [ 'role' => 'ROLE_ADMIN', - 'template' => '@ChillMain/User/new.html.twig' + 'template' => '@ChillMain/User/new.html.twig', ], 'edit' => [ 'role' => 'ROLE_ADMIN', - 'template' => '@ChillMain/User/edit.html.twig' - ] - ] + 'template' => '@ChillMain/User/edit.html.twig', + ], + ], ], [ 'class' => Location::class, @@ -334,15 +335,15 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, 'role' => 'ROLE_ADMIN', 'template' => '@ChillMain/Location/index.html.twig', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillMain/Location/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillMain/Location/edit.html.twig', - ] - ] + ], + ], ], [ 'class' => LocationType::class, @@ -356,15 +357,15 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, 'role' => 'ROLE_ADMIN', 'template' => '@ChillMain/LocationType/index.html.twig', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillMain/LocationType/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillMain/LocationType/edit.html.twig', - ] - ] + ], + ], ], ], 'apis' => [ @@ -378,7 +379,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ @@ -386,10 +387,10 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, Request::METHOD_GET => true, Request::METHOD_POST => true, Request::METHOD_HEAD => true, - Request::METHOD_PATCH => true - ] + Request::METHOD_PATCH => true, + ], ], - ] + ], ], [ 'controller' => \Chill\MainBundle\Controller\AddressReferenceAPIController::class, @@ -401,16 +402,16 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true - ] + Request::METHOD_HEAD => true, + ], ], - ] + ], ], [ 'controller' => \Chill\MainBundle\Controller\PostalCodeAPIController::class, @@ -422,7 +423,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ @@ -430,9 +431,9 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, Request::METHOD_GET => true, Request::METHOD_HEAD => true, Request::METHOD_POST => true, - ] + ], ], - ] + ], ], [ 'class' => \Chill\MainBundle\Entity\Country::class, @@ -443,16 +444,16 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true - ] + Request::METHOD_HEAD => true, + ], ], - ] + ], ], [ 'class' => \Chill\MainBundle\Entity\User::class, @@ -464,16 +465,16 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ 'methods' => [ Request::METHOD_GET => true, Request::METHOD_HEAD => true, - ] + ], ], - ] + ], ], [ 'class' => \Chill\MainBundle\Entity\Scope::class, @@ -484,16 +485,16 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ 'methods' => [ Request::METHOD_GET => true, Request::METHOD_HEAD => true, - ] + ], ], - ] + ], ], [ 'class' => \Chill\MainBundle\Entity\Location::class, @@ -505,7 +506,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ @@ -513,13 +514,13 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, Request::METHOD_GET => true, Request::METHOD_HEAD => true, Request::METHOD_POST => true, - ] + ], ], - - ] + ], ], [ 'class' => \Chill\MainBundle\Entity\LocationType::class, + 'controller' => \Chill\MainBundle\Controller\LocationTypeApiController::class, 'name' => 'location_type', 'base_path' => '/api/1.0/main/location-type', 'base_role' => 'ROLE_USER', @@ -527,19 +528,18 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ 'methods' => [ Request::METHOD_GET => true, Request::METHOD_HEAD => true, - ] + ], ], - - ] - ] - ] + ], + ], + ], ]); } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php index 9197ddb83..b8b3b4d85 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php @@ -1,37 +1,46 @@ + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Chill\MainBundle\Form\PermissionsGroupType; +use LogicException; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + class ACLFlagsCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $permissionGroupType = $container->getDefinition(PermissionsGroupType::class); - - foreach($container->findTaggedServiceIds('chill_main.flags') as $id => $tags) { + + foreach ($container->findTaggedServiceIds('chill_main.flags') as $id => $tags) { $reference = new Reference($id); - + foreach ($tags as $tag) { - switch($tag['scope']) { + switch ($tag['scope']) { case PermissionsGroupType::FLAG_SCOPE: - - $permissionGroupType->addMethodCall('addFlagProvider', [ $reference ]); + $permissionGroupType->addMethodCall('addFlagProvider', [$reference]); + break; + default: - throw new \LogicalException(sprintf( - "This tag 'scope' is not implemented: %s, on service with id %s", $tag['scope'], $id) - ); + throw new LogicException( + sprintf( + "This tag 'scope' is not implemented: %s, on service with id %s", + $tag['scope'], + $id + ) + ); } } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php index 2aa5327b0..64b5704bc 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php @@ -1,51 +1,40 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection\CompilerPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; +use LogicException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; /** - * Compiles the services tagged with : - * + * Compiles the services tagged with :. + * * - chill.export * - chill.export_formatter * - chill.export_aggregator * - chill.export_filter * - chill.export_elements_provider - * - * - * @author Julien Fastré */ class ExportsCompilerPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) { if (!$container->has('Chill\MainBundle\Export\ExportManager')) { - throw new \LogicException('service Chill\MainBundle\Export\ExportManager ' + throw new LogicException('service Chill\MainBundle\Export\ExportManager ' . 'is not defined. It is required by ExportsCompilerPass'); } - + $chillManagerDefinition = $container->findDefinition( 'Chill\MainBundle\Export\ExportManager' ); @@ -56,155 +45,159 @@ class ExportsCompilerPass implements CompilerPassInterface $this->compileFormatters($chillManagerDefinition, $container); $this->compileExportElementsProvider($chillManagerDefinition, $container); } - - private function compileExports(Definition $chillManagerDefinition, - ContainerBuilder $container) - { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export' - ); - - $knownAliases = array(); - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes["alias"])) { - throw new \LogicException("the 'alias' attribute is missing in your ". - "service '$id' definition"); - } - - if (array_search($attributes["alias"], $knownAliases)) { - throw new \LogicException("There is already a chill.export service with alias " - .$attributes["alias"].". Choose another alias."); - } - $knownAliases[] = $attributes["alias"]; - - $chillManagerDefinition->addMethodCall( - 'addExport', - array(new Reference($id), $attributes["alias"]) - ); - } - } - } - - private function compileFilters(Definition $chillManagerDefinition, - ContainerBuilder $container) - { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_filter' - ); - - $knownAliases = array(); - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes["alias"])) { - throw new \LogicException("the 'alias' attribute is missing in your ". - "service '$id' definition"); - } - - if (array_search($attributes["alias"], $knownAliases)) { - throw new \LogicException("There is already a chill.export_filter service with alias " - .$attributes["alias"].". Choose another alias."); - } - $knownAliases[] = $attributes["alias"]; - - $chillManagerDefinition->addMethodCall( - 'addFilter', - array(new Reference($id), $attributes["alias"]) - ); - } - } - } - - private function compileAggregators(Definition $chillManagerDefinition, - ContainerBuilder $container) - { + + private function compileAggregators( + Definition $chillManagerDefinition, + ContainerBuilder $container + ) { $taggedServices = $container->findTaggedServiceIds( 'chill.export_aggregator' ); - - $knownAliases = array(); - + + $knownAliases = []; + foreach ($taggedServices as $id => $tagAttributes) { foreach ($tagAttributes as $attributes) { - if (!isset($attributes["alias"])) { - throw new \LogicException("the 'alias' attribute is missing in your ". - "service '$id' definition"); + if (!isset($attributes['alias'])) { + throw new LogicException("the 'alias' attribute is missing in your " . + "service '{$id}' definition"); } - - if (array_search($attributes["alias"], $knownAliases)) { - throw new \LogicException("There is already a chill.export_aggregator service with alias " - .$attributes["alias"].". Choose another alias."); + + if (array_search($attributes['alias'], $knownAliases, true)) { + throw new LogicException('There is already a chill.export_aggregator service with alias ' + . $attributes['alias'] . '. Choose another alias.'); } - $knownAliases[] = $attributes["alias"]; - + $knownAliases[] = $attributes['alias']; + $chillManagerDefinition->addMethodCall( 'addAggregator', - array(new Reference($id), $attributes["alias"]) - ); - } - } - } - - private function compileFormatters(Definition $chillManagerDefinition, - ContainerBuilder $container) - { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_formatter' - ); - - $knownAliases = array(); - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes["alias"])) { - throw new \LogicException("the 'alias' attribute is missing in your ". - "service '$id' definition"); - } - - if (array_search($attributes["alias"], $knownAliases)) { - throw new \LogicException("There is already a chill.export_formatter service with alias " - .$attributes["alias"].". Choose another alias."); - } - $knownAliases[] = $attributes["alias"]; - - $chillManagerDefinition->addMethodCall( - 'addFormatter', - array(new Reference($id), $attributes["alias"]) - ); - } - } - } - - private function compileExportElementsProvider(Definition $chillManagerDefinition, - ContainerBuilder $container) - { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_elements_provider' - ); - - $knownAliases = array(); - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes["prefix"])) { - throw new \LogicException("the 'prefix' attribute is missing in your ". - "service '$id' definition"); - } - - if (array_search($attributes["prefix"], $knownAliases)) { - throw new \LogicException("There is already a chill.export_elements_provider service with prefix " - .$attributes["prefix"].". Choose another prefix."); - } - $knownAliases[] = $attributes["prefix"]; - - $chillManagerDefinition->addMethodCall( - 'addExportElementsProvider', - array(new Reference($id), $attributes["prefix"]) + [new Reference($id), $attributes['alias']] ); } } } + private function compileExportElementsProvider( + Definition $chillManagerDefinition, + ContainerBuilder $container + ) { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export_elements_provider' + ); + + $knownAliases = []; + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes['prefix'])) { + throw new LogicException("the 'prefix' attribute is missing in your " . + "service '{$id}' definition"); + } + + if (array_search($attributes['prefix'], $knownAliases, true)) { + throw new LogicException('There is already a chill.export_elements_provider service with prefix ' + . $attributes['prefix'] . '. Choose another prefix.'); + } + $knownAliases[] = $attributes['prefix']; + + $chillManagerDefinition->addMethodCall( + 'addExportElementsProvider', + [new Reference($id), $attributes['prefix']] + ); + } + } + } + + private function compileExports( + Definition $chillManagerDefinition, + ContainerBuilder $container + ) { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export' + ); + + $knownAliases = []; + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes['alias'])) { + throw new LogicException("the 'alias' attribute is missing in your " . + "service '{$id}' definition"); + } + + if (array_search($attributes['alias'], $knownAliases, true)) { + throw new LogicException('There is already a chill.export service with alias ' + . $attributes['alias'] . '. Choose another alias.'); + } + $knownAliases[] = $attributes['alias']; + + $chillManagerDefinition->addMethodCall( + 'addExport', + [new Reference($id), $attributes['alias']] + ); + } + } + } + + private function compileFilters( + Definition $chillManagerDefinition, + ContainerBuilder $container + ) { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export_filter' + ); + + $knownAliases = []; + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes['alias'])) { + throw new LogicException("the 'alias' attribute is missing in your " . + "service '{$id}' definition"); + } + + if (array_search($attributes['alias'], $knownAliases, true)) { + throw new LogicException('There is already a chill.export_filter service with alias ' + . $attributes['alias'] . '. Choose another alias.'); + } + $knownAliases[] = $attributes['alias']; + + $chillManagerDefinition->addMethodCall( + 'addFilter', + [new Reference($id), $attributes['alias']] + ); + } + } + } + + private function compileFormatters( + Definition $chillManagerDefinition, + ContainerBuilder $container + ) { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export_formatter' + ); + + $knownAliases = []; + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes['alias'])) { + throw new LogicException("the 'alias' attribute is missing in your " . + "service '{$id}' definition"); + } + + if (array_search($attributes['alias'], $knownAliases, true)) { + throw new LogicException('There is already a chill.export_formatter service with alias ' + . $attributes['alias'] . '. Choose another alias.'); + } + $knownAliases[] = $attributes['alias']; + + $chillManagerDefinition->addMethodCall( + 'addFormatter', + [new Reference($id), $attributes['alias']] + ); + } + } + } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php index 3239e7045..9b5d0903c 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php @@ -1,46 +1,37 @@ - * - * 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\MainBundle\DependencyInjection\CompilerPass; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Reference; -use Chill\MainBundle\Form\Type\Export\PickCenterType; /** - * + * Chill is a software for social workers * - * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use LogicException; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + class GroupingCenterCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (FALSE === $container->hasDefinition('chill.main.form.pick_centers_type')) { - throw new \LogicException("The service chill.main.form.pick_centers_type does " - . "not exists in container"); + if (false === $container->hasDefinition('chill.main.form.pick_centers_type')) { + throw new LogicException('The service chill.main.form.pick_centers_type does ' + . 'not exists in container'); } - + $pickCenterType = $container->getDefinition('chill.main.form.pick_centers_type'); - + foreach ($container->findTaggedServiceIds('chill.grouping_center') as $serviceId => $tagged) { - $pickCenterType->addMethodCall('addGroupingCenter', - [ new Reference($serviceId) ]); + $pickCenterType->addMethodCall( + 'addGroupingCenter', + [new Reference($serviceId)] + ); } } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/MenuCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/MenuCompilerPass.php index a8d87d950..7a63412d5 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/MenuCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/MenuCompilerPass.php @@ -1,44 +1,35 @@ - * - * 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\MainBundle\DependencyInjection\CompilerPass; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Reference; -use Chill\MainBundle\Routing\MenuComposer; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Chill\MainBundle\Routing\MenuComposer; +use LogicException; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + class MenuCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->hasDefinition('chill.main.menu_composer')) { - throw new \LogicException(sprintf("The service %s does not exists in " - . "container.", MenuComposer::class)); + if (!$container->hasDefinition('chill.main.menu_composer')) { + throw new LogicException(sprintf('The service %s does not exists in ' + . 'container.', MenuComposer::class)); } - + $menuComposerDefinition = $container->getDefinition('chill.main.menu_composer'); $services = []; + foreach ($container->findTaggedServiceIds('chill.menu_builder') as $id => $tags) { $services[] = [ 'id' => $id, @@ -46,15 +37,17 @@ class MenuCompilerPass implements CompilerPassInterface ]; } - usort($services, function ($a, $b) { - if ($a['priority'] == $b['priority']) { + usort($services, static function ($a, $b) { + if ($a['priority'] === $b['priority']) { return 0; } + return ($a['priority'] < $b['priority']) ? -1 : 1; }); foreach ($services as $service) { $class = $container->getDefinition($service['id'])->getClass(); + foreach ($class::getMenuIds() as $menuId) { $menuComposerDefinition ->addMethodCall('addLocalMenuBuilder', [new Reference($service['id']), $menuId]); diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php index 941aeeb7a..36435400c 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php @@ -1,43 +1,33 @@ - * - * 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\MainBundle\DependencyInjection\CompilerPass; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Chill\MainBundle\Templating\UI\CountNotificationUser; -use Symfony\Component\DependencyInjection\Reference; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Chill\MainBundle\Templating\UI\CountNotificationUser; +use LogicException; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + class NotificationCounterCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition(CountNotificationUser::class)) { - throw new \LogicException("The service ".CountNotificationUser::class." " - . "should be defined"); + throw new LogicException('The service ' . CountNotificationUser::class . ' ' + . 'should be defined'); } - + $notificationCounterDefinition = $container->getDefinition(CountNotificationUser::class); - + foreach ($container->findTaggedServiceIds('chill.count_notification.user') as $id => $tags) { $notificationCounterDefinition ->addMethodCall('addNotificationCounter', [new Reference($id)]); diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/SearchableServicesCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/SearchableServicesCompilerPass.php index e07849ed2..e5504a532 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/SearchableServicesCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/SearchableServicesCompilerPass.php @@ -1,43 +1,35 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection\CompilerPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; +use LogicException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; class SearchableServicesCompilerPass implements CompilerPassInterface { - - /* - * (non-PHPdoc) + /** + * (non-PHPdoc). + * * @see \Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface::process() */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('chill_main.search_provider')) { - throw new \LogicException('service chill_main.search_provider ' + throw new LogicException('service chill_main.search_provider ' . 'is not defined.'); } - + $definition = $container->getDefinition( 'chill_main.search_provider' ); @@ -45,29 +37,27 @@ class SearchableServicesCompilerPass implements CompilerPassInterface $taggedServices = $container->findTaggedServiceIds( 'chill.search' ); - - $knownAliases = array(); - + + $knownAliases = []; + foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - - if (!isset($attributes["alias"])) { - throw new \LogicException("the 'name' attribute is missing in your ". - "service '$id' definition"); + if (!isset($attributes['alias'])) { + throw new LogicException("the 'name' attribute is missing in your " . + "service '{$id}' definition"); } - - if (array_search($attributes["alias"], $knownAliases)) { - throw new \LogicException("There is already a chill.search service with alias " - .$attributes["alias"].". Choose another alias."); + + if (array_search($attributes['alias'], $knownAliases, true)) { + throw new LogicException('There is already a chill.search service with alias ' + . $attributes['alias'] . '. Choose another alias.'); } - $knownAliases[] = $attributes["alias"]; - + $knownAliases[] = $attributes['alias']; + $definition->addMethodCall( 'addSearchService', - array(new Reference($id), $attributes["alias"]) + [new Reference($id), $attributes['alias']] ); } } } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/TimelineCompilerClass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/TimelineCompilerClass.php index d5b8efbde..8b9b83fb6 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/TimelineCompilerClass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/TimelineCompilerClass.php @@ -1,42 +1,34 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection\CompilerPass; +use LogicException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; /** - * Add services taggued with `name: chill.timeline` to - * timeline_builder service definition - * + * Add services taggued with `name: chill.timeline` to + * timeline_builder service definition. */ class TimelineCompilerClass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('chill_main.timeline_builder')) { - throw new \LogicException('service chill_main.timeline_builder ' + throw new LogicException('service chill_main.timeline_builder ' . 'is not defined.'); } - + $definition = $container->getDefinition( 'chill_main.timeline_builder' ); @@ -44,22 +36,19 @@ class TimelineCompilerClass implements CompilerPassInterface $taggedServices = $container->findTaggedServiceIds( 'chill.timeline' ); - + foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - - if (!isset($attributes["context"])) { - throw new \LogicException("the 'context' attribute is missing in your ". - "service '$id' definition"); + if (!isset($attributes['context'])) { + throw new LogicException("the 'context' attribute is missing in your " . + "service '{$id}' definition"); } $definition->addMethodCall( 'addProvider', - array($attributes["context"], $id, new Reference($id)) + [$attributes['context'], $id, new Reference($id)] ); } } } - } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/WidgetsCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/WidgetsCompilerPass.php index b28b8fdbb..f079571a1 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/WidgetsCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/WidgetsCompilerPass.php @@ -1,34 +1,26 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection\CompilerPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; /** * Compile the service definition to register widgets. - * */ -class WidgetsCompilerPass extends AbstractWidgetsCompilerPass { - - public function process(ContainerBuilder $container) - { - $this->doProcess($container, 'chill_main', 'chill_main.widgets'); - } +class WidgetsCompilerPass extends AbstractWidgetsCompilerPass +{ + public function process(ContainerBuilder $container) + { + $this->doProcess($container, 'chill_main', 'chill_main.widgets'); + } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ConfigConsistencyCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/ConfigConsistencyCompilerPass.php index 3331b29bb..5a803e9de 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ConfigConsistencyCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ConfigConsistencyCompilerPass.php @@ -1,59 +1,55 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection; +use LogicException; +use RuntimeException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use function count; /** - * Description of ConfigConsistencyCompilerPass - * - * @author Julien Fastré + * Description of ConfigConsistencyCompilerPass. */ class ConfigConsistencyCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $availableLanguages = $container - ->getParameter('chill_main.available_languages'); + ->getParameter('chill_main.available_languages'); $methodCallsTranslator = $container - ->findDefinition('translator.default') - ->getMethodCalls(); - - $fallbackLocales = array(); - foreach($methodCallsTranslator as $call) { - if ($call[0] === 'setFallbackLocales') { - $fallbackLocales = array_merge($fallbackLocales, - $call[1][0]); - } + ->findDefinition('translator.default') + ->getMethodCalls(); + + $fallbackLocales = []; + + foreach ($methodCallsTranslator as $call) { + if ('setFallbackLocales' === $call[0]) { + $fallbackLocales = array_merge( + $fallbackLocales, + $call[1][0] + ); + } } - + if (count($fallbackLocales) === 0) { - throw new \LogicException('the fallback locale are not defined. ' + throw new LogicException('the fallback locale are not defined. ' . 'The framework config should not allow this.'); } - + $diff = array_diff($fallbackLocales, $availableLanguages); + if (count($diff) > 0) { - throw new \RuntimeException(sprintf('The chill_main.available_languages' + throw new RuntimeException(sprintf('The chill_main.available_languages' . ' parameter does not contains fallback locales. The languages %s' . ' are missing.', implode(', ', $diff))); } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php index c711f911f..9236c8a50 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php @@ -1,41 +1,39 @@ setWidgetFactories($widgetFactories); $this->containerBuilder = $containerBuilder; } - - /** - * {@inheritDoc} - */ + public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder('chill_main'); @@ -43,211 +41,221 @@ class Configuration implements ConfigurationInterface $rootNode ->children() - ->scalarNode('installation_name') - ->cannotBeEmpty() - ->defaultValue('Chill') - ->end() // end of scalar 'installation_name' - ->arrayNode('available_languages') - ->defaultValue(array('fr')) - ->prototype('scalar')->end() - ->end() // end of array 'available_languages' - ->arrayNode('routing') - ->children() - ->arrayNode('resources') - ->prototype('scalar')->end() - ->end() // end of array 'resources' - ->end() // end of children - ->end() // end of array node 'routing' - ->arrayNode('pagination') - ->canBeDisabled() - ->children() - ->integerNode('item_per_page') - ->info('The number of item to show in the page result, by default') - ->min(1) - ->defaultValue(50) - ->end() // end of integer 'item_per_page' - ->end() // end of children - ->end() // end of pagination - ->arrayNode('notifications') - ->children() - ->scalarNode('from_email') - ->cannotBeEmpty() - ->end() - ->scalarNode('from_name') - ->cannotBeEmpty() - ->end() - ->enumNode('scheme') - ->cannotBeEmpty() - ->values(['http', 'https']) - ->defaultValue('https') - ->end() - ->scalarNode('host') - ->cannotBeEmpty() - ->end() - ->end() - ->end() // end of notifications - ->arrayNode('phone_helper') - ->canBeUnset() - ->children() - ->scalarNode('twilio_sid') - ->defaultNull() - ->end() - ->scalarNode('twilio_secret') - ->defaultNull() - ->end() - ->end() - ->end() - ->arrayNode('redis') - ->children() - ->scalarNode('host') - ->cannotBeEmpty() - ->end() - ->scalarNode('port') - ->defaultValue(6379) - ->end() - ->scalarNode('timeout') - ->defaultValue(1) - ->end() - ->end() - ->end() - ->arrayNode('widgets') - ->canBeEnabled() - ->canBeUnset() - ->children() - ->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder)) - ->end() // end of widgets/children - ->end() // end of widgets - ->arrayNode('cruds') - ->defaultValue([]) - ->arrayPrototype() - ->children() - ->scalarNode('class')->cannotBeEmpty()->isRequired()->end() - ->scalarNode('controller') - ->cannotBeEmpty() - ->defaultValue(\Chill\MainBundle\CRUD\Controller\CRUDController::class) - ->end() - ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() - ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() - ->scalarNode('base_role')->defaultNull()->end() - ->scalarNode('form_class')->defaultNull()->end() - ->arrayNode('actions') - ->defaultValue([ - 'edit' => [], - 'new' => [] - ]) - ->useAttributeAsKey('name') - ->arrayPrototype() - ->children() - ->scalarNode('controller_action') - ->defaultNull() - ->info('the method name to call in the route. Will be set to the action name if left empty.') - ->example("action") - ->end() - ->scalarNode('path') - ->defaultNull() - ->info('the path that will be **appended** after the base path. Do not forget to add ' + ->scalarNode('installation_name') + ->cannotBeEmpty() + ->defaultValue('Chill') + ->end() // end of scalar 'installation_name' + ->arrayNode('available_languages') + ->defaultValue(['fr']) + ->prototype('scalar')->end() + ->end() // end of array 'available_languages' + ->arrayNode('available_countries') + ->defaultValue(['FR']) + ->prototype('scalar')->end() + ->end() // end of array 'available_countries' + ->arrayNode('routing') + ->children() + ->arrayNode('resources') + ->prototype('scalar')->end() + ->end() // end of array 'resources' + ->end() // end of children + ->end() // end of array node 'routing' + ->arrayNode('pagination') + ->canBeDisabled() + ->children() + ->integerNode('item_per_page') + ->info('The number of item to show in the page result, by default') + ->min(1) + ->defaultValue(50) + ->end() // end of integer 'item_per_page' + ->end() // end of children + ->end() // end of pagination + ->arrayNode('notifications') + ->children() + ->scalarNode('from_email') + ->cannotBeEmpty() + ->end() + ->scalarNode('from_name') + ->cannotBeEmpty() + ->end() + ->enumNode('scheme') + ->cannotBeEmpty() + ->values(['http', 'https']) + ->defaultValue('https') + ->end() + ->scalarNode('host') + ->cannotBeEmpty() + ->end() + ->end() + ->end() // end of notifications + ->arrayNode('phone_helper') + ->canBeUnset() + ->children() + ->scalarNode('twilio_sid') + ->defaultNull() + ->end() + ->scalarNode('twilio_secret') + ->defaultNull() + ->end() + ->end() + ->end() + ->arrayNode('acl') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('form_show_scopes') + ->defaultTrue() + ->end() + ->booleanNode('form_show_centers') + ->defaultTrue() + ->end() + ->end() + ->end() + ->arrayNode('redis') + ->children() + ->scalarNode('host') + ->cannotBeEmpty() + ->end() + ->scalarNode('port') + ->defaultValue(6379) + ->end() + ->scalarNode('timeout') + ->defaultValue(1) + ->end() + ->end() + ->end() + ->arrayNode('widgets') + ->canBeEnabled() + ->canBeUnset() + ->children() + ->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder)) + ->end() // end of widgets/children + ->end() // end of widgets + ->arrayNode('cruds') + ->defaultValue([]) + ->arrayPrototype() + ->children() + ->scalarNode('class')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('controller') + ->cannotBeEmpty() + ->defaultValue(\Chill\MainBundle\CRUD\Controller\CRUDController::class) + ->end() + ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_role')->defaultNull()->end() + ->scalarNode('form_class')->defaultNull()->end() + ->arrayNode('actions') + ->defaultValue([ + 'edit' => [], + 'new' => [], + ]) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('controller_action') + ->defaultNull() + ->info('the method name to call in the route. Will be set to the action name if left empty.') + ->example('action') + ->end() + ->scalarNode('path') + ->defaultNull() + ->info('the path that will be **appended** after the base path. Do not forget to add ' . 'arguments for the method. Will be set to the action name, including an `{id}` ' . 'parameter if left empty.') - ->example('/{id}/my-action') - ->end() - ->arrayNode('requirements') - ->ignoreExtraKeys(false) - ->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.') - ->end() - ->scalarNode('role') - ->defaultNull() - ->info('the role that will be required for this action. Override option `base_role`') - ->end() - ->scalarNode('template') - ->defaultNull() - ->info('the template to render the view') - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() - - ->end() - - - ->arrayNode('apis') - ->defaultValue([]) - ->arrayPrototype() - ->children() - ->scalarNode('class')->cannotBeEmpty()->isRequired()->end() - ->scalarNode('controller') - ->cannotBeEmpty() - ->defaultValue(\Chill\MainBundle\CRUD\Controller\ApiController::class) - ->end() - ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() - ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() - ->scalarNode('base_role')->defaultNull()->end() - ->arrayNode('actions') - ->useAttributeAsKey('name') - ->arrayPrototype() - ->children() - ->scalarNode('controller_action') - ->defaultNull() - ->info('the method name to call in the controller. Will be set to the concatenation '. + ->example('/{id}/my-action') + ->end() + ->arrayNode('requirements') + ->ignoreExtraKeys(false) + ->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.') + ->end() + ->scalarNode('role') + ->defaultNull() + ->info('the role that will be required for this action. Override option `base_role`') + ->end() + ->scalarNode('template') + ->defaultNull() + ->info('the template to render the view') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('apis') + ->defaultValue([]) + ->arrayPrototype() + ->children() + ->scalarNode('class')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('controller') + ->cannotBeEmpty() + ->defaultValue(\Chill\MainBundle\CRUD\Controller\ApiController::class) + ->end() + ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_role')->defaultNull()->end() + ->arrayNode('actions') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('controller_action') + ->defaultNull() + ->info('the method name to call in the controller. Will be set to the concatenation ' . 'of action name + \'Api\' if left empty.') - ->example("showApi") - ->end() - ->scalarNode('path') - ->defaultNull() - ->info('the path that will be **appended** after the base path. Do not forget to add ' . - 'arguments for the method. By default, will set to the action name, including an `{id}` '. - 'parameter. A suffix of action name will be appended, except if the action name '. + ->example('showApi') + ->end() + ->scalarNode('path') + ->defaultNull() + ->info('the path that will be **appended** after the base path. Do not forget to add ' . + 'arguments for the method. By default, will set to the action name, including an `{id}` ' . + 'parameter. A suffix of action name will be appended, except if the action name ' . 'is "_entity".') - ->example('/{id}/my-action') - ->end() - ->arrayNode('requirements') - ->ignoreExtraKeys(false) - ->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.') - ->end() - ->enumNode('single_collection') - ->values(['single', 'collection']) - ->defaultValue('single') - ->info('indicates if the returned object is a single element or a collection. '. - 'If the action name is `_index`, this value will always be considered as '. + ->example('/{id}/my-action') + ->end() + ->arrayNode('requirements') + ->ignoreExtraKeys(false) + ->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.') + ->end() + ->enumNode('single_collection') + ->values(['single', 'collection']) + ->defaultValue('single') + ->info('indicates if the returned object is a single element or a collection. ' . + 'If the action name is `_index`, this value will always be considered as ' . '`collection`') - ->end() - ->arrayNode('methods') - ->addDefaultsIfNotSet() - ->info('the allowed methods') - ->children() - ->booleanNode(Request::METHOD_GET)->defaultTrue()->end() - ->booleanNode(Request::METHOD_HEAD)->defaultTrue()->end() - ->booleanNode(Request::METHOD_POST)->defaultFalse()->end() - ->booleanNode(Request::METHOD_DELETE)->defaultFalse()->end() - ->booleanNode(Request::METHOD_PUT)->defaultFalse()->end() - ->booleanNode(Request::METHOD_PATCH)->defaultFalse()->end() - ->end() - ->end() - ->arrayNode('roles') - ->addDefaultsIfNotSet() - ->info("The role require for each http method") - ->children() - ->scalarNode(Request::METHOD_GET)->defaultNull()->end() - ->scalarNode(Request::METHOD_HEAD)->defaultNull()->end() - ->scalarNode(Request::METHOD_POST)->defaultNull()->end() - ->scalarNode(Request::METHOD_DELETE)->defaultNull()->end() - ->scalarNode(Request::METHOD_PUT)->defaultNull()->end() - ->scalarNode(Request::METHOD_PATCH)->defaultNull()->end() - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() - - ->end() - ->end() // end of root/children + ->end() + ->arrayNode('methods') + ->addDefaultsIfNotSet() + ->info('the allowed methods') + ->children() + ->booleanNode(Request::METHOD_GET)->defaultTrue()->end() + ->booleanNode(Request::METHOD_HEAD)->defaultTrue()->end() + ->booleanNode(Request::METHOD_POST)->defaultFalse()->end() + ->booleanNode(Request::METHOD_DELETE)->defaultFalse()->end() + ->booleanNode(Request::METHOD_PUT)->defaultFalse()->end() + ->booleanNode(Request::METHOD_PATCH)->defaultFalse()->end() + ->end() + ->end() + ->arrayNode('roles') + ->addDefaultsIfNotSet() + ->info('The role require for each http method') + ->children() + ->scalarNode(Request::METHOD_GET)->defaultNull()->end() + ->scalarNode(Request::METHOD_HEAD)->defaultNull()->end() + ->scalarNode(Request::METHOD_POST)->defaultNull()->end() + ->scalarNode(Request::METHOD_DELETE)->defaultNull()->end() + ->scalarNode(Request::METHOD_PUT)->defaultNull()->end() + ->scalarNode(Request::METHOD_PATCH)->defaultNull()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() // end of root/children ->end() // end of root - ; +; - return $treeBuilder; } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/MissingBundleException.php b/src/Bundle/ChillMainBundle/DependencyInjection/MissingBundleException.php index 2b4f33b88..54961699a 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/MissingBundleException.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/MissingBundleException.php @@ -1,20 +1,27 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection; -use Symfony\Component\DependencyInjection\ContainerBuilder; +use LogicException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; -/** - * - * - * @author Julien Fastré - */ class RoleProvidersCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('chill.main.role_provider')) { - throw new \LogicException('service chill.main.role_provider ' + throw new LogicException('service chill.main.role_provider ' . 'is not defined. It is required by RoleProviderCompilerPass'); } - + $definition = $container->getDefinition( 'chill.main.role_provider' ); @@ -44,14 +32,12 @@ class RoleProvidersCompilerPass implements CompilerPassInterface $taggedServices = $container->findTaggedServiceIds( 'chill.role' ); - + foreach ($taggedServices as $id => $tagAttributes) { $definition->addMethodCall( 'addProvider', - array(new Reference($id)) + [new Reference($id)] ); } - } - } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php index d26e09293..e352470e7 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php @@ -1,317 +1,239 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection\Widget; +use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; +use Doctrine\Common\Proxy\Exception\InvalidArgumentException; +use LengthException; +use LogicException; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; -use Doctrine\Common\Proxy\Exception\InvalidArgumentException; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; -use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; -use Chill\MainBundle\DependencyInjection\Widget\HasWidgetFactoriesExtensionInterface; +use Symfony\Component\DependencyInjection\Reference; +use UnexpectedValueException; +use function array_key_exists; +use function count; +use function get_class; +use function in_array; +use function is_array; /** * Compile the configurations and inject required service into container. - * + * * The widgets are services tagged with : - * + * * ``` * { name: chill_widget, alias: my_alias, place: my_place } * ``` - * - * Or, if the tag does not exist or if you need to add some config to your + * + * Or, if the tag does not exist or if you need to add some config to your * service depending on the config, you should use a `WidgetFactory` (see * `WidgetFactoryInterface`. - * - * To reuse this compiler pass, simple execute the doProcess metho in your + * + * To reuse this compiler pass, simple execute the doProcess metho in your * compiler. Example : - * + * * ``` * namespace Chill\MainBundle\DependencyInjection\CompilerPass; - * + * * use Symfony\Component\DependencyInjection\ContainerBuilder; * use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass; * class WidgetsCompilerPass extends AbstractWidgetsCompilerPass { - * + * * public function process(ContainerBuilder $container) * { * $this->doProcess($container, 'chill_main', 'chill_main.widgets'); * } * } * ``` - * - * */ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface { - private $widgetServices = array(); - /** - * - * @var WidgetFactoryInterface[] + * the key to use to identify widget for a given place. */ - private $widgetFactories; - + public const WIDGET_CONFIG_ALIAS = 'widget_alias'; + /** - * The service which will manage the widgets - * + * the key to use to order widget for a given place. + */ + public const WIDGET_CONFIG_ORDER = 'order'; + + /** + * The service which will manage the widgets. + * * @var string */ - const WIDGET_MANAGER = 'chill.main.twig.widget'; - + public const WIDGET_MANAGER = 'chill.main.twig.widget'; + /** * the method wich register the widget into give service. */ - const WIDGET_MANAGER_METHOD_REGISTER = 'addWidget'; - + public const WIDGET_MANAGER_METHOD_REGISTER = 'addWidget'; + /** - * the value of the `name` key in service definitions's tag - * - * @var string - */ - const WIDGET_SERVICE_TAG_NAME = 'chill_widget'; - - /** - * the key used to collect the alias in the service definition's tag. - * the alias must be + * the key used to collect the alias in the service definition's tag. + * the alias must be * injected into the configuration under 'alias' key. - * + * * @var string */ - const WIDGET_SERVICE_TAG_ALIAS = 'alias'; - + public const WIDGET_SERVICE_TAG_ALIAS = 'alias'; + /** - * the key used to collect the authorized place in the service definition's tag - * + * the value of the `name` key in service definitions's tag. + * * @var string */ - const WIDGET_SERVICE_TAG_PLACES = 'place'; - - /** - * the key to use to order widget for a given place - */ - const WIDGET_CONFIG_ORDER = 'order'; - - /** - * the key to use to identify widget for a given place - */ - const WIDGET_CONFIG_ALIAS = 'widget_alias'; - + public const WIDGET_SERVICE_TAG_NAME = 'chill_widget'; /** - * process the configuration and the container to add the widget available - * - * @param ContainerBuilder $container + * the key used to collect the authorized place in the service definition's tag. + * + * @var string + */ + public const WIDGET_SERVICE_TAG_PLACES = 'place'; + + /** + * cache of ordering by place. + * + * @internal used by function cacheAndGetOrdering + * + * @var array + */ + private $cacheOrdering = []; + + /** + * @var WidgetFactoryInterface[] + */ + private $widgetFactories; + + private $widgetServices = []; + + /** + * process the configuration and the container to add the widget available. + * * @param string $extension the extension of your bundle * @param string $containerWidgetConfigParameterName the key under which we can use the widget configuration - * @throws \LogicException - * @throws \UnexpectedValueException if the given extension does not implement HasWidgetExtensionInterface - * @throws \InvalidConfigurationException if there are errors in the config + * + * @throws LogicException + * @throws UnexpectedValueException if the given extension does not implement HasWidgetExtensionInterface */ - public function doProcess(ContainerBuilder $container, $extension, - $containerWidgetConfigParameterName) - { + public function doProcess( + ContainerBuilder $container, + $extension, + $containerWidgetConfigParameterName + ) { if (!$container->hasDefinition(self::WIDGET_MANAGER)) { - throw new \LogicException("the service ".self::WIDGET_MANAGER." should". - " be present. It is required by ".self::class); + throw new LogicException('the service ' . self::WIDGET_MANAGER . ' should' . + ' be present. It is required by ' . self::class); } - + $managerDefinition = $container->getDefinition(self::WIDGET_MANAGER); - + // collect the widget factories - /* @var $extensionClass HasWidgetFactoriesExtensionInterface */ + /** @var HasWidgetFactoriesExtensionInterface $extensionClass */ $extensionClass = $container->getExtension($extension); // throw an error if extension does not implement HasWidgetFactoriesExtensionInterface if (!$extensionClass instanceof HasWidgetFactoriesExtensionInterface) { - throw new \UnexpectedValueException(sprintf("The extension for %s " - . "do not implements %s. You should implement %s on %s", - $extension, - HasWidgetFactoriesExtensionInterface::class, - HasWidgetFactoriesExtensionInterface::class, - get_class($extensionClass))); + throw new UnexpectedValueException(sprintf( + 'The extension for %s ' + . 'do not implements %s. You should implement %s on %s', + $extension, + HasWidgetFactoriesExtensionInterface::class, + HasWidgetFactoriesExtensionInterface::class, + get_class($extensionClass) + )); } - - + $this->widgetFactories = $extensionClass->getWidgetFactories(); - + // collect the availabled tagged services $this->collectTaggedServices($container); - + // collect the widgets and their config : $widgetParameters = $container->getParameter($containerWidgetConfigParameterName); - + // and add them to the delegated_block - foreach($widgetParameters as $place => $widgets) { - + foreach ($widgetParameters as $place => $widgets) { foreach ($widgets as $param) { $alias = $param[self::WIDGET_CONFIG_ALIAS]; // check that the service exists if (!array_key_exists($alias, $this->widgetServices)) { - throw new InvalidConfigurationException(sprintf("The alias %s". - " is not defined.", $alias)); + throw new InvalidConfigurationException(sprintf('The alias %s' . + ' is not defined.', $alias)); } - + // check that the widget is allowed at this place if (!$this->isPlaceAllowedForWidget($place, $alias, $container)) { - throw new \InvalidConfigurationException(sprintf( - "The widget with alias %s is not allowed at place %s", - $alias, + throw new InvalidConfigurationException(sprintf( + 'The widget with alias %s is not allowed at place %s', + $alias, $place - )); + )); } - + // get the order, eventually corrected - $order = $this->cacheAndGetOrdering($place, $param[self::WIDGET_CONFIG_ORDER]); - + $order = $this->cacheAndGetOrdering($place, $param[self::WIDGET_CONFIG_ORDER]); + // register the widget with config to the service, using the method // `addWidget` if ($this->widgetServices[$alias] instanceof WidgetFactoryInterface) { - /* @var $factory WidgetFactoryInterface */ + /** @var WidgetFactoryInterface $factory */ $factory = $this->widgetServices[$alias]; // get the config (under the key which equals to widget_alias - $config = isset($param[$factory->getWidgetAlias()]) ? - $param[$factory->getWidgetAlias()] : array(); + $config = isset($param[$factory->getWidgetAlias()]) ? + $param[$factory->getWidgetAlias()] : []; // register the service into the container - $serviceId =$this->registerServiceIntoContainer($container, - $factory, $place, $order, $config); - - $managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER, - array( - $place, - $order, - new Reference($serviceId), - $config - )); + $serviceId = $this->registerServiceIntoContainer( + $container, + $factory, + $place, + $order, + $config + ); + + $managerDefinition->addMethodCall( + self::WIDGET_MANAGER_METHOD_REGISTER, + [ + $place, + $order, + new Reference($serviceId), + $config, + ] + ); } else { - $managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER, - array( - $place, - $order, - new Reference($this->widgetServices[$alias]), - array() // the config is alway an empty array - )); + $managerDefinition->addMethodCall( + self::WIDGET_MANAGER_METHOD_REGISTER, + [ + $place, + $order, + new Reference($this->widgetServices[$alias]), + [], // the config is alway an empty array + ] + ); } } } } - - /** - * register the service into container. - * - * @param ContainerBuilder $container - * @param WidgetFactoryInterface $factory - * @param string $place - * @param float $order - * @param array $config - * @return string the id of the new service - */ - protected function registerServiceIntoContainer( - ContainerBuilder $container, - WidgetFactoryInterface $factory, - $place, - $order, - array $config - ) { - $serviceId = $factory->getServiceId($container, $place, $order, $config); - $definition = $factory->createDefinition($container, $place, - $order, $config); - $container->setDefinition($serviceId, $definition); - - return $serviceId; - } - - /** - * cache of ordering by place. - * - * @internal used by function cacheAndGetOrdering - * @var array - */ - private $cacheOrdering = array(); - - /** - * check if the ordering has already be used for the given $place and, - * if yes, correct the ordering by incrementation of 1 until the ordering - * has not be used. - * - * recursive method. - * - * @param string $place - * @param float $ordering - * @return float - */ - private function cacheAndGetOrdering($place, $ordering) { - // create a key in the cache array if not exists - if (!array_key_exists($place, $this->cacheOrdering)) { - $this->cacheOrdering[$place] = array(); - } - - // check if the order exists - if (array_search($ordering, $this->cacheOrdering[$place])) { - // if the order exists, increment of 1 and try again - return $this->cacheAndGetOrdering($place, $ordering + 1); - } else { - // cache the ordering - $this->cacheOrdering[$place][] = $ordering; - - return $ordering; - } - } - - /** - * get the places where the service is allowed - * - * @param Definition $definition - * @return unknown - */ - private function isPlaceAllowedForWidget($place, $widgetAlias, ContainerBuilder $container) - { - if ($this->widgetServices[$widgetAlias] instanceof WidgetFactoryInterface) { - if (in_array($place, $this->widgetServices[$widgetAlias] - ->getAllowedPlaces())) { - return true; - } - - } else { - $definition = $container->findDefinition($this->widgetServices[$widgetAlias]); - foreach($definition->getTag(self::WIDGET_SERVICE_TAG_NAME) as $attrs) { - $placeValue = $attrs[self::WIDGET_SERVICE_TAG_PLACES]; - - if ($placeValue === $place) { - return true; - } - } - } - - return false; - } - /** * This method collect all service tagged with `self::WIDGET_SERVICE_TAG`, and - * add also the widget defined by factories - * + * add also the widget defined by factories. + * * This method also check that the service is correctly tagged with `alias` and * `places`, or the factory give a correct alias and more than one place. - * - * @param ContainerBuilder $container + * * @throws InvalidConfigurationException * @throws InvalidArgumentException */ @@ -320,69 +242,153 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface // first, check the service tagged in service definition foreach ($container->findTaggedServiceIds(self::WIDGET_SERVICE_TAG_NAME) as $id => $attrs) { foreach ($attrs as $attr) { - // check the alias is set if (!isset($attr[self::WIDGET_SERVICE_TAG_ALIAS])) { - throw new InvalidConfigurationException("you should add an ".self::WIDGET_SERVICE_TAG_ALIAS. - " key on the service ".$id); + throw new InvalidConfigurationException('you should add an ' . self::WIDGET_SERVICE_TAG_ALIAS . + ' key on the service ' . $id); } - + // check the place is set if (!isset($attr[self::WIDGET_SERVICE_TAG_PLACES])) { throw new InvalidConfigurationException(sprintf( - "You should add a %s key on the service %s", + 'You should add a %s key on the service %s', self::WIDGET_SERVICE_TAG_PLACES, $id - )); + )); } - + // check the alias does not exists yet if (array_key_exists($attr[self::WIDGET_SERVICE_TAG_ALIAS], $this->widgetServices)) { - throw new InvalidArgumentException("a service has already be defined with the ". - self::WIDGET_SERVICE_TAG_ALIAS." ".$attr[self::WIDGET_SERVICE_TAG_ALIAS]); + throw new InvalidArgumentException('a service has already be defined with the ' . + self::WIDGET_SERVICE_TAG_ALIAS . ' ' . $attr[self::WIDGET_SERVICE_TAG_ALIAS]); } - + // register the service as available $this->widgetServices[$attr[self::WIDGET_SERVICE_TAG_ALIAS]] = $id; } } - + // add the services defined by factories - foreach($this->widgetFactories as $factory) { - /* @var $factory WidgetFactoryInterface */ + foreach ($this->widgetFactories as $factory) { + /** @var WidgetFactoryInterface $factory */ $alias = $factory->getWidgetAlias(); - + // check the alias is not empty if (empty($alias)) { - throw new \LogicException(sprintf( - "the widget factory %s returns an empty alias", - get_class($factory))); + throw new LogicException(sprintf( + 'the widget factory %s returns an empty alias', + get_class($factory) + )); } - + // check the places are not empty if (!is_array($factory->getAllowedPlaces())) { - throw new \UnexpectedValueException("the method 'getAllowedPlaces' " - . "should return a non-empty array. Unexpected value on ". + throw new UnexpectedValueException("the method 'getAllowedPlaces' " + . 'should return a non-empty array. Unexpected value on ' . get_class($factory)); } - - if (count($factory->getAllowedPlaces()) == 0) { - throw new \LengthException("The method 'getAllowedPlaces' should " - . "return a non-empty array, but returned 0 elements on ". - get_class($factory).'::getAllowedPlaces()'); + + if (count($factory->getAllowedPlaces()) === 0) { + throw new LengthException("The method 'getAllowedPlaces' should " + . 'return a non-empty array, but returned 0 elements on ' . + get_class($factory) . '::getAllowedPlaces()'); } - + // check the alias does not exists yet if (array_key_exists($alias, $this->widgetServices)) { - throw new InvalidArgumentException("a service has already be defined with the ". - self::WIDGET_SERVICE_TAG_ALIAS." ".$alias); + throw new InvalidArgumentException('a service has already be defined with the ' . + self::WIDGET_SERVICE_TAG_ALIAS . ' ' . $alias); } - + // register the factory as available $this->widgetServices[$factory->getWidgetAlias()] = $factory; - } } - - -} \ No newline at end of file + + /** + * register the service into container. + * + * @param string $place + * @param float $order + * + * @return string the id of the new service + */ + protected function registerServiceIntoContainer( + ContainerBuilder $container, + WidgetFactoryInterface $factory, + $place, + $order, + array $config + ) { + $serviceId = $factory->getServiceId($container, $place, $order, $config); + $definition = $factory->createDefinition( + $container, + $place, + $order, + $config + ); + $container->setDefinition($serviceId, $definition); + + return $serviceId; + } + + /** + * check if the ordering has already be used for the given $place and, + * if yes, correct the ordering by incrementation of 1 until the ordering + * has not be used. + * + * recursive method. + * + * @param string $place + * @param float $ordering + * + * @return float + */ + private function cacheAndGetOrdering($place, $ordering) + { + // create a key in the cache array if not exists + if (!array_key_exists($place, $this->cacheOrdering)) { + $this->cacheOrdering[$place] = []; + } + + // check if the order exists + if (array_search($ordering, $this->cacheOrdering[$place], true)) { + // if the order exists, increment of 1 and try again + return $this->cacheAndGetOrdering($place, $ordering + 1); + } + // cache the ordering + $this->cacheOrdering[$place][] = $ordering; + + return $ordering; + } + + /** + * get the places where the service is allowed. + * + * @param mixed $place + * @param mixed $widgetAlias + * + * @return unknown + */ + private function isPlaceAllowedForWidget($place, $widgetAlias, ContainerBuilder $container) + { + if ($this->widgetServices[$widgetAlias] instanceof WidgetFactoryInterface) { + if (in_array($place, $this->widgetServices[$widgetAlias] + ->getAllowedPlaces(), true)) { + return true; + } + } else { + $definition = $container->findDefinition($this->widgetServices[$widgetAlias]); + + foreach ($definition->getTag(self::WIDGET_SERVICE_TAG_NAME) as $attrs) { + $placeValue = $attrs[self::WIDGET_SERVICE_TAG_PLACES]; + + if ($placeValue === $place) { + return true; + } + } + } + + return false; + } +} diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AddWidgetConfigurationTrait.php b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AddWidgetConfigurationTrait.php index 847af0618..2b2d0f001 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AddWidgetConfigurationTrait.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/AddWidgetConfigurationTrait.php @@ -1,90 +1,86 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection\Widget; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass as WidgetsCompilerPass; +use Generator; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; - +use Symfony\Component\DependencyInjection\ContainerBuilder; +use function array_key_exists; +use function count; +use function implode; +use function in_array; /** * This trait allow to add automatic configuration for widget inside your config. - * - * Usage + * + * Usage * ====== - * + * * 1. Register widget factories * ---------------------------- - * + * * Add widget factories, using `setWidgetFactories` - * - * Example : - * + * + * Example : + * * ``` * use Symfony\Component\DependencyInjection\ContainerBuilder; * use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait; - * - * class MyConfig + * + * class MyConfig * { - * + * * use addWidgetConfigurationTrait; - * + * * public function __construct(array $widgetFactories = array(), ContainerBuilder $container) * { * $this->setWidgetFactories($widgetFactories); * // will be used on next step * $this->container = $container; * } - * + * * } * ``` - * - * - * + * + * + * * 2. add widget config to your config * ----------------------------------- - * + * * ``` * use Symfony\Component\DependencyInjection\ContainerBuilder; * use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait; * use Symfony\Component\Config\Definition\Builder\TreeBuilder; - * - * class MyConfig + * + * class MyConfig * { - * + * * use addWidgetConfigurationTrait; - * + * * private $container; - * + * * public function __construct(array $widgetFactories = array(), ContainerBuilder $container) * { * $this->setWidgetFactories($widgetFactories); * $this->container; * } - * + * * public function getConfigTreeBuilder() * { * $treeBuilder = new TreeBuilder(); * $root = $treeBuilder->root('my_root'); - * + * * $root->children() * ->arrayNode('widgets') * ->canBeDisabled() @@ -93,15 +89,15 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; * ->end() * ->end() * ; - * + * * return $treeBuilder; * } - * + * * } * ``` - * - * the above code will add to the config : - * + * + * the above code will add to the config : + * * ``` * widgets: * enabled: true @@ -113,8 +109,6 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; * person_list: * # options for the person_list * ``` - * - * */ trait AddWidgetConfigurationTrait { @@ -122,16 +116,7 @@ trait AddWidgetConfigurationTrait * @param WidgetFactoryInterface[] */ private $widgetFactories; - - /** - * - * @param WidgetFactoryInterface[] $widgetFactories - */ - public function setWidgetFactories(array $widgetFactories) - { - $this->widgetFactories = $widgetFactories; - } - + /** * @return WidgetFactoryInterface[] */ @@ -139,12 +124,20 @@ trait AddWidgetConfigurationTrait { return $this->widgetFactories; } - + + /** + * @param WidgetFactoryInterface[] $widgetFactories + */ + public function setWidgetFactories(array $widgetFactories) + { + $this->widgetFactories = $widgetFactories; + } + /** * add configuration nodes for the widget at the given place. - * + * * @param type $place - * @param ContainerBuilder $containerBuilder + * * @return type */ protected function addWidgetsConfiguration($place, ContainerBuilder $containerBuilder) @@ -152,101 +145,104 @@ trait AddWidgetConfigurationTrait $treeBuilder = new TreeBuilder($place); $root = $treeBuilder->getRootNode($place) ->canBeUnset() - ->info('register widgets on place "'.$place.'"'); - + ->info('register widgets on place "' . $place . '"'); + // if no childen, return the root if (count(iterator_to_array($this->filterWidgetByPlace($place))) === 0) { return $root; } - + $prototypeChildren = $root->prototype('array')->children(); - + $prototypeChildren ->floatNode(WidgetsCompilerPass::WIDGET_CONFIG_ORDER) - ->isRequired() - ->info("the ordering of the widget. May be a number with decimal") - ->example("10.58") - ->end() + ->isRequired() + ->info('the ordering of the widget. May be a number with decimal') + ->example('10.58') + ->end() ->scalarNode(WidgetsCompilerPass::WIDGET_CONFIG_ALIAS) // this node is scalar: when the configuration is build, the // tagged services are not available. But when the config reference // is build, the services are avaialble => we add the possible aliases // in the info. - ->info("the widget alias (see your installed bundles config). " - . "Possible values are (maybe incomplete) : ". - \implode(", ", $this->getWidgetAliasesbyPlace($place, $containerBuilder))) - ->isRequired() - ->end() - ; - + ->info('the widget alias (see your installed bundles config). ' + . 'Possible values are (maybe incomplete) : ' . + implode(', ', $this->getWidgetAliasesbyPlace($place, $containerBuilder))) + ->isRequired() + ->end(); + // adding the possible config on each widget under the widget_alias foreach ($this->filterWidgetByPlace($place) as $factory) { $builder = new TreeBuilder($factory->getWidgetAlias()); $widgetOptionsRoot = $builder->getRootNode($factory->getWidgetAlias()); $widgetOptionsRoot->canBeUnset() - ->info(sprintf('the configuration for the widget "%s" (only required if this widget is set in widget_alias)', - $factory->getWidgetAlias())); + ->info(sprintf( + 'the configuration for the widget "%s" (only required if this widget is set in widget_alias)', + $factory->getWidgetAlias() + )); $factory->configureOptions($place, $widgetOptionsRoot->children()); $prototypeChildren->append($widgetOptionsRoot); } - + return $root; } - + /** * get all widget factories for the given place. * * @param string $place - * @return \Generator a generator containing a widget factory + * + * @return Generator a generator containing a widget factory */ protected function filterWidgetByPlace($place) { - foreach($this->widgetFactories as $factory) { - if (in_array($place, $factory->getAllowedPlaces())) { + foreach ($this->widgetFactories as $factory) { + if (in_array($place, $factory->getAllowedPlaces(), true)) { yield $factory; } } } - + /** * get the all possible aliases for the given place. This method - * search within service tags and widget factories - * - * **Note** that services are not available when the config is build: the whole + * search within service tags and widget factories. + * + * **Note** that services are not available when the config is build: the whole * aliases will be checked in compiler pass, or when the command * `config:dump-reference` is runned. - * + * * @param type $place - * @param ContainerBuilder $containerBuilder - * @return type + * * @throws InvalidConfigurationException if a service's tag does not have the "alias" key + * + * @return type */ protected function getWidgetAliasesbyPlace($place, ContainerBuilder $containerBuilder) { - $result = array(); - + $result = []; + foreach ($this->filterWidgetByPlace($place) as $factory) { $result[] = $factory->getWidgetAlias(); } // append the aliases added without factory foreach ($containerBuilder - ->findTaggedServiceIds(WidgetsCompilerPass::WIDGET_SERVICE_TAG_NAME) + ->findTaggedServiceIds(WidgetsCompilerPass::WIDGET_SERVICE_TAG_NAME) as $serviceId => $tags) { - foreach ($tags as $tag) { - // throw an error if no alias in definition - if (!array_key_exists(WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS, $tag)) { - throw new InvalidConfigurationException(sprintf( - "The service with id %s does not have any %d key", - $serviceId, - WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS - )); - } - // add the key to the possible results - $result[] = $tag[WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS]; + foreach ($tags as $tag) { + // throw an error if no alias in definition + if (!array_key_exists(WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS, $tag)) { + throw new InvalidConfigurationException(sprintf( + 'The service with id %s does not have any %d key', + $serviceId, + WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS + )); } + // add the key to the possible results + $result[] = $tag[WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS]; } - + } + return $result; } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php index 7e5a12871..242b46e9c 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php @@ -1,47 +1,34 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection\Widget\Factory; -use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; /** - * Allow to easily create WidgetFactory. - * + * Allow to easily create WidgetFactory. + * * Extending this factory, the widget will be created using the already defined * service created "as other services" in your configuration (the most frequent * way is using `services.yml` file. - * - * If you need to create different service based upon place, position or + * + * If you need to create different service based upon place, position or * definition, you should implements directly * `Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface` - * - * */ abstract class AbstractWidgetFactory implements WidgetFactoryInterface { - /** - * * {@inheritdoc} - * + * * Will create the definition by returning the definition from the `services.yml` * file (or `services.xml` or `what-you-want.yml`). * @@ -49,9 +36,9 @@ abstract class AbstractWidgetFactory implements WidgetFactoryInterface */ public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config) { - return $containerBuilder->getDefinition($this + return $containerBuilder->getDefinition( + $this ->getServiceId($containerBuilder, $place, $order, $config) - ); + ); } - -} \ No newline at end of file +} diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php index 6e7836b01..8ab19a270 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php @@ -1,52 +1,43 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ -namespace Chill\MainBundle\DependencyInjection\Widget\Factory; +declare(strict_types=1); +namespace Chill\MainBundle\DependencyInjection\Widget\Factory; use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder; /** * Factory for creating configuration of widgets. - * + * * When you need a widget with some configuration, you should implements this - * interface on a factory. The factory will add configuration to the bundle + * interface on a factory. The factory will add configuration to the bundle * giving the places for your widget. - * - * Using this interface, **you do not need** to define the service in your - * container configuration (`services.yml` files). - * + * + * Using this interface, **you do not need** to define the service in your + * container configuration (`services.yml` files). + * * Once the class is created, you should inject the factory inside the container * at compile time, in your `Bundle` class : - * - * + * + * * ``` * namespace Chill\PersonBundle; * * use Symfony\Component\HttpKernel\Bundle\Bundle; * use Symfony\Component\DependencyInjection\ContainerBuilder; * use Chill\PersonBundle\Widget\PersonListWidgetFactory; - * + * * class ChillPersonBundle extends Bundle * { - * public function build(ContainerBuilder $container) + * public function build(ContainerBuilder $container) * { * parent::build($container); * // register a widget factory into chill_main : @@ -55,49 +46,40 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; * } * } * ``` - * - * - * */ interface WidgetFactoryInterface { /** - * configure options for the widget. Those options will be added in + * configure options for the widget. Those options will be added in * configuration in the bundle where the widget will be used. - * + * * @param type $place - * @param NodeBuilder $node */ public function configureOptions($place, NodeBuilder $node); - - /** - * get the widget alias. This alias will be used in configuration (`config.yml`) - */ - public function getWidgetAlias(); - + /** * Create a definition for the service which will render the widget. - * - * (Note: you can define the service by yourself, as other services, + * + * (Note: you can define the service by yourself, as other services, * using the `AbstractWidgetFactory`) - * - * @param ContainerBuilder $containerBuilder + * * @param type $place * @param type $order - * @param array $config */ public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config); - + /** * return the service id to build the widget. - * - * @param ContainerBuilder $containerBuilder + * * @param string $place * @param float $order - * @param array $config - * + * * @return string the service definition - * */ public function getServiceId(ContainerBuilder $containerBuilder, $place, $order, array $config); + + /** + * get the widget alias. This alias will be used in configuration (`config.yml`). + */ + public function getWidgetAlias(); } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php index b25c487b6..92279ea70 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php @@ -1,42 +1,33 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\DependencyInjection\Widget; use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; /** * Register widget factories to an extension. - * */ -interface HasWidgetFactoriesExtensionInterface { - +interface HasWidgetFactoriesExtensionInterface +{ /** - * register a widget factory - * + * register a widget factory. + * * @param \Chill\MainBundle\DependencyInjection\Widget\WidgetFactoryInterface $factory */ public function addWidgetFactory(WidgetFactoryInterface $factory); - + /** - * get all the widget factories registered for this extension - * + * get all the widget factories registered for this extension. + * * @return WidgetFactoryInterface[] */ public function getWidgetFactories(); diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/GetJsonFieldByKey.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/GetJsonFieldByKey.php index addc6b9b7..d57cc866d 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/GetJsonFieldByKey.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/GetJsonFieldByKey.php @@ -1,20 +1,14 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Doctrine\DQL; use Doctrine\ORM\Query\AST\Functions\FunctionNode; @@ -22,16 +16,21 @@ use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; -/** - * - * - * @author Julien Fastré - */ class GetJsonFieldByKey extends FunctionNode { private $expr1; + private $expr2; + public function getSql(SqlWalker $sqlWalker) + { + return sprintf( + '(%s->%s)', + $this->expr1->dispatch($sqlWalker), + $this->expr2->dispatch($sqlWalker) + ); + } + public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); @@ -41,13 +40,4 @@ class GetJsonFieldByKey extends FunctionNode $this->expr2 = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } - - public function getSql(SqlWalker $sqlWalker) - { - return sprintf( - '(%s->%s)', - $this->expr1->dispatch($sqlWalker), - $this->expr2->dispatch($sqlWalker) - ); - } } diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonAggregate.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonAggregate.php index 54ef45493..a678f8c27 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonAggregate.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonAggregate.php @@ -1,20 +1,14 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Doctrine\DQL; use Doctrine\ORM\Query\AST\Functions\FunctionNode; @@ -24,16 +18,14 @@ use Doctrine\ORM\Query\SqlWalker; /** * Return an aggregation of values in a json representation, as a string. - * + * * Internally, this function use the postgresql `jsonb_agg` function. Using * json allow to aggregate data from different types without have to cast them. - * - * @author Julien Fastré */ class JsonAggregate extends FunctionNode { private $expr; - + public function getSql(SqlWalker $sqlWalker) { return sprintf('jsonb_agg(%s)', $this->expr->dispatch($sqlWalker)); diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php new file mode 100644 index 000000000..680d5c6f4 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php @@ -0,0 +1,41 @@ +expr1->dispatch($sqlWalker), + ); + } + + public function parse(Parser $parser): void + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + $this->expr1 = $parser->StringPrimary(); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbExistsInArray.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbExistsInArray.php index 6c4eaa5c3..6295cf366 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbExistsInArray.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbExistsInArray.php @@ -1,8 +1,14 @@ - */ class JsonbExistsInArray extends FunctionNode { private $expr1; + private $expr2; - + public function getSql(SqlWalker $sqlWalker): string { return sprintf( - 'jsonb_exists(%s, %s)', + '%s ?? %s', $this->expr1->dispatch($sqlWalker), $sqlWalker->walkInputParameter($this->expr2) ); diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php index ae9df72f4..52b39e97f 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php @@ -1,110 +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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Doctrine\DQL; use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\Lexer; +use Doctrine\ORM\Query\Parser; +use Exception; /** - * DQL function for OVERLAPS function in postgresql - * - * If a value is null in period start, it will be replaced by -infinity. + * DQL function for OVERLAPS function in postgresql. + * + * If a value is null in period start, it will be replaced by -infinity. * If a value is null in period end, it will be replaced by infinity - * */ class OverlapsI extends FunctionNode { - private $firstPeriodStart; - private $firstPeriodEnd; - - private $secondPeriodStart; - + + private $firstPeriodStart; + private $secondPeriodEnd; - + + private $secondPeriodStart; + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { - return '(' - .$this->makeCase($sqlWalker, $this->firstPeriodStart, 'start').', ' - .$this->makeCase($sqlWalker, $this->firstPeriodEnd, 'end'). - ') OVERLAPS (' - .$this->makeCase($sqlWalker, $this->secondPeriodStart, 'start').', ' - .$this->makeCase($sqlWalker, $this->secondPeriodEnd, 'end').')' - ; - } - - protected function makeCase($sqlWalker, $part, $position) - { - //return $part->dispatch($sqlWalker); - - switch ($position) { - case 'start' : - $p = '-infinity'; - break; - case 'end': - $p = 'infinity'; - break; - } - - if ($part instanceof \Doctrine\ORM\Query\AST\PathExpression) { - return 'CASE WHEN ' - .' '.$part->dispatch($sqlWalker).' IS NOT NULL ' - . 'THEN '. - $part->dispatch($sqlWalker) - . ' ELSE '. - "'".$p."'::date " - . 'END'; - } else { - return 'CASE WHEN ' - .' '.$part->dispatch($sqlWalker).'::date IS NOT NULL ' - . 'THEN '. - $part->dispatch($sqlWalker) - . '::date ELSE '. - "'".$p."'::date " - . 'END'; - } + return sprintf( + '(%s, %s) OVERLAPS (%s, %s)', + $this->makeCase($sqlWalker, $this->firstPeriodStart, 'start'), + $this->makeCase($sqlWalker, $this->firstPeriodEnd, 'end'), + $this->makeCase($sqlWalker, $this->secondPeriodStart, 'start'), + $this->makeCase($sqlWalker, $this->secondPeriodEnd, 'end') + ); } - public function parse(\Doctrine\ORM\Query\Parser $parser) + public function parse(Parser $parser): void { $parser->match(Lexer::T_IDENTIFIER); - + $parser->match(Lexer::T_OPEN_PARENTHESIS); - + $this->firstPeriodStart = $parser->StringPrimary(); - + $parser->match(Lexer::T_COMMA); - + $this->firstPeriodEnd = $parser->StringPrimary(); - + $parser->match(Lexer::T_CLOSE_PARENTHESIS); - + $parser->match(Lexer::T_COMMA); - + $parser->match(Lexer::T_OPEN_PARENTHESIS); - + $this->secondPeriodStart = $parser->StringPrimary(); - + $parser->match(Lexer::T_COMMA); - + $this->secondPeriodEnd = $parser->StringPrimary(); - + $parser->match(Lexer::T_CLOSE_PARENTHESIS); } + + protected function makeCase($sqlWalker, $part, string $position): string + { + switch ($position) { + case 'start': + $p = '-infinity'; + + break; + + case 'end': + $p = 'infinity'; + + break; + + default: + throw new Exception('Unexpected position value.'); + } + + if ($part instanceof PathExpression) { + return sprintf( + "CASE WHEN %s IS NOT NULL THEN %s ELSE '%s'::date END", + $part->dispatch($sqlWalker), + $part->dispatch($sqlWalker), + $p + ); + } + + return sprintf( + "CASE WHEN %s::date IS NOT NULL THEN %s::date ELSE '%s'::date END", + $part->dispatch($sqlWalker), + $part->dispatch($sqlWalker), + $p + ); + } } diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Replace.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Replace.php index 6d30ec7d9..d7f46ab54 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/Replace.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Replace.php @@ -1,46 +1,35 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Doctrine\DQL; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\Lexer; -/** - * - * - */ class Replace extends FunctionNode { - protected $string; - protected $from; - + + protected $string; + protected $to; - + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker): string { - return 'REPLACE('. - $this->string->dispatch($sqlWalker). - ', '. - $this->from->dispatch($sqlWalker). - ', '. - $this->to->dispatch($sqlWalker). + return 'REPLACE(' . + $this->string->dispatch($sqlWalker) . + ', ' . + $this->from->dispatch($sqlWalker) . + ', ' . + $this->to->dispatch($sqlWalker) . ')'; } @@ -48,17 +37,17 @@ class Replace extends FunctionNode { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); - + $this->string = $parser->StringPrimary(); - + $parser->match(Lexer::T_COMMA); - + $this->from = $parser->StringPrimary(); - + $parser->match(Lexer::T_COMMA); - + $this->to = $parser->StringPrimary(); - + $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/STContains.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/STContains.php index 5235d51bb..810084e56 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/STContains.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/STContains.php @@ -1,28 +1,21 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Doctrine\DQL; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\Lexer; /** - * Geometry function 'ST_CONTAINS', added by postgis + * Geometry function 'ST_CONTAINS', added by postgis. */ class STContains extends FunctionNode { @@ -32,8 +25,8 @@ class STContains extends FunctionNode public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { - return 'ST_CONTAINS('.$this->firstPart->dispatch($sqlWalker). - ', ' . $this->secondPart->dispatch($sqlWalker) .")"; + return 'ST_CONTAINS(' . $this->firstPart->dispatch($sqlWalker) . + ', ' . $this->secondPart->dispatch($sqlWalker) . ')'; } public function parse(\Doctrine\ORM\Query\Parser $parser) diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Similarity.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Similarity.php index 1875f978c..d6a3c1238 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/Similarity.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Similarity.php @@ -1,21 +1,14 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Doctrine\DQL; use Doctrine\ORM\Query\AST\Functions\FunctionNode; @@ -29,8 +22,8 @@ class Similarity extends FunctionNode public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { - return 'SIMILARITY('.$this->firstPart->dispatch($sqlWalker). - ', ' . $this->secondPart->dispatch($sqlWalker) .")"; + return 'SIMILARITY(' . $this->firstPart->dispatch($sqlWalker) . + ', ' . $this->secondPart->dispatch($sqlWalker) . ')'; } public function parse(\Doctrine\ORM\Query\Parser $parser) diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/StrictWordSimilarityOPS.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/StrictWordSimilarityOPS.php index 22dcda879..fead5a37d 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/StrictWordSimilarityOPS.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/StrictWordSimilarityOPS.php @@ -1,5 +1,14 @@ firstPart->dispatch($sqlWalker). + return $this->firstPart->dispatch($sqlWalker) . ' <<% ' . $this->secondPart->dispatch($sqlWalker); } - public function parse(\Doctrine\ORM\Query\Parser $parser) + public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php index 2ced15344..799b208bb 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php @@ -1,53 +1,41 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Doctrine\DQL; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\Lexer; /** - * Unaccent string using postgresql extension unaccent : - * http://www.postgresql.org/docs/current/static/unaccent.html - * - * Usage : StringFunction UNACCENT(string) + * Unaccent string using postgresql extension unaccent : + * http://www.postgresql.org/docs/current/static/unaccent.html. * - * @author Julien Fastré + * Usage : StringFunction UNACCENT(string) */ class Unaccent extends FunctionNode { private $string; - + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { - return 'UNACCENT(' . $this->string->dispatch($sqlWalker) .")"; + return 'UNACCENT(' . $this->string->dispatch($sqlWalker) . ')'; } public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); - + $this->string = $parser->StringPrimary(); - + $parser->match(Lexer::T_CLOSE_PARENTHESIS); } - } diff --git a/src/Bundle/ChillMainBundle/Doctrine/Event/TrackCreateUpdateSubscriber.php b/src/Bundle/ChillMainBundle/Doctrine/Event/TrackCreateUpdateSubscriber.php index 3926e2062..3aabe46fc 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Event/TrackCreateUpdateSubscriber.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Event/TrackCreateUpdateSubscriber.php @@ -1,36 +1,39 @@ security = $security; } - - /** - * {@inheritDoc} - */ + public function getSubscribedEvents() { return [ Events::prePersist, - Events::preUpdate + Events::preUpdate, ]; } @@ -41,7 +44,7 @@ class TrackCreateUpdateSubscriber implements EventSubscriber if ($object instanceof TrackCreationInterface && $this->security->getUser() instanceof User) { $object->setCreatedBy($this->security->getUser()); - $object->setCreatedAt(new \DateTimeImmutable('now')); + $object->setCreatedAt(new DateTimeImmutable('now')); } $this->onUpdate($object); @@ -56,10 +59,10 @@ class TrackCreateUpdateSubscriber implements EventSubscriber protected function onUpdate(object $object): void { - if ($object instanceof TrackUpdateInterface + if ($object instanceof TrackUpdateInterface && $this->security->getUser() instanceof User) { $object->setUpdatedBy($this->security->getUser()); - $object->setUpdatedAt(new \DateTimeImmutable('now')); + $object->setUpdatedAt(new DateTimeImmutable('now')); } } } diff --git a/src/Bundle/ChillMainBundle/Doctrine/Migrations/VersionComparator.php b/src/Bundle/ChillMainBundle/Doctrine/Migrations/VersionComparator.php index 120d621a0..aafa852f6 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Migrations/VersionComparator.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Migrations/VersionComparator.php @@ -1,12 +1,21 @@ getNumber($a), $this->getNumber($b)); + } + private function getNumber(Version $version): string { - $names = explode("\\", (string) $version); + $names = explode('\\', (string) $version); return end($names); } - - public function compare(Version $a, Version $b): int - { - $q = strcmp($this->getNumber($a), $this->getNumber($b)); - - return $q; - } } - diff --git a/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php b/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php index 2e6f83a69..f63101b33 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php @@ -1,92 +1,71 @@ lat = $lat; $this->lon = $lon; } - public function toGeoJson(): string + public static function fromArrayGeoJson(array $array): self { - $array = $this->toArrayGeoJson(); - return \json_encode($array); + if ('Point' === $array['type'] && isset($array['coordinates'])) { + return self::fromLonLat($array['coordinates'][0], $array['coordinates'][1]); + } + + throw new Exception('Unable to build a point from input data.'); } - public function jsonSerialize(): array - { - return $this->toArrayGeoJson(); - } - - public function toArrayGeoJson(): array - { - return [ - "type" => "Point", - "coordinates" => [ $this->lon, $this->lat ] - ]; - } - - /** - * - * @return string - */ - public function toWKT(): string - { - return 'SRID='.self::$SRID.';POINT('.$this->lon.' '.$this->lat.')'; - } - - /** - * - * @param type $geojson - * @return Point - */ - public static function fromGeoJson(string $geojson): Point + public static function fromGeoJson(string $geojson): self { $a = json_decode($geojson); - //check if the geojson string is correct - if (NULL === $a or !isset($a->type) or !isset($a->coordinates)){ + + if (null === $a) { throw PointException::badJsonString($geojson); } - if ($a->type != 'Point'){ + if (null === $a->type || null === $a->coordinates) { + throw PointException::badJsonString($geojson); + } + + if ('Point' !== $a->type) { throw PointException::badGeoType(); } - $lat = $a->coordinates[1]; - $lon = $a->coordinates[0]; + [$lon, $lat] = $a->coordinates; return Point::fromLonLat($lon, $lat); } - public static function fromLonLat(float $lon, float $lat): Point + public static function fromLonLat(float $lon, float $lat): self { - if (($lon > -180 && $lon < 180) && ($lat > -90 && $lat < 90)) - { + if ((-180 < $lon && 180 > $lon) && (-90 < $lat && 90 > $lat)) { return new Point($lon, $lat); - } else { - throw PointException::badCoordinates($lon, $lat); } - } - public static function fromArrayGeoJson(array $array): Point - { - if ($array['type'] == 'Point' && - isset($array['coordinates'])) - { - return self::fromLonLat($array['coordinates'][0], $array['coordinates'][1]); - } + throw PointException::badCoordinates($lon, $lat); } public function getLat(): float @@ -98,6 +77,29 @@ class Point implements JsonSerializable { { return $this->lon; } + + public function jsonSerialize(): array + { + return $this->toArrayGeoJson(); + } + + public function toArrayGeoJson(): array + { + return [ + 'type' => 'Point', + 'coordinates' => [$this->lon, $this->lat], + ]; + } + + public function toGeoJson(): string + { + $array = $this->toArrayGeoJson(); + + return json_encode($array); + } + + public function toWKT(): string + { + return sprintf('SRID=%s;POINT(%s %s)', self::$SRID, $this->lon, $this->lat); + } } - - diff --git a/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php b/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php index 4e3101435..5186e8168 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php @@ -1,27 +1,35 @@ flatListGenerator($this->buildChildrenHashmap(parent::hydrateAllData())))); } - private function flatListGenerator(array $hashMap, ?object $parent = null): Generator - { - $parent = null === $parent ? null : spl_object_id($parent); - $hashMap += [$parent => []]; - - foreach ($hashMap[$parent] as $node) { - yield spl_object_id($node) => $node; - yield from $this->flatListGenerator($hashMap, $node); - } - } - private function buildChildrenHashmap(array $nodes): array { return array_reduce( @@ -44,4 +40,15 @@ final class FlatHierarchyEntityHydrator extends ObjectHydrator ); } + private function flatListGenerator(array $hashMap, ?object $parent = null): Generator + { + $parent = null === $parent ? null : spl_object_id($parent); + $hashMap += [$parent => []]; + + foreach ($hashMap[$parent] as $node) { + yield spl_object_id($node) => $node; + + yield from $this->flatListGenerator($hashMap, $node); + } + } } diff --git a/src/Bundle/ChillMainBundle/Doctrine/Type/NativeDateIntervalType.php b/src/Bundle/ChillMainBundle/Doctrine/Type/NativeDateIntervalType.php index 38c704a2d..a1ec0c955 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Type/NativeDateIntervalType.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Type/NativeDateIntervalType.php @@ -1,111 +1,128 @@ + * postgreql. */ class NativeDateIntervalType extends DateIntervalType { - const FORMAT = '%rP%YY%MM%DDT%HH%IM%SS'; - - public function getName(): string - { - return \Doctrine\DBAL\Types\Type::DATEINTERVAL; - } + public const FORMAT = '%rP%YY%MM%DDT%HH%IM%SS'; - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string - { - return 'INTERVAL'; - } - - /** - * {@inheritdoc} - */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if (null === $value) { return null; } - if ($value instanceof \DateInterval) { + if ($value instanceof DateInterval) { return $value->format(self::FORMAT); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']); } - + public function convertToPHPValue($value, AbstractPlatform $platform) { - if ($value === null || $value instanceof \DateInterval) { + if (null === $value || $value instanceof DateInterval) { return $value; } - + try { $strings = explode(' ', $value); - + if (count($strings) === 0) { return null; } $intervalSpec = 'P'; - \reset($strings); - + reset($strings); + do { $intervalSpec .= $this->convertEntry($strings); - } while (next($strings) !== FALSE); - - return new \DateInterval($intervalSpec); - } catch (\Exception $exception) { + } while (next($strings) !== false); + + return new DateInterval($intervalSpec); + } catch (Exception $exception) { throw $this->createConversionException($value, $exception); } } - - private function convertEntry(&$strings) + + public function getName(): string { - $current = \current($strings); - - if (is_numeric($current)) { - $next = \next($strings); - switch($next) { - case 'year': - case 'years': - $unit = 'Y'; - break; - case 'mon': - case 'mons': - $unit = 'M'; - break; - case 'day': - case 'days': - $unit = 'D'; - break; - default: - throw $this->createConversionException(implode('', $strings)); - } - - return $current.$unit; - - } elseif (\preg_match('/([0-9]{2}\:[0-9]{2}:[0-9]{2})/', $current) === 1) { - $tExploded = explode(':', $current); - $intervalSpec = 'T'; - $intervalSpec.= $tExploded[0].'H'; - $intervalSpec.= $tExploded[1].'M'; - $intervalSpec.= $tExploded[2].'S'; - - return $intervalSpec; - } + return \Doctrine\DBAL\Types\Types::DATEINTERVAL; } - + + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + { + return 'INTERVAL'; + } + protected function createConversionException($value, $exception = null) { return ConversionException::conversionFailedFormat($value, $this->getName(), 'xx year xx mons xx days 01:02:03', $exception); } + private function convertEntry(&$strings) + { + $current = current($strings); + + if (is_numeric($current)) { + $next = next($strings); + + switch ($next) { + case 'year': + case 'years': + $unit = 'Y'; + + break; + + case 'mon': + case 'mons': + $unit = 'M'; + + break; + + case 'day': + case 'days': + $unit = 'D'; + + break; + + default: + throw $this->createConversionException(implode('', $strings)); + } + + return $current . $unit; + } + + if (preg_match('/([0-9]{2}\:[0-9]{2}:[0-9]{2})/', $current) === 1) { + $tExploded = explode(':', $current); + $intervalSpec = 'T'; + $intervalSpec .= $tExploded[0] . 'H'; + $intervalSpec .= $tExploded[1] . 'M'; + $intervalSpec .= $tExploded[2] . 'S'; + + return $intervalSpec; + } + } } diff --git a/src/Bundle/ChillMainBundle/Doctrine/Type/PointType.php b/src/Bundle/ChillMainBundle/Doctrine/Type/PointType.php index 0a0c160cd..1d44b2358 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Type/PointType.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Type/PointType.php @@ -1,46 +1,64 @@ toWKT(); + } + + public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) + { + return $sqlExpr; } /** + * @param mixed $value * - * @param type $value - * @param AbstractPlatform $platform * @return ?Point */ public function convertToPHPValue($value, AbstractPlatform $platform) { - if ($value === NULL){ - return NULL; - } else { - return Point::fromGeoJson($value); + if (null === $value) { + return null; } + + return Point::fromGeoJson($value); + } + + public function convertToPHPValueSQL($sqlExpr, $platform) + { + return 'ST_AsGeoJSON(' . $sqlExpr . ') '; } public function getName() @@ -48,28 +66,11 @@ class PointType extends Type { return self::POINT; } - public function convertToDatabaseValue($value, AbstractPlatform $platform) + /** + * @return string + */ + public function getSQLDeclaration(array $column, AbstractPlatform $platform) { - if ($value === NULL){ - return NULL; - } else { - return $value->toWKT(); - } - } - - public function canRequireSQLConversion() - { - return true; - } - - public function convertToPHPValueSQL($sqlExpr, $platform) - { - return 'ST_AsGeoJSON('.$sqlExpr.') '; - } - - public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) - { - return $sqlExpr; + return 'geometry(POINT,' . Point::$SRID . ')'; } } - diff --git a/src/Bundle/ChillMainBundle/Entity/Address.php b/src/Bundle/ChillMainBundle/Entity/Address.php index 06839ec99..dcf0c5a96 100644 --- a/src/Bundle/ChillMainBundle/Entity/Address.php +++ b/src/Bundle/ChillMainBundle/Entity/Address.php @@ -1,79 +1,38 @@ validFrom = new \DateTime(); + $this->validFrom = new DateTime(); } - /** - * Get id - * - * @return integer - */ - public function getId() - { - return $this->id; - } - - /** - * Set streetAddress1 (legacy function) - * - * @param string $streetAddress1 - * - * @return Address - */ - public function setStreetAddress1($streetAddress1) - { - $this->street = $streetAddress1 === NULL ? '' : $streetAddress1; - - return $this; - } - - /** - * Get streetAddress1 (legacy function) - * - * @return string - */ - public function getStreetAddress1() - { - return $this->street; - } - - /** - * Set streetAddress2 (legacy function) - * - * @param string $streetAddress2 - * - * @return Address - */ - public function setStreetAddress2($streetAddress2) - { - $this->streetNumber = $streetAddress2 === NULL ? '' : $streetAddress2; - - return $this; - } - - /** - * Get streetAddress2 (legacy function) - * - * @return string - */ - public function getStreetAddress2() - { - return $this->streetNumber; - } - - /** - * Set postcode - * - * @param PostalCode $postcode - * - * @return Address - */ - public function setPostcode(PostalCode $postcode = null) - { - $this->postcode = $postcode; - - return $this; - } - - /** - * Get postcode - * - * @return PostalCode - */ - public function getPostcode() - { - return $this->postcode; - } - - /** - * @return \DateTime - */ - public function getValidFrom() - { - return $this->validFrom; - } - - /** - * @param \DateTime $validFrom - * @return Address - */ - public function setValidFrom(\DateTime $validFrom) - { - $this->validFrom = $validFrom; - return $this; - } - - /** - * Get IsNoAddress - * - * Indicate true if the address is a fake address (homeless, ...) - * - * @return bool - */ - public function getIsNoAddress(): bool - { - return $this->isNoAddress; - } - - /** - * @return bool - */ - public function isNoAddress(): bool - { - return $this->getIsNoAddress(); - } - - /** - * Set IsNoAddress - * - * Indicate true if the address is a fake address (homeless, ...) - * - * @param bool $isNoAddress - * @return $this - */ - public function setIsNoAddress(bool $isNoAddress): self - { - $this->isNoAddress = $isNoAddress; - return $this; - } - - /** - * Get customs informations in the address - * - * @return array - */ - public function getCustoms(): array - { - return $this->customs; - } - - /** - * Store custom informations in the address - * - * @param array $customs - * @return $this - */ - public function setCustoms(array $customs): self - { - $this->customs = $customs; - - return $this; - } - - /** - * Validate the address. - * - * Check that: - * - * * if the address is not home address: - * * the postal code is present - * * the valid from is not null - * * the address street 1 is greater than 2 - * - * @param ExecutionContextInterface $context - * @param array $payload - */ - public function validate(ExecutionContextInterface $context, $payload) - { - if (!$this->getValidFrom() instanceof \DateTime) { - $context - ->buildViolation("address.date-should-be-set") - ->atPath('validFrom') - ->addViolation(); - } - - if ($this->isNoAddress()) { - return; - } - - if (empty($this->getStreetAddress1())) { - $context - ->buildViolation("address.street1-should-be-set") - ->atPath('streetAddress1') - ->addViolation(); - } - - if (!$this->getPostcode() instanceof PostalCode) { - $context - ->buildViolation("address.postcode-should-be-set") - ->atPath('postCode') - ->addViolation(); - } - } - - /** - * @param Address $original - * @return Address - */ - public static function createFromAddress(Address $original) : Address + public static function createFromAddress(Address $original): Address { return (new Address()) ->setAddressReference($original->getAddressReference()) @@ -398,8 +204,7 @@ class Address ->setStreet($original->getStreet()) ->setStreetNumber($original->getStreetNumber()) ->setValidFrom($original->getValidFrom()) - ->setValidTo($original->getValidTo()) - ; + ->setValidTo($original->getValidTo()); } public static function createFromAddressReference(AddressReference $original): Address @@ -409,44 +214,17 @@ class Address ->setPostcode($original->getPostcode()) ->setStreet($original->getStreet()) ->setStreetNumber($original->getStreetNumber()) - ->setAddressReference($original) - ; + ->setAddressReference($original); } - public function getStreet(): ?string + public function getAddressReference(): ?AddressReference { - return $this->street; + return $this->addressReference; } - public function setStreet(string $street): self + public function getBuildingName(): ?string { - $this->street = $street; - - return $this; - } - - public function getStreetNumber(): ?string - { - return $this->streetNumber; - } - - public function setStreetNumber(string $streetNumber): self - { - $this->streetNumber = $streetNumber; - - return $this; - } - - public function getFloor(): ?string - { - return $this->floor; - } - - public function setFloor(?string $floor): self - { - $this->floor = $floor; - - return $this; + return $this->buildingName; } public function getCorridor(): ?string @@ -454,11 +232,72 @@ class Address return $this->corridor; } - public function setCorridor(?string $corridor): self + /** + * Get customs informations in the address. + */ + public function getCustoms(): array { - $this->corridor = $corridor; + return $this->customs; + } - return $this; + public function getDistribution(): ?string + { + return $this->distribution; + } + + public function getExtra(): ?string + { + return $this->extra; + } + + public function getFlat(): ?string + { + return $this->flat; + } + + public function getFloor(): ?string + { + return $this->floor; + } + + /** + * Get id. + * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * Get IsNoAddress. + * + * Indicate true if the address is a fake address (homeless, ...) + */ + public function getIsNoAddress(): bool + { + return $this->isNoAddress; + } + + public function getLinkedToThirdParty() + { + return $this->linkedToThirdParty; + } + + public function getPoint(): ?Point + { + return $this->point; + } + + /** + * Get postcode. + * + * @return PostalCode + */ + public function getPostcode() + { + return $this->postcode; } public function getSteps(): ?string @@ -466,16 +305,59 @@ class Address return $this->steps; } - public function setSteps(?string $steps): self + public function getStreet(): ?string { - $this->steps = $steps; - - return $this; + return $this->street; } - public function getBuildingName(): ?string + /** + * Get streetAddress1 (legacy function). + * + * @return string + */ + public function getStreetAddress1() { - return $this->buildingName; + return $this->street; + } + + /** + * Get streetAddress2 (legacy function). + * + * @return string + */ + public function getStreetAddress2() + { + return $this->streetNumber; + } + + public function getStreetNumber(): ?string + { + return $this->streetNumber; + } + + /** + * @return DateTime + */ + public function getValidFrom() + { + return $this->validFrom; + } + + public function getValidTo(): ?DateTimeInterface + { + return $this->validTo; + } + + public function isNoAddress(): bool + { + return $this->getIsNoAddress(); + } + + public function setAddressReference(?AddressReference $addressReference = null): Address + { + $this->addressReference = $addressReference; + + return $this; } public function setBuildingName(?string $buildingName): self @@ -485,21 +367,23 @@ class Address return $this; } - public function getFlat(): ?string + public function setCorridor(?string $corridor): self { - return $this->flat; - } - - public function setFlat(?string $flat): self - { - $this->flat = $flat; + $this->corridor = $corridor; return $this; } - public function getDistribution(): ?string + /** + * Store custom informations in the address. + * + * @return $this + */ + public function setCustoms(array $customs): self { - return $this->distribution; + $this->customs = $customs; + + return $this; } public function setDistribution(?string $distribution): self @@ -509,11 +393,6 @@ class Address return $this; } - public function getExtra(): ?string - { - return $this->extra; - } - public function setExtra(?string $extra): self { $this->extra = $extra; @@ -521,33 +400,32 @@ class Address return $this; } - public function getValidTo(): ?\DateTimeInterface + public function setFlat(?string $flat): self { - return $this->validTo; - } - - public function setValidTo(?\DateTimeInterface $validTo = null): self - { - $this->validTo = $validTo; + $this->flat = $flat; return $this; } - public function getPoint(): ?Point + public function setFloor(?string $floor): self { - return $this->point; - } - - public function setPoint(?Point $point): self - { - $this->point = $point; + $this->floor = $floor; return $this; } - public function getLinkedToThirdParty() + /** + * Set IsNoAddress. + * + * Indicate true if the address is a fake address (homeless, ...) + * + * @return $this + */ + public function setIsNoAddress(bool $isNoAddress): self { - return $this->linkedToThirdParty; + $this->isNoAddress = $isNoAddress; + + return $this; } public function setLinkedToThirdParty($linkedToThirdParty): self @@ -557,22 +435,130 @@ class Address return $this; } - /** - * @return AddressReference|null - */ - public function getAddressReference(): ?AddressReference + public function setPoint(?Point $point): self { - return $this->addressReference; - } + $this->point = $point; - /** - * @param AddressReference|null $addressReference - * @return Address - */ - public function setAddressReference(?AddressReference $addressReference = null): Address - { - $this->addressReference = $addressReference; return $this; } -} + /** + * Set postcode. + * + * @param PostalCode $postcode + * + * @return Address + */ + public function setPostcode(?PostalCode $postcode = null) + { + $this->postcode = $postcode; + + return $this; + } + + public function setSteps(?string $steps): self + { + $this->steps = $steps; + + return $this; + } + + public function setStreet(string $street): self + { + $this->street = $street; + + return $this; + } + + /** + * Set streetAddress1 (legacy function). + * + * @param string $streetAddress1 + * + * @return Address + */ + public function setStreetAddress1($streetAddress1) + { + $this->street = null === $streetAddress1 ? '' : $streetAddress1; + + return $this; + } + + /** + * Set streetAddress2 (legacy function). + * + * @param string $streetAddress2 + * + * @return Address + */ + public function setStreetAddress2($streetAddress2) + { + $this->streetNumber = null === $streetAddress2 ? '' : $streetAddress2; + + return $this; + } + + public function setStreetNumber(string $streetNumber): self + { + $this->streetNumber = $streetNumber; + + return $this; + } + + /** + * @return Address + */ + public function setValidFrom(DateTime $validFrom) + { + $this->validFrom = $validFrom; + + return $this; + } + + public function setValidTo(?DateTimeInterface $validTo = null): self + { + $this->validTo = $validTo; + + return $this; + } + + /** + * Validate the address. + * + * Check that: + * + * * if the address is not home address: + * * the postal code is present + * * the valid from is not null + * * the address street 1 is greater than 2 + * + * @param array $payload + */ + public function validate(ExecutionContextInterface $context, $payload) + { + if (!$this->getValidFrom() instanceof DateTime) { + $context + ->buildViolation('address.date-should-be-set') + ->atPath('validFrom') + ->addViolation(); + } + + if ($this->isNoAddress()) { + return; + } + + if (empty($this->getStreetAddress1())) { + $context + ->buildViolation('address.street1-should-be-set') + ->atPath('streetAddress1') + ->addViolation(); + } + + if (!$this->getPostcode() instanceof PostalCode) { + $context + ->buildViolation('address.postcode-should-be-set') + ->atPath('postCode') + ->addViolation(); + } + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/AddressReference.php b/src/Bundle/ChillMainBundle/Entity/AddressReference.php index 2a50932f4..99efd391d 100644 --- a/src/Bundle/ChillMainBundle/Entity/AddressReference.php +++ b/src/Bundle/ChillMainBundle/Entity/AddressReference.php @@ -1,18 +1,36 @@ id; } - public function getRefId(): ?string + public function getMunicipalityCode(): ?string { - return $this->refId; + return $this->municipalityCode; } - public function setRefId(string $refId): self + public function getPoint(): ?Point { - $this->refId = $refId; - - return $this; - } - - public function getStreet(): ?string - { - return $this->street; - } - - public function setStreet(?string $street): self - { - $this->street = $street; - - return $this; - } - - public function getStreetNumber(): ?string - { - return $this->streetNumber; - } - - public function setStreetNumber(?string $streetNumber): self - { - $this->streetNumber = $streetNumber; - - return $this; + return $this->point; } /** - * Set postcode - * - * @param PostalCode $postcode - * - * @return Address - */ - public function setPostcode(PostalCode $postcode = null) - { - $this->postcode = $postcode; - - return $this; - } - - /** - * Get postcode + * Get postcode. * * @return PostalCode */ @@ -135,9 +112,24 @@ class AddressReference return $this->postcode; } - public function getMunicipalityCode(): ?string + public function getRefId(): ?string { - return $this->municipalityCode; + return $this->refId; + } + + public function getSource(): ?string + { + return $this->source; + } + + public function getStreet(): ?string + { + return $this->street; + } + + public function getStreetNumber(): ?string + { + return $this->streetNumber; } public function setMunicipalityCode(?string $municipalityCode): self @@ -147,9 +139,32 @@ class AddressReference return $this; } - public function getSource(): ?string + public function setPoint(?Point $point): self { - return $this->source; + $this->point = $point; + + return $this; + } + + /** + * Set postcode. + * + * @param PostalCode $postcode + * + * @return Address + */ + public function setPostcode(?PostalCode $postcode = null) + { + $this->postcode = $postcode; + + return $this; + } + + public function setRefId(string $refId): self + { + $this->refId = $refId; + + return $this; } public function setSource(?string $source): self @@ -159,14 +174,16 @@ class AddressReference return $this; } - public function getPoint(): ?Point + public function setStreet(?string $street): self { - return $this->point; + $this->street = $street; + + return $this; } - public function setPoint(?Point $point): self + public function setStreetNumber(?string $streetNumber): self { - $this->point = $point; + $this->streetNumber = $streetNumber; return $this; } diff --git a/src/Bundle/ChillMainBundle/Entity/Center.php b/src/Bundle/ChillMainBundle/Entity/Center.php index 2d87a20ab..5b25ee119 100644 --- a/src/Bundle/ChillMainBundle/Entity/Center.php +++ b/src/Bundle/ChillMainBundle/Entity/Center.php @@ -1,54 +1,27 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; -use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation as Serializer; /** * @ORM\Entity * @ORM\Table(name="centers") - * - * @author Julien Fastré */ class Center implements HasCenterInterface { - - /** - * @var integer - * - * @ORM\Id - * @ORM\Column(name="id", type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ - private $id; - - /** - * @var string - * - * @ORM\Column(type="string", length=255) - */ - private $name; - /** * @var Collection * @@ -58,8 +31,23 @@ class Center implements HasCenterInterface * ) */ private $groupCenters; - - + + /** + * @var int + * + * @ORM\Id + * @ORM\Column(name="id", type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + * @Serializer\Groups({"docgen:read"}) + */ + private ?int $id = null; + + /** + * @ORM\Column(type="string", length=255) + * @Serializer\Groups({"docgen:read"}) + */ + private string $name = ''; + /** * Center constructor. */ @@ -67,51 +55,7 @@ class Center implements HasCenterInterface { $this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection(); } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @param $name - * @return $this - */ - public function setName($name) - { - $this->name = $name; - return $this; - } - - /** - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * @return ArrayCollection|Collection - */ - public function getGroupCenters() - { - return $this->groupCenters; - } - - /** - * @param GroupCenter $groupCenter - * @return $this - */ - public function addGroupCenter(GroupCenter $groupCenter) - { - $this->groupCenters->add($groupCenter); - return $this; - } - + /** * @return string */ @@ -119,7 +63,17 @@ class Center implements HasCenterInterface { return $this->getName(); } - + + /** + * @return $this + */ + public function addGroupCenter(GroupCenter $groupCenter) + { + $this->groupCenters->add($groupCenter); + + return $this; + } + /** * @return $this|Center */ @@ -128,4 +82,39 @@ class Center implements HasCenterInterface return $this; } + /** + * @return ArrayCollection|Collection + */ + public function getGroupCenters() + { + return $this->groupCenters; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/Civility.php b/src/Bundle/ChillMainBundle/Entity/Civility.php index 32f5396cd..48b2a7569 100644 --- a/src/Bundle/ChillMainBundle/Entity/Civility.php +++ b/src/Bundle/ChillMainBundle/Entity/Civility.php @@ -1,25 +1,14 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; use Doctrine\ORM\Mapping as ORM; @@ -31,23 +20,10 @@ use Symfony\Component\Serializer\Annotation as Serializer; */ class Civility { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) - */ - private $id; - /** * @ORM\Column(type="json") - * @Serializer\Groups({"read"}) - */ - private array $name = []; - - /** - * @ORM\Column(type="json") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) + * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) */ private array $abbreviation = []; @@ -56,6 +32,31 @@ class Civility */ private bool $active = true; + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + * @Serializer\Groups({"read", "docgen:read"}) + */ + private ?int $id = null; + + /** + * @ORM\Column(type="json") + * @Serializer\Groups({"read", "docgen:read"}) + * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) + */ + private array $name = []; + + public function getAbbreviation(): array + { + return $this->abbreviation; + } + + public function getActive(): ?bool + { + return $this->active; + } + public function getId(): ?int { return $this->id; @@ -66,18 +67,16 @@ class Civility return $this->name; } - public function setName(array $name): self + /** + * @return Civility + */ + public function setAbbreviation(array $abbreviation): self { - $this->name = $name; + $this->abbreviation = $abbreviation; return $this; } - public function getActive(): ?bool - { - return $this->active; - } - public function setActive(bool $active): self { $this->active = $active; @@ -85,21 +84,10 @@ class Civility return $this; } - /** - * @return array - */ - public function getAbbreviation(): array + public function setName(array $name): self { - return $this->abbreviation; - } + $this->name = $name; - /** - * @param array $abbreviation - * @return Civility - */ - public function setAbbreviation(array $abbreviation): self - { - $this->abbreviation = $abbreviation; return $this; } } diff --git a/src/Bundle/ChillMainBundle/Entity/Country.php b/src/Bundle/ChillMainBundle/Entity/Country.php index 043a4e82e..f1fc18557 100644 --- a/src/Bundle/ChillMainBundle/Entity/Country.php +++ b/src/Bundle/ChillMainBundle/Entity/Country.php @@ -1,81 +1,56 @@ id; - } - - /** - * Set name - * - * @param string $name - * @return Country - */ - public function setName($name) - { - $this->name = $name; - - return $this; - } - - /** - * Get name - * - * @return string - */ - public function getName() - { - return $this->name; - } - /** * @return string */ @@ -85,7 +60,6 @@ class Country } /** - * * @return the string */ public function getCountryCode() @@ -94,13 +68,46 @@ class Country } /** + * Get id. * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * Get name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** * @param string $countryCode */ public function setCountryCode($countryCode) { $this->countryCode = $countryCode; + return $this; } + /** + * Set name. + * + * @param string $name + * + * @return Country + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php b/src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php index 9c99c98b4..51b8978a0 100644 --- a/src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php +++ b/src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php @@ -1,12 +1,21 @@ comment; } + /** + * @return DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * @return interger $userId + */ + public function getUserId() + { + return $this->userId; + } + public function isEmpty() { return empty($this->getComment()); @@ -51,34 +77,18 @@ class CommentEmbeddable } /** - * @return interger $userId + * @param DateTime $date */ - public function getUserId() + public function setDate(?DateTime $date) { - return $this->userId; + $this->date = $date; } /** - * @param integer $userId + * @param int $userId */ public function setUserId($userId) { $this->userId = $userId; } - - /** - * @return \DateTime - */ - public function getDate() - { - return $this->date; - } - - /** - * @param \DateTime $date - */ - public function setDate(?\DateTime $date) - { - $this->date = $date; - } } diff --git a/src/Bundle/ChillMainBundle/Entity/GroupCenter.php b/src/Bundle/ChillMainBundle/Entity/GroupCenter.php index a7700001f..ccf000b9d 100644 --- a/src/Bundle/ChillMainBundle/Entity/GroupCenter.php +++ b/src/Bundle/ChillMainBundle/Entity/GroupCenter.php @@ -1,49 +1,27 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; -use Doctrine\ORM\Mapping as ORM; -use Chill\MainBundle\Entity\Center; -use Chill\MainBundle\Entity\PermissionsGroup; -use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="group_centers") * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region") - * - * @author Julien Fastré */ class GroupCenter { - /** - * @var int - * - * @ORM\Id - * @ORM\Column(name="id", type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ - private $id; - /** * @var Center * @@ -54,7 +32,26 @@ class GroupCenter * @ORM\Cache(usage="NONSTRICT_READ_WRITE") */ private $center; - + + /** + * @var int + * + * @ORM\Id + * @ORM\Column(name="id", type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @var PermissionsGroup + * + * @ORM\ManyToOne( + * targetEntity="Chill\MainBundle\Entity\PermissionsGroup", + * inversedBy="groupCenters") + * @ORM\Cache(usage="NONSTRICT_READ_WRITE") + */ + private $permissionsGroup; + /** * @var Collection * @@ -64,18 +61,7 @@ class GroupCenter * ) */ private $users; - - /** - * @var PermissionsGroup - * - * @ORM\ManyToOne( - * targetEntity="Chill\MainBundle\Entity\PermissionsGroup", - * inversedBy="groupCenters") - * @ORM\Cache(usage="NONSTRICT_READ_WRITE") - */ - private $permissionsGroup; - - + /** * GroupCenter constructor. */ @@ -84,14 +70,6 @@ class GroupCenter $this->permissionsGroup = new ArrayCollection(); $this->users = new ArrayCollection(); } - - /** - * @return int - */ - public function getId() - { - return $this->id; - } /** * @return Center @@ -102,23 +80,13 @@ class GroupCenter } /** - * @param Center $center - * @return \Chill\MainBundle\Entity\GroupCenter + * @return int */ - public function setCenter(Center $center) + public function getId() { - $this->center = $center; - return $this; + return $this->id; } - - /** - * @return ArrayCollection|Collection - */ - public function getUsers() - { - return $this->users; - } - + /** * @return PermissionGroup */ @@ -128,17 +96,30 @@ class GroupCenter } /** - * @param \Chill\MainBundle\Entity\PermissionsGroup $permissionGroup + * @return ArrayCollection|Collection + */ + public function getUsers() + { + return $this->users; + } + + /** + * @return \Chill\MainBundle\Entity\GroupCenter + */ + public function setCenter(Center $center) + { + $this->center = $center; + + return $this; + } + + /** * @return \Chill\MainBundle\Entity\GroupCenter */ public function setPermissionsGroup(PermissionsGroup $permissionsGroup) { $this->permissionsGroup = $permissionsGroup; + return $this; } - - - - - } diff --git a/src/Bundle/ChillMainBundle/Entity/HasCenterInterface.php b/src/Bundle/ChillMainBundle/Entity/HasCenterInterface.php index 8d6d541d8..5ffe6dc87 100644 --- a/src/Bundle/ChillMainBundle/Entity/HasCenterInterface.php +++ b/src/Bundle/ChillMainBundle/Entity/HasCenterInterface.php @@ -1,35 +1,24 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; /** - * Interface for entities which may be linked to a center - * - * - * @author Julien Fastré + * Interface for entities which may be linked to a center. */ interface HasCenterInterface { /** - * the linked center - * + * the linked center. + * * @return Center */ public function getCenter(); diff --git a/src/Bundle/ChillMainBundle/Entity/HasCentersInterface.php b/src/Bundle/ChillMainBundle/Entity/HasCentersInterface.php index 921cbba41..ae22cd6c6 100644 --- a/src/Bundle/ChillMainBundle/Entity/HasCentersInterface.php +++ b/src/Bundle/ChillMainBundle/Entity/HasCentersInterface.php @@ -1,5 +1,14 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; /** - * Interface for entities which have a scop - * - * @author Julien Fastré + * Interface for entities which have a scop. */ interface HasScopeInterface { /** - * Return the linked scope - * + * Return the linked scope. + * * @return Scope */ public function getScope(); diff --git a/src/Bundle/ChillMainBundle/Entity/HasScopesInterface.php b/src/Bundle/ChillMainBundle/Entity/HasScopesInterface.php index 911dea563..1beea38f5 100644 --- a/src/Bundle/ChillMainBundle/Entity/HasScopesInterface.php +++ b/src/Bundle/ChillMainBundle/Entity/HasScopesInterface.php @@ -1,5 +1,14 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation as Serializer; /** - * Language + * Language. * * @ORM\Entity * @ORM\Table(name="language") * @ORM\Cache(usage="READ_ONLY", region="language_cache_region") - * @ORM\HasLifecycleCallbacks() + * @ORM\HasLifecycleCallbacks */ class Language { /** * @var string * - * @ORM\Id() + * @ORM\Id * @ORM\Column(type="string") + * @Serializer\Groups({"docgen:read"}) */ - private $id; + private ?string $id = null; /** * @var string array * - * @ORM\Column(type="json_array") + * @ORM\Column(type="json") + * @Serializer\Groups({"docgen:read"}) + * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) */ - private $name; + private array $name = []; /** - * Get id + * Get id. * - * @return string + * @return string */ public function getId() { @@ -58,37 +53,40 @@ class Language } /** - * Set id - * + * Get name. + * + * @return string array + */ + public function getName() + { + return $this->name; + } + + /** + * Set id. + * * @param string $id + * * @return Language */ public function setId($id) { $this->id = $id; + return $this; } /** - * Set name + * Set name. * * @param string array $name + * * @return Language */ public function setName($name) { $this->name = $name; - + return $this; } - - /** - * Get name - * - * @return string array - */ - public function getName() - { - return $this->name; - } } diff --git a/src/Bundle/ChillMainBundle/Entity/Location.php b/src/Bundle/ChillMainBundle/Entity/Location.php index 06e5ccca5..ba1ad8fb0 100644 --- a/src/Bundle/ChillMainBundle/Entity/Location.php +++ b/src/Bundle/ChillMainBundle/Entity/Location.php @@ -1,25 +1,73 @@ active; + } + public function getAddress(): ?Address + { + return $this->address; + } + public function getAvailableForUsers(): ?bool + { + return $this->availableForUsers; + } + public function getCreatedAt(): ?DateTimeImmutable + { + return $this->createdAt; + } + + public function getCreatedBy(): ?User + { + return $this->createdBy; + } + + public function getEmail(): ?string + { + return $this->email; + } public function getId(): ?int { @@ -119,88 +157,29 @@ class Location implements TrackCreationInterface, TrackUpdateInterface return $this->locationType; } - public function setLocationType(?LocationType $locationType): self - { - $this->locationType = $locationType; - - return $this; - } - - public function getAddress(): ?Address - { - return $this->address; - } - - public function setAddress(Address $address): self - { - $this->address = $address; - - return $this; - } - public function getName(): ?string { return $this->name; } - public function setName(?string $name): self - { - $this->name = $name; - - return $this; - } - public function getPhonenumber1(): ?string { return $this->phonenumber1; } - public function setPhonenumber1(?string $phonenumber1): self - { - $this->phonenumber1 = $phonenumber1; - - return $this; - } - public function getPhonenumber2(): ?string { return $this->phonenumber2; } - public function setPhonenumber2(?string $phonenumber2): self + public function getUpdatedAt(): ?DateTimeImmutable { - $this->phonenumber2 = $phonenumber2; - - return $this; + return $this->updatedAt; } - public function getEmail(): ?string + public function getUpdatedBy(): ?User { - return $this->email; - } - - public function setEmail(?string $email): self - { - $this->email = $email; - - return $this; - } - - public function getAvailableForUsers(): ?bool - { - return $this->availableForUsers; - } - - public function setAvailableForUsers(bool $availableForUsers): self - { - $this->availableForUsers = $availableForUsers; - - return $this; - } - - public function getActive(): ?bool - { - return $this->active; + return $this->updatedBy; } public function setActive(bool $active): self @@ -210,9 +189,25 @@ class Location implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function getCreatedBy(): ?User + public function setAddress(Address $address): self { - return $this->createdBy; + $this->address = $address; + + return $this; + } + + public function setAvailableForUsers(bool $availableForUsers): self + { + $this->availableForUsers = $availableForUsers; + + return $this; + } + + public function setCreatedAt(?DateTimeInterface $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; } public function setCreatedBy(?User $createdBy): self @@ -222,21 +217,46 @@ class Location implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function getCreatedAt(): ?\DateTimeImmutable + public function setEmail(?string $email): self { - return $this->createdAt; - } - - public function setCreatedAt(?\DateTimeInterface $createdAt): self - { - $this->createdAt = $createdAt; + $this->email = $email; return $this; } - public function getUpdatedBy(): ?User + public function setLocationType(?LocationType $locationType): self { - return $this->updatedBy; + $this->locationType = $locationType; + + return $this; + } + + public function setName(?string $name): self + { + $this->name = $name; + + return $this; + } + + public function setPhonenumber1(?string $phonenumber1): self + { + $this->phonenumber1 = $phonenumber1; + + return $this; + } + + public function setPhonenumber2(?string $phonenumber2): self + { + $this->phonenumber2 = $phonenumber2; + + return $this; + } + + public function setUpdatedAt(?DateTimeInterface $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; } public function setUpdatedBy(?User $updatedBy): self @@ -245,16 +265,4 @@ class Location implements TrackCreationInterface, TrackUpdateInterface return $this; } - - public function getUpdatedAt(): ?\DateTimeImmutable - { - return $this->updatedAt; - } - - public function setUpdatedAt(?\DateTimeInterface $updatedAt): self - { - $this->updatedAt = $updatedAt; - - return $this; - } } diff --git a/src/Bundle/ChillMainBundle/Entity/LocationType.php b/src/Bundle/ChillMainBundle/Entity/LocationType.php index 026fc727d..cda7e073f 100644 --- a/src/Bundle/ChillMainBundle/Entity/LocationType.php +++ b/src/Bundle/ChillMainBundle/Entity/LocationType.php @@ -1,9 +1,19 @@ active; + } - /** - * @ORM\Column(type="boolean", nullable=true) - * @Serializer\Groups({"read"}) - */ - private bool $active = true; + public function getAddressRequired(): ?string + { + return $this->addressRequired; + } - /** - * @ORM\Column(type="string", length=32, options={"default"="optional"}) - * @Serializer\Groups({"read"}) - */ - private string $addressRequired = self::STATUS_OPTIONAL; + public function getAvailableForUsers(): ?bool + { + return $this->availableForUsers; + } - /** - * @ORM\Column(type="string", length=32, options={"default"="optional"}) - * @Serializer\Groups({"read"}) - */ - private string $contactData = self::STATUS_OPTIONAL; + public function getContactData(): ?string + { + return $this->contactData; + } + public function getDefaultFor(): ?string + { + return $this->defaultFor; + } public function getId(): ?int { @@ -69,18 +116,6 @@ class LocationType return $this->title; } - public function setTitle(array $title): self - { - $this->title = $title; - - return $this; - } - - public function getActive(): ?bool - { - return $this->active; - } - public function setActive(bool $active): self { $this->active = $active; @@ -88,23 +123,6 @@ class LocationType return $this; } - public function getAvailableForUsers(): ?bool - { - return $this->availableForUsers; - } - - public function setAvailableForUsers(bool $availableForUsers): self - { - $this->availableForUsers = $availableForUsers; - - return $this; - } - - public function getAddressRequired(): ?string - { - return $this->addressRequired; - } - public function setAddressRequired(string $addressRequired): self { $this->addressRequired = $addressRequired; @@ -112,9 +130,11 @@ class LocationType return $this; } - public function getContactData(): ?string + public function setAvailableForUsers(bool $availableForUsers): self { - return $this->contactData; + $this->availableForUsers = $availableForUsers; + + return $this; } public function setContactData(string $contactData): self @@ -123,4 +143,18 @@ class LocationType return $this; } + + public function setDefaultFor(?string $defaultFor): self + { + $this->defaultFor = $defaultFor; + + return $this; + } + + public function setTitle(array $title): self + { + $this->title = $title; + + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 092445c9b..cba4f33e5 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -1,25 +1,17 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; -use Chill\MainBundle\Entity\User; +use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -27,14 +19,25 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table( - * name="chill_main_notification", - * uniqueConstraints={ - * @ORM\UniqueConstraint(columns={"relatedEntityClass", "relatedEntityId"}) - * } + * name="chill_main_notification", + * uniqueConstraints={ + * @ORM\UniqueConstraint(columns={"relatedEntityClass", "relatedEntityId"}) + * } * ) */ class Notification { + /** + * @ORM\ManyToMany(targetEntity=User::class) + * @ORM\JoinTable(name="chill_main_notification_addresses_user") + */ + private Collection $addressees; + + /** + * @ORM\Column(type="datetime_immutable") + */ + private DateTimeImmutable $date; + /** * @ORM\Id * @ORM\GeneratedValue @@ -48,21 +51,9 @@ class Notification private string $message; /** - * @ORM\Column(type="datetime_immutable") + * @ORM\Column(type="json") */ - private \DateTimeImmutable $date; - - /** - * @ORM\ManyToOne(targetEntity=User::class) - * @ORM\JoinColumn(nullable=false) - */ - private User $sender; - - /** - * @ORM\ManyToMany(targetEntity=User::class) - * @ORM\JoinTable(name="chill_main_notification_addresses_user") - */ - private Collection $addressees; + private array $read; /** * @ORM\Column(type="string", length=255) @@ -75,52 +66,21 @@ class Notification private int $relatedEntityId; /** - * @ORM\Column(type="json") + * @ORM\ManyToOne(targetEntity=User::class) + * @ORM\JoinColumn(nullable=false) */ - private array $read; + private User $sender; public function __construct() { $this->addressees = new ArrayCollection(); } - public function getId(): ?int + public function addAddressee(User $addressee): self { - return $this->id; - } - - public function getMessage(): ?string - { - return $this->message; - } - - public function setMessage(string $message): self - { - $this->message = $message; - - return $this; - } - - public function getDate(): ?\DateTimeImmutable - { - return $this->date; - } - - public function setDate(\DateTimeImmutable $date): self - { - $this->date = $date; - - return $this; - } - - public function getSender(): ?User - { - return $this->sender; - } - - public function setSender(?User $sender): self - { - $this->sender = $sender; + if (!$this->addressees->contains($addressee)) { + $this->addressees[] = $addressee; + } return $this; } @@ -133,13 +93,39 @@ class Notification return $this->addressees; } - public function addAddressee(User $addressee): self + public function getDate(): ?DateTimeImmutable { - if (!$this->addressees->contains($addressee)) { - $this->addressees[] = $addressee; - } + return $this->date; + } - return $this; + public function getId(): ?int + { + return $this->id; + } + + public function getMessage(): ?string + { + return $this->message; + } + + public function getRead(): array + { + return $this->read; + } + + public function getRelatedEntityClass(): ?string + { + return $this->relatedEntityClass; + } + + public function getRelatedEntityId(): ?int + { + return $this->relatedEntityId; + } + + public function getSender(): ?User + { + return $this->sender; } public function removeAddressee(User $addressee): self @@ -149,9 +135,25 @@ class Notification return $this; } - public function getRelatedEntityClass(): ?string + public function setDate(DateTimeImmutable $date): self { - return $this->relatedEntityClass; + $this->date = $date; + + return $this; + } + + public function setMessage(string $message): self + { + $this->message = $message; + + return $this; + } + + public function setRead(array $read): self + { + $this->read = $read; + + return $this; } public function setRelatedEntityClass(string $relatedEntityClass): self @@ -161,11 +163,6 @@ class Notification return $this; } - public function getRelatedEntityId(): ?int - { - return $this->relatedEntityId; - } - public function setRelatedEntityId(int $relatedEntityId): self { $this->relatedEntityId = $relatedEntityId; @@ -173,14 +170,9 @@ class Notification return $this; } - public function getRead(): array + public function setSender(?User $sender): self { - return $this->read; - } - - public function setRead(array $read): self - { - $this->read = $read; + $this->sender = $sender; return $this; } diff --git a/src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php b/src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php index 456ee29e3..f066178dc 100644 --- a/src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php +++ b/src/Bundle/ChillMainBundle/Entity/PermissionsGroup.php @@ -1,74 +1,36 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; -use Doctrine\ORM\Mapping as ORM; -use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; -use Chill\MainBundle\Entity\RoleScope; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use RuntimeException; use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @ORM\Entity * @ORM\Table(name="permission_groups") * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region") - * - * @author Julien Fastré */ class PermissionsGroup { - /** - * @var integer - * - * @ORM\Id - * @ORM\Column(name="id", type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ - private $id; - - /** - * @var string - * - * @ORM\Column(type="string", length=255) - */ - private $name; - /** * @var string[] * * @ORM\Column(type="json") */ private $flags = []; - - /** - * @var Collection - * - * @ORM\ManyToMany( - * targetEntity="Chill\MainBundle\Entity\RoleScope", - * inversedBy="permissionsGroups", - * cascade={ "persist" }) - * @ORM\Cache(usage="NONSTRICT_READ_WRITE") - */ - private $roleScopes; - + /** * @var Collection * @@ -78,8 +40,34 @@ class PermissionsGroup * ) */ private $groupCenters; - - + + /** + * @var int + * + * @ORM\Id + * @ORM\Column(name="id", type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @var string + * + * @ORM\Column(type="string", length=255) + */ + private $name; + + /** + * @var Collection + * + * @ORM\ManyToMany( + * targetEntity="Chill\MainBundle\Entity\RoleScope", + * inversedBy="permissionsGroups", + * cascade={ "persist" }) + * @ORM\Cache(usage="NONSTRICT_READ_WRITE") + */ + private $roleScopes; + /** * PermissionsGroup constructor. */ @@ -88,7 +76,20 @@ class PermissionsGroup $this->roleScopes = new \Doctrine\Common\Collections\ArrayCollection(); $this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection(); } - + + public function addRoleScope(RoleScope $roleScope) + { + $this->roleScopes->add($roleScope); + } + + /** + * @return string[] + */ + public function getFlags() + { + return $this->flags; + } + /** * @return int */ @@ -104,7 +105,7 @@ class PermissionsGroup { return $this->name; } - + /** * @return ArrayCollection|Collection */ @@ -112,78 +113,62 @@ class PermissionsGroup { return $this->roleScopes; } - + /** - * @param $name - * @return $this + * Test that a role scope is associated only once + * with the permission group. */ - public function setName($name) + public function isRoleScopePresentOnce(ExecutionContextInterface $context) { - $this->name = $name; - return $this; + $roleScopesId = array_map( + static function (RoleScope $roleScope) { + return $roleScope->getId(); + }, + $this->getRoleScopes()->toArray() + ); + $countedIds = array_count_values($roleScopesId); + + foreach ($countedIds as $id => $nb) { + if (1 < $nb) { + $context->buildViolation('A permission is already present ' + . 'for the same role and scope') + ->addViolation(); + } + } } /** - * @param RoleScope $roleScope - */ - public function addRoleScope(RoleScope $roleScope) - { - $this->roleScopes->add($roleScope); - } - - /** - * @param RoleScope $roleScope - * @throws \RuntimeException if the roleScope could not be removed. + * @throws RuntimeException if the roleScope could not be removed. */ public function removeRoleScope(RoleScope $roleScope) { $result = $this->roleScopes->removeElement($roleScope); - if ($result === FALSE) { - throw new \RuntimeException(sprintf("The roleScope '%s' could not be removed, " - . "aborting.", spl_object_hash($roleScope))); + + if (false === $result) { + throw new RuntimeException(sprintf("The roleScope '%s' could not be removed, " + . 'aborting.', spl_object_hash($roleScope))); } } - + /** - * @return string[] - */ - public function getFlags() - { - return $this->flags; - } - - /** - * @param array $flags * @return $this */ public function setFlags(array $flags) { $this->flags = $flags; - + return $this; } - - /** - * Test that a role scope is associated only once - * with the permission group - * - * @param ExecutionContextInterface $context - */ - public function isRoleScopePresentOnce(ExecutionContextInterface $context) - { - $roleScopesId = array_map(function(RoleScope $roleScope) { - return $roleScope->getId(); - }, - $this->getRoleScopes()->toArray()); - $countedIds = array_count_values($roleScopesId); - - foreach ($countedIds as $id => $nb) { - if ($nb > 1) { - $context->buildViolation("A permission is already present " - . "for the same role and scope") - ->addViolation(); - } - } - } + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/PostalCode.php b/src/Bundle/ChillMainBundle/Entity/PostalCode.php index 91ce46e17..484a9e322 100644 --- a/src/Bundle/ChillMainBundle/Entity/PostalCode.php +++ b/src/Bundle/ChillMainBundle/Entity/PostalCode.php @@ -1,5 +1,14 @@ center; + } /** - * @var Point + * Get code. * - * @ORM\Column(type="point", nullable=true) - * @groups({"read"}) + * @return string */ - private $center; + public function getCode() + { + return $this->code; + } /** - * Get id + * Get country. * - * @return integer + * @return Country + */ + public function getCountry() + { + return $this->country; + } + + /** + * Get id. + * + * @return int */ public function getId() { return $this->id; } - /** - * Set origin - * - * @param int $origin - * - * @return PostalCode - */ - public function setOrigin($origin) - { - $this->origin = $origin; - - return $this; - } - - /** - * Get origin - * - * @return int - */ - public function getOrigin() - { - return $this->origin; - } - - - /** - * Set name - * - * @param string $name - * - * @return PostalCode - */ - public function setName($name) - { - $this->name = $name; - - return $this; - } - - /** - * Get name + * Get name. * * @return string */ @@ -148,7 +150,34 @@ class PostalCode } /** - * Set code + * Get origin. + * + * @return int + */ + public function getOrigin() + { + return $this->origin; + } + + public function getPostalCodeSource(): ?string + { + return $this->postalCodeSource; + } + + public function getRefPostalCodeId(): ?string + { + return $this->refPostalCodeId; + } + + public function setCenter(?Point $center): self + { + $this->center = $center; + + return $this; + } + + /** + * Set code. * * @param string $code * @@ -162,23 +191,13 @@ class PostalCode } /** - * Get code - * - * @return string - */ - public function getCode() - { - return $this->code; - } - - /** - * Set country + * Set country. * * @param Country $country * * @return PostalCode */ - public function setCountry(Country $country = null) + public function setCountry(?Country $country = null) { $this->country = $country; @@ -186,30 +205,31 @@ class PostalCode } /** - * Get country + * Set name. * - * @return Country + * @param string $name + * + * @return PostalCode */ - public function getCountry() + public function setName($name) { - return $this->country; - } - - public function getRefPostalCodeId(): ?string - { - return $this->refPostalCodeId; - } - - public function setRefPostalCodeId(?string $refPostalCodeId): self - { - $this->refPostalCodeId = $refPostalCodeId; + $this->name = $name; return $this; } - public function getPostalCodeSource(): ?string + /** + * Set origin. + * + * @param int $origin + * + * @return PostalCode + */ + public function setOrigin($origin) { - return $this->postalCodeSource; + $this->origin = $origin; + + return $this; } public function setPostalCodeSource(?string $postalCodeSource): self @@ -219,16 +239,10 @@ class PostalCode return $this; } - public function getCenter(): ?Point + public function setRefPostalCodeId(?string $refPostalCodeId): self { - return $this->center; - } - - public function setCenter(?Point $center): self - { - $this->center = $center; + $this->refPostalCodeId = $refPostalCodeId; return $this; } } - diff --git a/src/Bundle/ChillMainBundle/Entity/RoleScope.php b/src/Bundle/ChillMainBundle/Entity/RoleScope.php index 7842267a9..a9a65799d 100644 --- a/src/Bundle/ChillMainBundle/Entity/RoleScope.php +++ b/src/Bundle/ChillMainBundle/Entity/RoleScope.php @@ -1,126 +1,88 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; -use Doctrine\ORM\Mapping as ORM; -use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="role_scopes") * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region") - * - * @author Julien Fastré */ class RoleScope { /** - * @var integer - * * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ - private $id; - - /** - * @var string - * - * @ORM\Column(type="string", length=255) - */ - private $role; - - /** - * @var Scope - * - * @ORM\ManyToOne( - * targetEntity="Chill\MainBundle\Entity\Scope", - * inversedBy="roleScopes") - * @ORM\JoinColumn(nullable=true, name="scope_id") - * @ORM\Cache(usage="NONSTRICT_READ_WRITE") - */ - private $scope; - + private ?int $id = null; + /** * @var Collection * * @ORM\ManyToMany( * targetEntity="Chill\MainBundle\Entity\PermissionsGroup", - * mappedBy="roleScopes") + * mappedBy="roleScopes") */ private $permissionsGroups; - - + /** - * RoleScope constructor. + * @ORM\Column(type="string", length=255) */ - public function __construct() { - $this->new = true; + private ?string $role = null; + + /** + * @ORM\ManyToOne( + * targetEntity="Chill\MainBundle\Entity\Scope", + * inversedBy="roleScopes") + * @ORM\JoinColumn(nullable=true, name="scope_id") + * @ORM\Cache(usage="NONSTRICT_READ_WRITE") + */ + private ?Scope $scope = null; + + public function __construct() + { $this->permissionsGroups = new ArrayCollection(); } - - /** - * @return int - */ - public function getId() + + public function getId(): ?int { return $this->id; } - /** - * @return string - */ - public function getRole() + public function getRole(): ?string { return $this->role; } - /** - * @return Scope - */ - public function getScope() + public function getScope(): ?Scope { return $this->scope; } - - /** - * @param type $role - * @return RoleScope - */ - public function setRole($role) + + public function setRole(?string $role = null): self { $this->role = $role; return $this; } - /** - * @param Scope $scope - * @return RoleScope - */ - public function setScope(Scope $scope = null) + public function setScope(?Scope $scope = null): self { $this->scope = $scope; - + return $this; } } diff --git a/src/Bundle/ChillMainBundle/Entity/Scope.php b/src/Bundle/ChillMainBundle/Entity/Scope.php index 2e75c5791..72ac0ad01 100644 --- a/src/Bundle/ChillMainBundle/Entity/Scope.php +++ b/src/Bundle/ChillMainBundle/Entity/Scope.php @@ -1,73 +1,60 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Entity; -use Doctrine\ORM\Mapping as ORM; -use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; -use Chill\MainBundle\Entity\RoleScope; -use Symfony\Component\Serializer\Annotation\Groups; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Annotation\Groups; /** * @ORM\Entity * @ORM\Table(name="scopes") * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region") * @DiscriminatorMap(typeProperty="type", mapping={ - * "scope"=Scope::class + * "scope": Scope::class * }) */ class Scope { /** - * @var integer - * * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $id; + private ?int $id = null; /** - * translatable names + * translatable names. * - * @var array - * - * @ORM\Column(type="json_array") - * @Groups({"read"}) + * @ORM\Column(type="json") + * @Groups({"read", "docgen:read"}) + * @Context({"is-translatable": true}, groups={"docgen:read"}) */ - private $name = []; + private array $name = []; /** * @var Collection * * @ORM\OneToMany( * targetEntity="Chill\MainBundle\Entity\RoleScope", - * mappedBy="scope") + * mappedBy="scope") * @ORM\Cache(usage="NONSTRICT_READ_WRITE") */ private $roleScopes; - /** * Scope constructor. */ @@ -76,45 +63,40 @@ class Scope $this->roleScopes = new ArrayCollection(); } - /** - * @return int - */ - public function getId() + public function addRoleScope(RoleScope $roleScope): self { - return $this->id; - } + $this->roleScopes->add($roleScope); - /** - * @return array - */ - public function getName() - { - return $this->name; - } - - /** - * @param $name - * @return $this - */ - public function setName($name) - { - $this->name = $name; return $this; } /** - * @return Collection + * @return int */ - public function getRoleScopes() + public function getId(): ?int + { + return $this->id; + } + + public function getName(): array + { + return $this->name; + } + + public function getRoleScopes(): Collection { return $this->roleScopes; } /** - * @param RoleScope $roleScope + * @param $name + * + * @return $this */ - public function addRoleScope(RoleScope $roleScope) + public function setName(array $name): self { - $this->roleScopes->add($roleScope); + $this->name = $name; + + return $this; } } diff --git a/src/Bundle/ChillMainBundle/Entity/User.php b/src/Bundle/ChillMainBundle/Entity/User.php index 41d6db40c..de9d1199c 100644 --- a/src/Bundle/ChillMainBundle/Entity/User.php +++ b/src/Bundle/ChillMainBundle/Entity/User.php @@ -1,53 +1,84 @@ id; - } - - /** - * Set username - * - * @param string $name - * @return Agent - */ - public function setUsername($name) - { - $this->username = $name; - - if (empty($this->getLabel())) { - $this->setLabel($name); - } + $this->groupCenters->add($groupCenter); return $this; } - /** - * @return string - */ - public function getUsername() + public function eraseCredentials() { - return $this->username; } /** - */ - public function eraseCredentials() {} - - /** + * Get attributes. + * * @return array */ - public function getRoles() + public function getAttributes() { - return array('ROLE_USER'); + if (null === $this->attributes) { + $this->attributes = []; + } + + return $this->attributes; } - /** - * @return null|string - */ - public function getSalt() + public function getCurrentLocation(): ?Location { - return $this->salt; - } - - /** - * @param $usernameCanonical - * @return $this - */ - public function setUsernameCanonical($usernameCanonical) - { - $this->usernameCanonical = $usernameCanonical; - - return $this; + return $this->currentLocation; } /** * @return string */ - public function getUsernameCanonical() - { - return $this->usernameCanonical; - } - - /** - * @param $email - * @return $this - */ - public function setEmail($email) - { - $this->email = $email; - - return $this; - } - - /** - * @return string - */ - public function getEmail() + public function getEmail(): ?string { return $this->email; } - /** - * @param $emailCanonical - * @return $this - */ - public function setEmailCanonical($emailCanonical) - { - $this->emailCanonical = $emailCanonical; - - return $this; - } - /** * @return string */ @@ -267,14 +200,36 @@ class User implements AdvancedUserInterface { } /** - * @param $password - * @return $this + * @return GroupCenter */ - function setPassword($password) + public function getGroupCenters() { - $this->password = $password; + return $this->groupCenters; + } - return $this; + /** + * Get id. + * + * @return int + */ + public function getId() + { + return $this->id; + } + + public function getLabel(): string + { + return $this->label; + } + + public function getMainCenter(): ?Center + { + return $this->mainCenter; + } + + public function getMainScope(): ?Scope + { + return $this->mainScope; } /** @@ -285,14 +240,38 @@ class User implements AdvancedUserInterface { return $this->password; } - /** - * @param $salt - * @return $this - */ - function setSalt($salt) + public function getRoles(): array { - $this->salt = $salt; - return $this; + return ['ROLE_USER']; + } + + /** + * @return string|null + */ + public function getSalt() + { + return $this->salt; + } + + public function getUserJob(): ?UserJob + { + return $this->userJob; + } + + /** + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * @return string + */ + public function getUsernameCanonical() + { + return $this->usernameCanonical; } /** @@ -327,46 +306,6 @@ class User implements AdvancedUserInterface { return $this->enabled; } - /** - * @param bool $enabled - */ - public function setEnabled($enabled) - { - $this->enabled = $enabled; - - return $this; - } - - /** - * @return GroupCenter - */ - public function getGroupCenters() - { - return $this->groupCenters; - } - - /** - * @param \Chill\MainBundle\Entity\GroupCenter $groupCenter - * @return \Chill\MainBundle\Entity\User - */ - public function addGroupCenter(GroupCenter $groupCenter) - { - $this->groupCenters->add($groupCenter); - return $this; - } - - /** - * @param \Chill\MainBundle\Entity\GroupCenter $groupCenter - * @throws \RuntimeException if the groupCenter is not in the collection - */ - public function removeGroupCenter(GroupCenter $groupCenter) - { - if ($this->groupCenters->removeElement($groupCenter) === FALSE) { - throw new \RuntimeException(sprintf("The groupCenter could not be removed, " - . "it seems not to be associated with the user. Aborting.")); - } - } - /** * This function check that groupCenter are present only once. The validator * use this function to avoid a user to be associated to the same groupCenter @@ -374,12 +313,12 @@ class User implements AdvancedUserInterface { */ public function isGroupCenterPresentOnce(ExecutionContextInterface $context) { - $groupCentersIds = array(); - foreach ($this->getGroupCenters() as $groupCenter) { - if (in_array($groupCenter->getId(), $groupCentersIds)) { - $context->buildViolation("The user has already those permissions") - ->addViolation(); + $groupCentersIds = []; + foreach ($this->getGroupCenters() as $groupCenter) { + if (in_array($groupCenter->getId(), $groupCentersIds, true)) { + $context->buildViolation('The user has already those permissions') + ->addViolation(); } else { $groupCentersIds[] = $groupCenter->getId(); } @@ -387,7 +326,20 @@ class User implements AdvancedUserInterface { } /** - * Set attributes + * @param \Chill\MainBundle\Entity\GroupCenter $groupCenter + * + * @throws RuntimeException if the groupCenter is not in the collection + */ + public function removeGroupCenter(GroupCenter $groupCenter) + { + if ($this->groupCenters->removeElement($groupCenter) === false) { + throw new RuntimeException('The groupCenter could not be removed, ' + . 'it seems not to be associated with the user. Aborting.'); + } + } + + /** + * Set attributes. * * @param array $attributes * @@ -400,89 +352,123 @@ class User implements AdvancedUserInterface { return $this; } + public function setCurrentLocation(?Location $currentLocation): User + { + $this->currentLocation = $currentLocation; + + return $this; + } + /** - * Get attributes + * @param $email * - * @return array + * @return $this */ - public function getAttributes() + public function setEmail($email) { - if ($this->attributes === null) { - $this->attributes = []; - } + $this->email = $email; - return $this->attributes; + return $this; } /** - * @return string + * @param $emailCanonical + * + * @return $this */ - public function getLabel(): string + public function setEmailCanonical($emailCanonical) { - return $this->label; + $this->emailCanonical = $emailCanonical; + + return $this; + } + + public function setEnabled(bool $enabled) + { + $this->enabled = $enabled; + + return $this; } - /** - * @param string $label - * @return User - */ public function setLabel(string $label): User { $this->label = $label; + return $this; } - /** - * @return Center|null - */ - public function getMainCenter(): ?Center - { - return $this->mainCenter; - } - - /** - * @param Center|null $mainCenter - * @return User - */ public function setMainCenter(?Center $mainCenter): User { $this->mainCenter = $mainCenter; + return $this; } - /** - * @return Scope|null - */ - public function getMainScope(): ?Scope - { - return $this->mainScope; - } - - /** - * @param Scope|null $mainScope - * @return User - */ public function setMainScope(?Scope $mainScope): User { $this->mainScope = $mainScope; + return $this; } /** - * @return UserJob|null + * @param $password + * + * @return $this */ - public function getUserJob(): ?UserJob + public function setPassword($password) { - return $this->userJob; + $this->password = $password; + + return $this; } /** - * @param UserJob|null $userJob - * @return User + * @param $salt + * + * @return $this */ + public function setSalt($salt) + { + $this->salt = $salt; + + return $this; + } + public function setUserJob(?UserJob $userJob): User { $this->userJob = $userJob; + + return $this; + } + + /** + * Set username. + * + * @param string $name + * + * @return Agent + */ + public function setUsername($name) + { + $this->username = $name; + + if (empty($this->getLabel())) { + $this->setLabel($name); + } + + return $this; + } + + /** + * @param $usernameCanonical + * + * @return $this + */ + public function setUsernameCanonical($usernameCanonical) + { + $this->usernameCanonical = $usernameCanonical; + return $this; } } diff --git a/src/Bundle/ChillMainBundle/Entity/UserJob.php b/src/Bundle/ChillMainBundle/Entity/UserJob.php index 411d5c40f..5f0bea45d 100644 --- a/src/Bundle/ChillMainBundle/Entity/UserJob.php +++ b/src/Bundle/ChillMainBundle/Entity/UserJob.php @@ -1,5 +1,14 @@ id; @@ -52,31 +56,25 @@ class UserJob return $this->label; } - /** - * @param array|string[] $label - * @return UserJob - */ - public function setLabel(array $label): UserJob - { - $this->label = $label; - return $this; - } - - /** - * @return bool - */ public function isActive(): bool { return $this->active; } - /** - * @param bool $active - * @return UserJob - */ public function setActive(bool $active): UserJob { $this->active = $active; + + return $this; + } + + /** + * @param array|string[] $label + */ + public function setLabel(array $label): UserJob + { + $this->label = $label; + return $this; } } diff --git a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php index db65600bd..244d3ba14 100644 --- a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php +++ b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php @@ -1,69 +1,50 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export; +use Closure; + /** - * Interface for Aggregators. - * + * Interface for Aggregators. + * * Aggregators gather result of a query. Most of the time, it will add * a GROUP BY clause. - * - * @author Julien Fastré */ interface AggregatorInterface extends ModifierInterface -{ - /** - * give the list of keys the current export added to the queryBuilder in - * self::initiateQuery - * - * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, - * this function will return `array('count_id')`. - * - * @param mixed[] $data the data from the export's form (added by self::buildForm) - */ - public function getQueryKeys($data); - +{ /** * get a callable which will be able to transform the results into * viewable and understable string. - * - * The callable will have only one argument: the `value` to translate. - * + * + * The callable will have only one argument: the `value` to translate. + * * The callable should also be able to return a key `_header`, which - * will contains the header of the column. - * - * The string returned **must** be already translated if necessary, + * will contains the header of the column. + * + * The string returned **must** be already translated if necessary, * **with an exception** for the string returned for `_header`. - * - * Example : - * + * + * Example : + * * ``` * protected $translator; - * + * * public function getLabels($key, array $values, $data) * { * return function($value) { * case $value * { - * case '_header' : - * return 'my header not translated'; + * case '_header' : + * return 'my header not translated'; * case true: * return $this->translator->trans('true'); * case false: @@ -74,17 +55,28 @@ interface AggregatorInterface extends ModifierInterface * } * } * ``` - * - * **Note:** Why each string must be translated with an exception for + * + * **Note:** Why each string must be translated with an exception for * the `_header` ? For performance reasons: most of the value will be number * which do not need to be translated, or value already translated in * database. But the header must be, in every case, translated. - * + * * @param string $key The column key, as added in the query * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') * @param mixed $data The data from the export's form (as defined in `buildForm` - * @return \Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + * + * @return Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` */ public function getLabels($key, array $values, $data); + /** + * give the list of keys the current export added to the queryBuilder in + * self::initiateQuery. + * + * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, + * this function will return `array('count_id')`. + * + * @param mixed[] $data the data from the export's form (added by self::buildForm) + */ + public function getQueryKeys($data); } diff --git a/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php b/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php index ea82175e9..734939b7c 100644 --- a/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php @@ -1,37 +1,36 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export; use Symfony\Component\Form\FormBuilderInterface; /** * The common methods between different object used to build export (i.e. : ExportInterface, - * FilterInterface, AggregatorInterface) - * - * @author Julien Fastré + * FilterInterface, AggregatorInterface). */ interface ExportElementInterface { /** - * get a title, which will be used in UI (and translated) - * + * Add a form to collect data from the user. + */ + public function buildForm(FormBuilderInterface $builder); + + /** + * get a title, which will be used in UI (and translated). + * * @return string */ public function getTitle(); - - - /** - * Add a form to collect data from the user. - * - * @param FormBuilderInterface $builder - */ - public function buildForm(FormBuilderInterface $builder); - } diff --git a/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php b/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php index cbc2f3881..c2e13d25e 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php @@ -1,46 +1,37 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Export; use Symfony\Component\Validator\Context\ExecutionContextInterface; /** - * Add a validation method to validate data collected by Export Element - * + * Add a validation method to validate data collected by Export Element. + * * Implements this interface on other `ExportElementInterface` to validate * the form submitted by users. - * + * * **note**: When this interface is implemented on filters or aggregators, * the form is validated **only** if the filter/aggregator is `enabled` by the * user. - * - * @link http://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator Example of building violations * - * @author Julien Fastré + * @see http://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator Example of building violations */ interface ExportElementValidatedInterface { /** * validate the form's data and, if required, build a contraint * violation on the data. - * + * * @param mixed $data the data, as returned by the user - * @param ExecutionContextInterface $context */ public function validateForm($data, ExecutionContextInterface $context); } diff --git a/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php b/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php index 077272c9b..b43d2d267 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php @@ -1,30 +1,22 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Export; /** * Interface to provide export elements dynamically. - * - * The typical use case is providing exports or aggregators depending on + * + * The typical use case is providing exports or aggregators depending on * dynamic data. Example: providing exports for reports, reports depending * on data stored in database. - * - * @author Julien Fastré */ interface ExportElementsProviderInterface { diff --git a/src/Bundle/ChillMainBundle/Export/ExportInterface.php b/src/Bundle/ChillMainBundle/Export/ExportInterface.php index c260649d1..419d9b250 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportInterface.php @@ -1,143 +1,70 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export; +use Closure; use Doctrine\ORM\QueryBuilder; /** * Interface for Export. - * - * An export is a class which will initiate a query for an export. - * - * **Note** : the report implementing this class will be modified by + * + * An export is a class which will initiate a query for an export. + * + * **Note** : the report implementing this class will be modified by * both filters **and** aggregators. If the report does not support * aggregation, use `ListInterface`. * * @example Chill\PersonBundle\Export\CountPerson an example of implementation - * @author Julien Fastré */ interface ExportInterface extends ExportElementInterface { - /** - * Return the Export's type. This will inform _on what_ export will apply. - * Most of the type, it will be a string which references an entity. - * - * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string - */ - public function getType(); - - /** - * A description, which will be used in the UI to explain what the export does. - * This description will be translated. - * - * @return string - */ - public function getDescription(); - - /** - * The initial query, which will be modified by ModifiersInterface - * (i.e. AggregatorInterface, FilterInterface). - * - * This query should take into account the `$acl` and restrict result only to - * what the user is allowed to see. (Do not show personal data the user - * is not allowed to see). - * - * The returned object should be an instance of QueryBuilder or NativeQuery. - * - * @param array $requiredModifiers - * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` - * @param array $data the data from the form, if any - * @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute. - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = array()); - - /** - * Inform which ModifiersInterface (i.e. AggregatorInterface, FilterInterface) - * are allowed. The modifiers should be an array of types the _modifier_ apply on - * (@see ModifiersInterface::applyOn()). - * - * @return string[] - */ - public function supportsModifiers(); - - /** - * Return the required Role to execute the Export. - * - * @return \Symfony\Component\Security\Core\Role\Role - * - */ - public function requiredRole(); - /** * Return which formatter type is allowed for this report. - * + * * @return string[] */ public function getAllowedFormattersTypes(); - + /** - * give the list of keys the current export added to the queryBuilder in - * self::initiateQuery - * - * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, - * this function will return `array('count_id')`. - * - * @param mixed[] $data the data from the export's form (added by self::buildForm) + * A description, which will be used in the UI to explain what the export does. + * This description will be translated. + * + * @return string */ - public function getQueryKeys($data); - - /** - * Return the results of the query builder. - * - * @param QueryBuilder|\Doctrine\ORM\NativeQuery $query - * @param mixed[] $data the data from the export's fomr (added by self::buildForm) - * @return mixed[] an array of results - */ - public function getResult($query, $data); - - + public function getDescription(); + /** * transform the results to viewable and understable string. - * - * The callable will have only one argument: the `value` to translate. - * + * + * The callable will have only one argument: the `value` to translate. + * * The callable should also be able to return a key `_header`, which - * will contains the header of the column. - * - * The string returned **must** be already translated if necessary, + * will contains the header of the column. + * + * The string returned **must** be already translated if necessary, * **with an exception** for the string returned for `_header`. - * - * Example : - * + * + * Example : + * * ``` * protected $translator; - * + * * public function getLabels($key, array $values, $data) * { * return function($value) { * case $value * { - * case '_header' : - * return 'my header not translated'; + * case '_header' : + * return 'my header not translated'; * case true: * return $this->translator->trans('true'); * case false: @@ -148,18 +75,81 @@ interface ExportInterface extends ExportElementInterface * } * } * ``` - * - * **Note:** Why each string must be translated with an exception for + * + * **Note:** Why each string must be translated with an exception for * the `_header` ? For performance reasons: most of the value will be number * which do not need to be translated, or value already translated in * database. But the header must be, in every case, translated. - * - * + * * @param string $key The column key, as added in the query * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') * @param mixed $data The data from the export's form (as defined in `buildForm` - * @return \Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + * + * @return Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` */ public function getLabels($key, array $values, $data); + /** + * give the list of keys the current export added to the queryBuilder in + * self::initiateQuery. + * + * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, + * this function will return `array('count_id')`. + * + * @param mixed[] $data the data from the export's form (added by self::buildForm) + */ + public function getQueryKeys($data); + + /** + * Return the results of the query builder. + * + * @param \Doctrine\ORM\NativeQuery|QueryBuilder $query + * @param mixed[] $data the data from the export's fomr (added by self::buildForm) + * + * @return mixed[] an array of results + */ + public function getResult($query, $data); + + /** + * Return the Export's type. This will inform _on what_ export will apply. + * Most of the type, it will be a string which references an entity. + * + * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE + * + * @return string + */ + public function getType(); + + /** + * The initial query, which will be modified by ModifiersInterface + * (i.e. AggregatorInterface, FilterInterface). + * + * This query should take into account the `$acl` and restrict result only to + * what the user is allowed to see. (Do not show personal data the user + * is not allowed to see). + * + * The returned object should be an instance of QueryBuilder or NativeQuery. + * + * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` + * @param array $data the data from the form, if any + * + * @return \Doctrine\ORM\NativeQuery|QueryBuilder the query to execute. + */ + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []); + + /** + * Return the required Role to execute the Export. + * + * @return \Symfony\Component\Security\Core\Role\Role + */ + public function requiredRole(); + + /** + * Inform which ModifiersInterface (i.e. AggregatorInterface, FilterInterface) + * are allowed. The modifiers should be an array of types the _modifier_ apply on + * (@see ModifiersInterface::applyOn()). + * + * @return string[] + */ + public function supportsModifiers(); } diff --git a/src/Bundle/ChillMainBundle/Export/ExportManager.php b/src/Bundle/ChillMainBundle/Export/ExportManager.php index d3ec7eabf..6c0d3bd8b 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportManager.php +++ b/src/Bundle/ChillMainBundle/Export/ExportManager.php @@ -1,154 +1,173 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export; -use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Export\AggregatorInterface; -use Chill\MainBundle\Export\ExportInterface; -use Chill\MainBundle\Export\FormatterInterface; -use Symfony\Component\HttpFoundation\Response; -use Psr\Log\LoggerInterface; +use Chill\MainBundle\Form\Type\Export\ExportType; +use Chill\MainBundle\Form\Type\Export\PickCenterType; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Chill\MainBundle\Form\Type\Export\PickCenterType; +use Generator; +use InvalidArgumentException; +use LogicException; +use Psr\Log\LoggerInterface; +use RuntimeException; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; -use Chill\MainBundle\Form\Type\Export\ExportType; -use Chill\MainBundle\Export\ListInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use UnexpectedValueException; +use function array_key_exists; +use function count; +use function get_class; +use function gettype; +use function in_array; /** * Collects all agregators, filters and export from * the installed bundle, and performs the export logic. - * - * - * @author Julien Fastré */ class ExportManager { /** - * The collected filters, injected by DI - * - * @var FilterInterface[] - */ - private $filters = array(); - - /** - * The collected aggregators, injected by DI + * The collected aggregators, injected by DI. * * @var AggregatorInterface[] */ - private $aggregators = array(); - + private $aggregators = []; + /** - * Collected Exports, injected by DI + * @var AuthorizationChecker + */ + private $authorizationChecker; + + /** + * @var AuthorizationHelper + */ + private $authorizationHelper; + + /** + * @var EntityManagerInterface + */ + private $em; + + /** + * Collected Exports, injected by DI. * * @var ExportInterface[] */ - private $exports = array(); - + private $exports = []; + /** - * Collected Formatters, injected by DI + * The collected filters, injected by DI. + * + * @var FilterInterface[] + */ + private $filters = []; + + /** + * Collected Formatters, injected by DI. * * @var FormatterInterface[] */ - private $formatters = array(); - + private $formatters = []; + /** - * a logger + * a logger. * * @var LoggerInterface */ private $logger; - + /** - * - * @var EntityManagerInterface - */ - private $em; - - /** - * - * @var AuthorizationChecker - */ - private $authorizationChecker; - - /** - * - * @var AuthorizationHelper - */ - private $authorizationHelper; - - /** - * * @var \Symfony\Component\Security\Core\User\UserInterface */ private $user; - + public function __construct( - LoggerInterface $logger, - EntityManagerInterface $em, - AuthorizationCheckerInterface $authorizationChecker, - AuthorizationHelper $authorizationHelper, - TokenStorageInterface $tokenStorage) - { + LoggerInterface $logger, + EntityManagerInterface $em, + AuthorizationCheckerInterface $authorizationChecker, + AuthorizationHelper $authorizationHelper, + TokenStorageInterface $tokenStorage + ) { $this->logger = $logger; $this->em = $em; $this->authorizationChecker = $authorizationChecker; $this->authorizationHelper = $authorizationHelper; $this->user = $tokenStorage->getToken()->getUser(); } - + /** - * add a Filter - * - * @internal Normally used by the dependency injection - * - * @param FilterInterface $filter - * @param string $alias + * Return a \Generator containing filter which support type. If `$centers` is + * not null, restrict the given filters to the center the user have access to. + * + * if $centers is null, the function will returns all filters where the user + * has access in every centers he can reach (if the user can use the filter F in + * center A, but not in center B, the filter F will not be returned) + * + * @param \Chill\MainBundle\Entity\Center[] $centers the centers where the user have access to + * + * @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias */ - public function addFilter(FilterInterface $filter, $alias) + public function &getFiltersApplyingOn(ExportInterface $export, ?array $centers = null) { - $this->filters[$alias] = $filter; + foreach ($this->filters as $alias => $filter) { + if (in_array($filter->applyOn(), $export->supportsModifiers(), true) + && $this->isGrantedForElement($filter, $export, $centers)) { + yield $alias => $filter; + } + } } - + /** - * add an aggregator - * + * Return a \Generator containing aggregators supported by the given export. + * + * @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array + * + * @return AggregatorInterface[] a \Generator that contains aggretagors. The key is the filter's alias + */ + public function &getAggregatorsApplyingOn(ExportInterface $export, ?array $centers = null) + { + if ($export instanceof ListInterface) { + return; + } + + foreach ($this->aggregators as $alias => $aggregator) { + if (in_array($aggregator->applyOn(), $export->supportsModifiers(), true) + && $this->isGrantedForElement($aggregator, $export, $centers)) { + yield $alias => $aggregator; + } + } + } + + /** + * add an aggregator. + * * @internal used by DI - * - * @param AggregatorInterface $aggregator + * * @param string $alias */ public function addAggregator(AggregatorInterface $aggregator, $alias) { $this->aggregators[$alias] = $aggregator; } - + /** - * add an export - * + * add an export. + * * @internal used by DI - * - * @param ExportInterface|DirectExportInterface $export + * + * @param DirectExportInterface|ExportInterface $export * @param type $alias */ public function addExport($export, $alias) @@ -156,30 +175,21 @@ class ExportManager if ($export instanceof ExportInterface || $export instanceof DirectExportInterface) { $this->exports[$alias] = $export; } else { - throw new \InvalidArgumentException(sprintf("The export with alias %s " - . "does not implements %s or %s.", $alias, ExportInterface::class, - DirectExportInterface::class)); + throw new InvalidArgumentException(sprintf( + 'The export with alias %s ' + . 'does not implements %s or %s.', + $alias, + ExportInterface::class, + DirectExportInterface::class + )); } } - - /** - * add a formatter - * - * @internal used by DI - * - * @param FormatterInterface $formatter - * @param type $alias - */ - public function addFormatter(FormatterInterface $formatter, $alias) - { - $this->formatters[$alias] = $formatter; - } - + public function addExportElementsProvider(ExportElementsProviderInterface $provider, $prefix) { foreach ($provider->getExportElements() as $suffix => $element) { - $alias = $prefix.'_'.$suffix; - + $alias = $prefix . '_' . $suffix; + if ($element instanceof ExportInterface) { $this->addExport($element, $alias); } elseif ($element instanceof FilterInterface) { @@ -189,33 +199,190 @@ class ExportManager } elseif ($element instanceof FormatterInterface) { $this->addFormatter($element, $alias); } else { - throw new \LogicException("This element ".\get_class($element)." " - . "is not an instance of export element"); + throw new LogicException('This element ' . get_class($element) . ' ' + . 'is not an instance of export element'); } } } - + + /** + * add a Filter. + * + * @internal Normally used by the dependency injection + * + * @param string $alias + */ + public function addFilter(FilterInterface $filter, $alias) + { + $this->filters[$alias] = $filter; + } + + /** + * add a formatter. + * + * @internal used by DI + * + * @param type $alias + */ + public function addFormatter(FormatterInterface $formatter, $alias) + { + $this->formatters[$alias] = $formatter; + } + + /** + * Generate a response which contains the requested data. + * + * @param string $exportAlias + * @param mixed[] $data + * + * @return Response + */ + public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData) + { + $export = $this->getExport($exportAlias); + //$qb = $this->em->createQueryBuilder(); + $centers = $this->getPickedCenters($pickedCentersData); + + if ($export instanceof DirectExportInterface) { + return $export->generate( + $this->buildCenterReachableScopes($centers, $export), + $data[ExportType::EXPORT_KEY] + ); + } + + $query = $export->initiateQuery( + $this->retrieveUsedModifiers($data), + $this->buildCenterReachableScopes($centers, $export), + $data[ExportType::EXPORT_KEY] + ); + + if ($query instanceof \Doctrine\ORM\NativeQuery) { + // throw an error if the export require other modifier, which is + // not allowed when the export return a `NativeQuery` + if (count($export->supportsModifiers()) > 0) { + throw new LogicException("The export with alias `{$exportAlias}` return " + . 'a `\\Doctrine\\ORM\\NativeQuery` and supports modifiers, which is not ' + . 'allowed. Either the method `supportsModifiers` should return an empty ' + . 'array, or return a `Doctrine\\ORM\\QueryBuilder`'); + } + } elseif ($query instanceof QueryBuilder) { + //handle filters + $this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers); + + //handle aggregators + $this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers); + + $this->logger->debug('current query is ' . $query->getDQL(), [ + 'class' => self::class, 'function' => __FUNCTION__, + ]); + } else { + throw new UnexpectedValueException('The method `intiateQuery` should return ' + . 'a `\\Doctrine\\ORM\\NativeQuery` or a `Doctrine\\ORM\\QueryBuilder` ' + . 'object.'); + } + + $result = $export->getResult($query, $data[ExportType::EXPORT_KEY]); + + if (!is_iterable($result)) { + throw new UnexpectedValueException( + sprintf( + 'The result of the export should be an iterable, %s given', + gettype($result) + ) + ); + } + + /** @var FormatterInterface $formatter */ + $formatter = $this->getFormatter($this->getFormatterAlias($data)); + $filtersData = []; + $aggregatorsData = []; + + if ($query instanceof QueryBuilder) { + $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); + + foreach ($aggregators as $alias => $aggregator) { + $aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form']; + } + } + + $filters = $this->retrieveUsedFilters($data[ExportType::FILTER_KEY]); + + foreach ($filters as $alias => $filter) { + $filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form']; + } + + return $formatter->getResponse( + $result, + $formatterData, + $exportAlias, + $data[ExportType::EXPORT_KEY], + $filtersData, + $aggregatorsData + ); + } + + /** + * @param string $alias + * + * @throws RuntimeException if the aggregator is not known + * + * @return AggregatorInterface + */ + public function getAggregator($alias) + { + if (!array_key_exists($alias, $this->aggregators)) { + throw new RuntimeException("The aggregator with alias {$alias} is not known."); + } + + return $this->aggregators[$alias]; + } + + public function getAggregators(array $aliases) + { + foreach ($aliases as $alias) { + yield $alias => $this->getAggregator($alias); + } + } + /** - * * @return string[] the existing type for known exports */ public function getExistingExportsTypes() { - $existingTypes = array(); - - foreach($this->exports as $export) { - if (!in_array($export->getType(), $existingTypes)) { - array_push($existingTypes, $export->getType()); + $existingTypes = []; + + foreach ($this->exports as $export) { + if (!in_array($export->getType(), $existingTypes, true)) { + $existingTypes[] = $export->getType(); } } - + return $existingTypes; } - + + /** + * Return an export by his alias. + * + * @param string $alias + * + * @throws RuntimeException + * + * @return ExportInterface + */ + public function getExport($alias) + { + if (!array_key_exists($alias, $this->exports)) { + throw new RuntimeException("The export with alias {$alias} is not known."); + } + + return $this->exports[$alias]; + } + /** * Return all exports. The exports's alias are the array's keys. - * - * @param boolean $whereUserIsGranted if true (default), restrict to user which are granted the right to execute the export + * + * @param bool $whereUserIsGranted if true (default), restrict to user which are granted the right to execute the export + * * @return ExportInterface[] an array where export's alias are keys */ public function getExports($whereUserIsGranted = true) @@ -230,16 +397,18 @@ class ExportManager } } } - + /** * Get all exports grouped in an array. - * + * * @param bool $whereUserIsGranted + * * @return array where keys are the groups's name and value is an array of exports */ public function getExportsGrouped($whereUserIsGranted = true): array { - $groups = [ '_' => [] ]; + $groups = ['_' => []]; + foreach ($this->getExports($whereUserIsGranted) as $alias => $export) { if ($export instanceof GroupedExportInterface) { $groups[$export->getGroup()][$alias] = $export; @@ -247,512 +416,346 @@ class ExportManager $groups['_'][$alias] = $export; } } - + return $groups; } - + /** - * Return an export by his alias - * - * @param string $alias - * @return ExportInterface - * @throws \RuntimeException - */ - public function getExport($alias) - { - if (!array_key_exists($alias, $this->exports)) { - throw new \RuntimeException("The export with alias $alias is not known."); - } - - return $this->exports[$alias]; - } - - /** - * * @param string $alias + * + * @throws RuntimeException if the filter is not known + * * @return FilterInterface - * @throws \RuntimeException if the filter is not known */ public function getFilter($alias) { if (!array_key_exists($alias, $this->filters)) { - throw new \RuntimeException("The filter with alias $alias is not known."); + throw new RuntimeException("The filter with alias {$alias} is not known."); } - + return $this->filters[$alias]; } - + /** - * get all filters - * - * @param \Generator $aliases + * get all filters. + * + * @param Generator $aliases */ public function getFilters(array $aliases) { - foreach($aliases as $alias) { + foreach ($aliases as $alias) { yield $alias => $this->getFilter($alias); } } - - /** - * - * @param string $alias - * @return AggregatorInterface - * @throws \RuntimeException if the aggregator is not known - */ - public function getAggregator($alias) - { - if (!array_key_exists($alias, $this->aggregators)) { - throw new \RuntimeException("The aggregator with alias $alias is not known."); - } - - return $this->aggregators[$alias]; - } - - public function getAggregators(array $aliases) - { - foreach ($aliases as $alias) { - yield $alias => $this->getAggregator($alias); - } - } - + public function getFormatter($alias) { if (!array_key_exists($alias, $this->formatters)) { - throw new \RuntimeException("The formatter with alias $alias is not known."); + throw new RuntimeException("The formatter with alias {$alias} is not known."); } - + return $this->formatters[$alias]; } - - /** - * Get all formatters which supports one of the given types. - * - * - * @param array $types - * @return \Generator - */ - public function getFormattersByTypes(array $types) - { - foreach ($this->formatters as $alias => $formatter) { - if (in_array($formatter->getType(), $types)) { - yield $alias => $formatter; - } - } - } - - - /** - * Return a \Generator containing filter which support type. If `$centers` is - * not null, restrict the given filters to the center the user have access to. - * - * if $centers is null, the function will returns all filters where the user - * has access in every centers he can reach (if the user can use the filter F in - * center A, but not in center B, the filter F will not be returned) - * - * @param \Chill\MainBundle\Entity\Center[] $centers the centers where the user have access to - * @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias - */ - public function &getFiltersApplyingOn(ExportInterface $export, array $centers = null) - { - foreach ($this->filters as $alias => $filter) { - if (in_array($filter->applyOn(), $export->supportsModifiers()) - && $this->isGrantedForElement($filter, $export, $centers)) { - yield $alias => $filter; - } - } - } - - /** - * Return true if the current user has access to the ExportElement for every - * center, false if the user hasn't access to element for at least one center. - * - * @param \Chill\MainBundle\Export\ExportElementInterface $element - * @param ExportInterface|DirectExportInterface $export - * @param array|null $centers, if null, the function take into account all the reachables centers for the current user and the role given by element::requiredRole - * @return boolean - */ - public function isGrantedForElement(ExportElementInterface $element, ExportElementInterface $export = NULL, array $centers = null) - { - if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) { - $role = $element->requiredRole(); - } elseif ($element instanceof ModifierInterface ) { - if (is_null($element->addRole())) { - if (is_null($export)) { - throw new \LogicException("The export should not be null: as the " - . "ModifierInstance element is not an export, we should " - . "be aware of the export to determine which role is required"); - } else { - $role = $export->requiredRole(); - } - } else { - $role = $element->addRole(); - } - } else { - throw new \LogicException("The element is not an ModifiersInterface or " - . "an ExportInterface."); - } - - if ($centers === null) { - $centers = $this->authorizationHelper->getReachableCenters($this->user, - $role); - } - - if (count($centers) === 0) { - return false; - } - - foreach($centers as $center) { - if ($this->authorizationChecker->isGranted($role->getRole(), $center) === false) { - //debugging - $this->logger->debug('user has no access to element', array( - 'method' => __METHOD__, - 'type' => get_class($element), - 'center' => $center->getName(), - 'role' => $role->getRole() - )); - - return false; - } - } - - return true; - } - - /** - * Return a \Generator containing aggregators supported by the given export - * - * @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array - * @return AggregatorInterface[] a \Generator that contains aggretagors. The key is the filter's alias - */ - public function &getAggregatorsApplyingOn(ExportInterface $export, array $centers = null) - { - if ($export instanceof ListInterface) { - - return; - } - - foreach ($this->aggregators as $alias => $aggregator) { - if (in_array($aggregator->applyOn(), $export->supportsModifiers()) && - $this->isGrantedForElement($aggregator, $export, $centers)) { - yield $alias => $aggregator; - } - } - } - - /** - * Generate a response which contains the requested data. - * - * @param string $exportAlias - * @param mixed[] $data - * @return Response - */ - public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData) - { - $export = $this->getExport($exportAlias); - //$qb = $this->em->createQueryBuilder(); - $centers = $this->getPickedCenters($pickedCentersData); - - if ($export instanceof DirectExportInterface) { - return $export->generate( - $this->buildCenterReachableScopes($centers, $export), - $data[ExportType::EXPORT_KEY] - ); - } - - $query = $export->initiateQuery( - $this->retrieveUsedModifiers($data), - $this->buildCenterReachableScopes($centers, $export), - $data[ExportType::EXPORT_KEY] - ); - - if ($query instanceof \Doctrine\ORM\NativeQuery) { - // throw an error if the export require other modifier, which is - // not allowed when the export return a `NativeQuery` - if (count($export->supportsModifiers()) > 0) { - throw new \LogicException("The export with alias `$exportAlias` return " - . "a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not " - . "allowed. Either the method `supportsModifiers` should return an empty " - . "array, or return a `Doctrine\ORM\QueryBuilder`"); - } - } elseif ($query instanceof QueryBuilder) { - //handle filters - $this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers); - //handle aggregators - $this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers); - - $this->logger->debug('current query is '.$query->getDQL(), array( - 'class' => self::class, 'function' => __FUNCTION__ - )); - } else { - throw new \UnexpectedValueException("The method `intiateQuery` should return " - . "a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` " - . "object."); - } - - $result = $export->getResult($query, $data[ExportType::EXPORT_KEY]); - - if (!is_iterable($result)) { - throw new \UnexpectedValueException( - sprintf( - 'The result of the export should be an iterable, %s given', - gettype($result) - ) - ); - } - - /* @var $formatter FormatterInterface */ - $formatter = $this->getFormatter($this->getFormatterAlias($data)); - $filtersData = array(); - $aggregatorsData = array(); - - if ($query instanceof QueryBuilder) { - $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); - - foreach($aggregators as $alias => $aggregator) { - $aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form']; - } - } - - $filters = $this->retrieveUsedFilters($data[ExportType::FILTER_KEY]); - - foreach($filters as $alias => $filter) { - $filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form']; - } - - return $formatter->getResponse( - $result, - $formatterData, - $exportAlias, - $data[ExportType::EXPORT_KEY], - $filtersData, - $aggregatorsData); - } - /** - * build the array required for defining centers and circles in the initiate - * queries of ExportElementsInterfaces - * - * @param \Chill\MainBundle\Entity\Center[] $centers - */ - private function buildCenterReachableScopes(array $centers, ExportElementInterface $element) { - $r = array(); - - foreach($centers as $center) { - $r[] = array( - 'center' => $center, - 'circles' => $this->authorizationHelper->getReachableScopes($this->user, - $element->requiredRole(), $center) - ); - } - - return $r; - } - - /** - * get the aggregators typse used in the form export data - * - * @param array $data the data from the export form - * @return string[] - */ - public function getUsedAggregatorsAliases(array $data) - { - $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); - - return array_keys(iterator_to_array($aggregators)); - } - - /** - * get the formatter alias from the form export data - * + * get the formatter alias from the form export data. + * * @param array $data the data from the export form * @string the formatter alias|null */ public function getFormatterAlias(array $data) { if (array_key_exists(ExportType::PICK_FORMATTER_KEY, $data)) { - return $data[ExportType::PICK_FORMATTER_KEY]['alias']; + return $data[ExportType::PICK_FORMATTER_KEY]['alias']; } return null; } - + + /** + * Get all formatters which supports one of the given types. + * + * @return Generator + */ + public function getFormattersByTypes(array $types) + { + foreach ($this->formatters as $alias => $formatter) { + if (in_array($formatter->getType(), $types, true)) { + yield $alias => $formatter; + } + } + } + /** * Get the Center picked by the user for this export. The data are - * extracted from the PickCenterType data - * + * extracted from the PickCenterType data. + * * @param array $data the data from a PickCenterType + * * @return \Chill\MainBundle\Entity\Center[] the picked center */ public function getPickedCenters(array $data) { return $data; } - + /** - * parse the data to retrieve the used filters and aggregators - * - * @param mixed $data + * get the aggregators typse used in the form export data. + * + * @param array $data the data from the export form + * * @return string[] */ - private function retrieveUsedModifiers($data) + public function getUsedAggregatorsAliases(array $data) { - if ($data === null) { - return []; - } - - $usedTypes = array_merge( - $this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]), - $this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]) - ); - - $this->logger->debug('Required types are '.implode(', ', $usedTypes), - array('class' => self::class, 'function' => __FUNCTION__)); - - return array_unique($usedTypes); + $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); + + return array_keys(iterator_to_array($aggregators)); } - + /** - * Retrieve the filter used in this export. - * - * @param mixed $data the data from the `filters` key of the ExportType - * @return array an array with types + * Return true if the current user has access to the ExportElement for every + * center, false if the user hasn't access to element for at least one center. + * + * @param \Chill\MainBundle\Export\ExportElementInterface $element + * @param DirectExportInterface|ExportInterface $export + * + * @return bool */ - private function retrieveUsedFiltersType($data) + public function isGrantedForElement(ExportElementInterface $element, ?ExportElementInterface $export = null, ?array $centers = null) { - if ($data === null) { - return []; - } - - $usedTypes = array(); - foreach($data as $alias => $filterData) { - if ($filterData['enabled'] == true){ - $filter = $this->getFilter($alias); - if (!in_array($filter->applyOn(), $usedTypes)) { - array_push($usedTypes, $filter->applyOn()); + if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) { + $role = $element->requiredRole(); + } elseif ($element instanceof ModifierInterface) { + if (null === $element->addRole()) { + if (null === $export) { + throw new LogicException('The export should not be null: as the ' + . 'ModifierInstance element is not an export, we should ' + . 'be aware of the export to determine which role is required'); } + $role = $export->requiredRole(); + } else { + $role = $element->addRole(); } + } else { + throw new LogicException('The element is not an ModifiersInterface or ' + . 'an ExportInterface.'); } - - return $usedTypes; - } - - /** - * - * @param mixed $data - * @return string[] - */ - private function retrieveUsedAggregatorsType($data) - { - if ($data === null) { - return []; - } - - $usedTypes = array(); - foreach($this->retrieveUsedAggregators($data) as $alias => $aggregator) { - if (!in_array($aggregator->applyOn(), $usedTypes)) { - array_push($usedTypes, $aggregator->applyOn()); - } - } - - return $usedTypes; - } - - /** - * - * @param mixed $data - * @return AggregatorInterface[] - */ - private function retrieveUsedAggregators($data) - { - if ($data === null) { - return []; - } - - foreach ($data as $alias => $aggregatorData) { - if ($aggregatorData['enabled'] === true){ - yield $alias => $this->getAggregator($alias); - } - } - } - - /** - * - * @param type $data the data from the filter key of the ExportType - */ - private function retrieveUsedFilters($data) - { - if ($data === null) { - return []; - } - - foreach ($data as $alias => $filterData) { - if ($filterData['enabled'] === true) { - yield $alias => $this->getFilter($alias); - } - } - } - - /** - * alter the query with selected filters. - * - * This function check the acl. - * - * @param ExportInterface $export - * @param QueryBuilder $qb - * @param mixed $data the data under the initial 'filters' data - * @param \Chill\MainBundle\Entity\Center[] $centers the picked centers - * @throw UnauthorizedHttpException if the user is not authorized - */ - private function handleFilters( - ExportInterface $export, - QueryBuilder $qb, - $data, - array $centers) - { - $filters = $this->retrieveUsedFilters($data); - - foreach($filters as $alias => $filter) { - if ($this->isGrantedForElement($filter, $export, $centers) === false) { - throw new UnauthorizedHttpException("You are not authorized to " - . "use the filter ".$filter->getTitle()); - } - - $formData = $data[$alias]; - - $this->logger->debug('alter query by filter '.$alias, array( - 'class' => self::class, 'function' => __FUNCTION__ - )); - $filter->alterQuery($qb, $formData['form']); + if (null === $centers) { + $centers = $this->authorizationHelper->getReachableCenters( + $this->user, + $role->getRole(), + ); } + + if (count($centers) === 0) { + return false; + } + + foreach ($centers as $center) { + if ($this->authorizationChecker->isGranted($role->getRole(), $center) === false) { + //debugging + $this->logger->debug('user has no access to element', [ + 'method' => __METHOD__, + 'type' => get_class($element), + 'center' => $center->getName(), + 'role' => $role->getRole(), + ]); + + return false; + } + } + + return true; } - + /** - * Alter the query with selected aggregators - * + * build the array required for defining centers and circles in the initiate + * queries of ExportElementsInterfaces. + * + * @param \Chill\MainBundle\Entity\Center[] $centers + */ + private function buildCenterReachableScopes(array $centers, ExportElementInterface $element) + { + $r = []; + + foreach ($centers as $center) { + $r[] = [ + 'center' => $center, + 'circles' => $this->authorizationHelper->getReachableScopes( + $this->user, + $element->requiredRole()->getRole(), + $center + ), + ]; + } + + return $r; + } + + /** + * Alter the query with selected aggregators. + * * Check for acl. If an user is not authorized to see an aggregator, throw an * UnauthorizedException. - * - * @param ExportInterface $export - * @param QueryBuilder $qb + * * @param type $data - * @param \Chill\MainBundle\Entity\Center[] $centers the picked centers * @throw UnauthorizedHttpException if the user is not authorized */ private function handleAggregators( - ExportInterface $export, - QueryBuilder $qb, - $data, - array $center) - { + ExportInterface $export, + QueryBuilder $qb, + $data, + array $center + ) { $aggregators = $this->retrieveUsedAggregators($data); - + foreach ($aggregators as $alias => $aggregator) { $formData = $data[$alias]; $aggregator->alterQuery($qb, $formData['form']); } } - + + /** + * alter the query with selected filters. + * + * This function check the acl. + * + * @param mixed $data the data under the initial 'filters' data + * @param \Chill\MainBundle\Entity\Center[] $centers the picked centers + * @throw UnauthorizedHttpException if the user is not authorized + */ + private function handleFilters( + ExportInterface $export, + QueryBuilder $qb, + $data, + array $centers + ) { + $filters = $this->retrieveUsedFilters($data); + + foreach ($filters as $alias => $filter) { + if ($this->isGrantedForElement($filter, $export, $centers) === false) { + throw new UnauthorizedHttpException('You are not authorized to ' + . 'use the filter ' . $filter->getTitle()); + } + + $formData = $data[$alias]; + + $this->logger->debug('alter query by filter ' . $alias, [ + 'class' => self::class, 'function' => __FUNCTION__, + ]); + $filter->alterQuery($qb, $formData['form']); + } + } + + /** + * @param mixed $data + * + * @return AggregatorInterface[] + */ + private function retrieveUsedAggregators($data) + { + if (null === $data) { + return []; + } + + foreach ($data as $alias => $aggregatorData) { + if (true === $aggregatorData['enabled']) { + yield $alias => $this->getAggregator($alias); + } + } + } + + /** + * @param mixed $data + * + * @return string[] + */ + private function retrieveUsedAggregatorsType($data) + { + if (null === $data) { + return []; + } + + $usedTypes = []; + + foreach ($this->retrieveUsedAggregators($data) as $alias => $aggregator) { + if (!in_array($aggregator->applyOn(), $usedTypes, true)) { + $usedTypes[] = $aggregator->applyOn(); + } + } + + return $usedTypes; + } + + /** + * @param type $data the data from the filter key of the ExportType + */ + private function retrieveUsedFilters($data) + { + if (null === $data) { + return []; + } + + foreach ($data as $alias => $filterData) { + if (true === $filterData['enabled']) { + yield $alias => $this->getFilter($alias); + } + } + } + + /** + * Retrieve the filter used in this export. + * + * @param mixed $data the data from the `filters` key of the ExportType + * + * @return array an array with types + */ + private function retrieveUsedFiltersType($data) + { + if (null === $data) { + return []; + } + + $usedTypes = []; + + foreach ($data as $alias => $filterData) { + if (true === $filterData['enabled']) { + $filter = $this->getFilter($alias); + + if (!in_array($filter->applyOn(), $usedTypes, true)) { + $usedTypes[] = $filter->applyOn(); + } + } + } + + return $usedTypes; + } + + /** + * parse the data to retrieve the used filters and aggregators. + * + * @param mixed $data + * + * @return string[] + */ + private function retrieveUsedModifiers($data) + { + if (null === $data) { + return []; + } + + $usedTypes = array_merge( + $this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]), + $this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]) + ); + + $this->logger->debug( + 'Required types are ' . implode(', ', $usedTypes), + ['class' => self::class, 'function' => __FUNCTION__] + ); + + return array_unique($usedTypes); + } } diff --git a/src/Bundle/ChillMainBundle/Export/FilterInterface.php b/src/Bundle/ChillMainBundle/Export/FilterInterface.php index b644ae8a7..0b27f1cf1 100644 --- a/src/Bundle/ChillMainBundle/Export/FilterInterface.php +++ b/src/Bundle/ChillMainBundle/Export/FilterInterface.php @@ -1,70 +1,57 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export; - /** - * Interface for filters. - * - * Filter will filter result on the query initiated by Export. Most of the time, - * it will add a `WHERE` clause on this query. - * - * Filters should not add column in `SELECT` clause. - * + * Interface for filters. * - * @author Julien Fastré + * Filter will filter result on the query initiated by Export. Most of the time, + * it will add a `WHERE` clause on this query. + * + * Filters should not add column in `SELECT` clause. */ interface FilterInterface extends ModifierInterface { - - const STRING_FORMAT = 'string'; - + public const STRING_FORMAT = 'string'; + /** * Describe the filtering action. - * - * The description will be inserted in report to remains to the user the + * + * The description will be inserted in report to remains to the user the * filters applyied on this report. - * - * Should return a statement about the filtering action, like : - * "filtering by date: from xxxx-xx-xx to xxxx-xx-xx" or + * + * Should return a statement about the filtering action, like : + * "filtering by date: from xxxx-xx-xx to xxxx-xx-xx" or * "filtering by nationality: only Germany, France" - * + * * The format will be determined by the $format. Currently, only 'string' is - * supported, later some 'html' will be added. The filter should always + * supported, later some 'html' will be added. The filter should always * implement the 'string' format and fallback to it if other format are used. - * + * * If no i18n is necessery, or if the filter translate the string by himself, * this function should return a string. If the filter does not do any translation, - * the return parameter should be an array, where - * - * - the first element is the string, + * the return parameter should be an array, where + * + * - the first element is the string, * - and the second an array of parameters (may be an empty array) * - the 3rd the domain (optional) * - the 4th the locale (optional) - * + * * Example: `array('my string with %parameter%', ['%parameter%' => 'good news'], 'mydomain', 'mylocale')` - * + * * @param array $data * @param string $format the format - * @return string|array a string with the data or, if translatable, an array where first element is string, second elements is an array of arguments + * + * @return array|string a string with the data or, if translatable, an array where first element is string, second elements is an array of arguments */ public function describeAction($data, $format = 'string'); - } diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php index e77556a43..914fa15c3 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php @@ -1,95 +1,74 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export\Formatter; -use Chill\MainBundle\Export\ExportInterface; -use Symfony\Component\HttpFoundation\Response; -use Chill\MainBundle\Export\FormatterInterface; -use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Export\ExportManager; -use Symfony\Component\Form\Extension\Core\Type\FormType; +use Chill\MainBundle\Export\FormatterInterface; +use LogicException; +use RuntimeException; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; - -// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatorInterface; +use function array_key_exists; +use function array_slice; +use function call_user_func; +use function count; +use function is_array; /** + * Command to get the report with curl: + * curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff. * - * - * @author Julien Fastré * @deprecated this formatter is not used any more. */ class CSVFormatter implements FormatterInterface { - /** - * - * @var TranslatorInterface - */ - protected $translator; - - protected $result; - - protected $formatterData; - - protected $export; - protected $aggregators; - protected $exportData; - protected $aggregatorsData; - protected $filtersData; + protected $export; - protected $labels; + protected $exportData; /** - * * @var ExportManager */ protected $exportManager; + protected $filtersData; - public function __construct(TranslatorInterface $translator, - ExportManager $manager) - { + protected $formatterData; + + protected $labels; + + protected $result; + + protected TranslatorInterface $translator; + + public function __construct( + TranslatorInterface $translator, + ExportManager $manager + ) { $this->translator = $translator; $this->exportManager = $manager; } - public function getType() - { - return 'tabular'; - } - - public function getName() - { - return 'Comma separated values (CSV)'; - } - /** - * * @uses appendAggregatorForm - * @param FormBuilderInterface $builder - * @param type $exportAlias - * @param array $aggregatorAliases + * + * @param mixed $exportAlias */ public function buildForm(FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases) { @@ -97,58 +76,61 @@ class CSVFormatter implements FormatterInterface $nb = count($aggregatorAliases); foreach ($aggregators as $alias => $aggregator) { - $builderAggregator = $builder->create($alias, FormType::class, array( - 'label' => $aggregator->getTitle(), - 'block_name' => '_aggregator_placement_csv_formatter' - )); + $builderAggregator = $builder->create($alias, FormType::class, [ + 'label' => $aggregator->getTitle(), + 'block_name' => '_aggregator_placement_csv_formatter', + ]); $this->appendAggregatorForm($builderAggregator, $nb); $builder->add($builderAggregator); } } - /** - * append a form line by aggregator on the formatter form. - * - * This form allow to choose the aggregator position (row or column) and - * the ordering - * - * @param FormBuilderInterface $builder - * @param string $nbAggregators - */ - private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) + public function gatherFiltersDescriptions() { - $builder->add('order', ChoiceType::class, array( - 'choices' => array_combine( - range(1, $nbAggregators), - range(1, $nbAggregators) - ), - 'multiple' => false, - 'expanded' => false - )); + $descriptions = []; - $builder->add('position', ChoiceType::class, array( - 'choices' => array( - 'row' => 'r', - 'column' => 'c' - ), - 'multiple' => false, - 'expanded' => false - )); + foreach ($this->filtersData as $key => $filterData) { + $statement = $this->exportManager + ->getFilter($key) + ->describeAction($filterData); + + if (null === $statement) { + continue; + } + + if (is_array($statement)) { + $descriptions[] = $this->translator->trans( + $statement[0], + $statement[1], + $statement[2] ?? null, + $statement[3] ?? null + ); + } else { + $descriptions[] = $statement; + } + } + + return $descriptions; + } + + public function getName() + { + return 'Comma separated values (CSV)'; } public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData ) { $this->result = $result; $this->orderingHeaders($formatterData); $this->export = $this->exportManager->getExport($exportAlias); $this->aggregators = iterator_to_array($this->exportManager - ->getAggregators(array_keys($aggregatorsData))); + ->getAggregators(array_keys($aggregatorsData))); $this->exportData = $exportData; $this->aggregatorsData = $aggregatorsData; $this->labels = $this->gatherLabels(); @@ -164,67 +146,101 @@ class CSVFormatter implements FormatterInterface return $response; } - /** - * ordering aggregators, preserving key association. - * - * This function do not mind about position. - * - * If two aggregators have the same order, the second given will be placed - * after. This is not significant for the first ordering. - * - * @param type $formatterData - * @return type - */ - protected function orderingHeaders($formatterData) + public function getType() { - $this->formatterData = $formatterData; - uasort($this->formatterData, function($a, $b) { + return 'tabular'; + } - return ($a['order'] <= $b['order'] ? -1 : 1); - }); + protected function gatherLabels() + { + return array_merge( + $this->gatherLabelsFromAggregators(), + $this->gatherLabelsFromExport() + ); + } + + protected function gatherLabelsFromAggregators() + { + $labels = []; + /* @var $aggretator \Chill\MainBundle\Export\AggregatorInterface */ + foreach ($this->aggregators as $alias => $aggregator) { + $keys = $aggregator->getQueryKeys($this->aggregatorsData[$alias]); + + // gather data in an array + foreach ($keys as $key) { + $values = array_map(static function ($row) use ($key, $alias) { + if (!array_key_exists($key, $row)) { + throw new LogicException("the key '" . $key . "' is declared by " + . "the aggregator with alias '" . $alias . "' but is not " + . 'present in results'); + } + + return $row[$key]; + }, $this->result); + $labels[$key] = $aggregator->getLabels( + $key, + array_unique($values), + $this->aggregatorsData[$alias] + ); + } + } + + return $labels; + } + + protected function gatherLabelsFromExport() + { + $labels = []; + $export = $this->export; + $keys = $this->export->getQueryKeys($this->exportData); + + foreach ($keys as $key) { + $values = array_map(static function ($row) use ($key, $export) { + if (!array_key_exists($key, $row)) { + throw new LogicException("the key '" . $key . "' is declared by " + . "the export with title '" . $export->getTitle() . "' but is not " + . 'present in results'); + } + + return $row[$key]; + }, $this->result); + $labels[$key] = $this->export->getLabels( + $key, + array_unique($values), + $this->exportData + ); + } + + return $labels; } protected function generateContent() { + $line = null; $rowKeysNb = count($this->getRowHeaders()); $columnKeysNb = count($this->getColumnHeaders()); $resultsKeysNb = count($this->export->getQueryKeys($this->exportData)); $results = $this->getOrderedResults(); - /* @var $columnHeaders string[] the column headers associations */ - $columnHeaders = array(); - /* @var $data string[] the data of the csv file */ - $contentData = array(); - $content = array(); - - function findColumnPosition(&$columnHeaders, $columnToFind) { - $i = 0; - foreach($columnHeaders as $set) { - if ($set === $columnToFind) { - return $i; - } - $i++; - } - - //we didn't find it, adding the column - $columnHeaders[] = $columnToFind; - - return $i++; - } + /** @var string[] $columnHeaders the column headers associations */ + $columnHeaders = []; + /** @var string[] $data the data of the csv file */ + $contentData = []; + $content = []; // create a file pointer connected to the output stream - $output = fopen('php://output', 'w'); + $output = fopen('php://output', 'wb'); //title - fputcsv($output, array($this->translator->trans($this->export->getTitle()))); + fputcsv($output, [$this->translator->trans($this->export->getTitle())]); //blank line - fputcsv($output, array("")); + fputcsv($output, ['']); // add filtering description - foreach($this->gatherFiltersDescriptions() as $desc) { - fputcsv($output, array($desc)); + foreach ($this->gatherFiltersDescriptions() as $desc) { + fputcsv($output, [$desc]); } // blank line - fputcsv($output, array("")); + fputcsv($output, ['']); // iterate on result to : 1. populate row headers, 2. populate column headers, 3. add result foreach ($results as $row) { @@ -242,46 +258,47 @@ class CSVFormatter implements FormatterInterface } // add the column headers - /* @var $columns string[] the column for this row */ + /** @var string[] $columns the column for this row */ $columns = array_slice($row, $rowKeysNb, $columnKeysNb); - $columnPosition = findColumnPosition($columnHeaders, $columns); + $columnPosition = $this->findColumnPosition($columnHeaders, $columns); //fill with blank at the position given by the columnPosition + nbRowHeaders - for ($i=0; $i < $columnPosition; $i++) { + for ($i = 0; $i < $columnPosition; ++$i) { if (!isset($line[$rowKeysNb + $i])) { - $line[$rowKeysNb + $i] = ""; + $line[$rowKeysNb + $i] = ''; } } - $resultData = array_slice($row, $resultsKeysNb*-1); - foreach($resultData as $data) { + $resultData = array_slice($row, $resultsKeysNb * -1); + + foreach ($resultData as $data) { $line[] = $data; } - } // we add the last line $contentData[] = $line; //column title headers - for ($i=0; $i < $columnKeysNb; $i++) { + for ($i = 0; $i < $columnKeysNb; ++$i) { $line = array_fill(0, $rowKeysNb, ''); - foreach($columnHeaders as $set) { + foreach ($columnHeaders as $set) { $line[] = $set[$i]; } $content[] = $line; } - //row title headers - $headerLine = array(); - foreach($this->getRowHeaders() as $headerKey) { + $headerLine = []; + + foreach ($this->getRowHeaders() as $headerKey) { $headerLine[] = array_key_exists('_header', $this->labels[$headerKey]) ? $this->labels[$headerKey]['_header'] : ''; } - foreach($this->export->getQueryKeys($this->exportData) as $key) { + + foreach ($this->export->getQueryKeys($this->exportData) as $key) { $headerLine[] = array_key_exists('_header', $this->labels[$key]) ? $this->labels[$key]['_header'] : ''; } @@ -289,10 +306,11 @@ class CSVFormatter implements FormatterInterface unset($headerLine); //free memory //generate CSV - foreach($content as $line) { + foreach ($content as $line) { fputcsv($output, $line); } - foreach($contentData as $line) { + + foreach ($contentData as $line) { fputcsv($output, $line); } @@ -302,10 +320,82 @@ class CSVFormatter implements FormatterInterface return $text; } + protected function getColumnHeaders() + { + return $this->getPositionnalHeaders('c'); + } + + protected function getRowHeaders() + { + return $this->getPositionnalHeaders('r'); + } + + /** + * ordering aggregators, preserving key association. + * + * This function do not mind about position. + * + * If two aggregators have the same order, the second given will be placed + * after. This is not significant for the first ordering. + */ + protected function orderingHeaders(array $formatterData) + { + $this->formatterData = $formatterData; + uasort( + $this->formatterData, + static fn (array $a, array $b): int => ($a['order'] <= $b['order'] ? -1 : 1) + ); + } + + /** + * append a form line by aggregator on the formatter form. + * + * This form allow to choose the aggregator position (row or column) and + * the ordering + * + * @param string $nbAggregators + */ + private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) + { + $builder->add('order', ChoiceType::class, [ + 'choices' => array_combine( + range(1, $nbAggregators), + range(1, $nbAggregators) + ), + 'multiple' => false, + 'expanded' => false, + ]); + + $builder->add('position', ChoiceType::class, [ + 'choices' => [ + 'row' => 'r', + 'column' => 'c', + ], + 'multiple' => false, + 'expanded' => false, + ]); + } + + private function findColumnPosition(&$columnHeaders, $columnToFind): int + { + $i = 0; + + foreach ($columnHeaders as $set) { + if ($set === $columnToFind) { + return $i; + } + ++$i; + } + + //we didn't find it, adding the column + $columnHeaders[] = $columnToFind; + + return $i++; + } private function getOrderedResults() { - $r = array(); + $r = []; $results = $this->result; $labels = $this->labels; $rowKeys = $this->getRowHeaders(); @@ -314,9 +404,9 @@ class CSVFormatter implements FormatterInterface $headers = array_merge($rowKeys, $columnKeys); foreach ($results as $row) { - $line = array(); - foreach ($headers as $key) { + $line = []; + foreach ($headers as $key) { $line[] = call_user_func($labels[$key], $row[$key]); } @@ -333,31 +423,22 @@ class CSVFormatter implements FormatterInterface return $r; } - - protected function getRowHeaders() - { - return $this->getPositionnalHeaders('r'); - } - - protected function getColumnHeaders() - { - return $this->getPositionnalHeaders('c'); - } - /** - * * @param string $position may be 'c' (column) or 'r' (row) + * + * @throws RuntimeException + * * @return string[] - * @throws \RuntimeException */ private function getPositionnalHeaders($position) { - $headers = array(); - foreach($this->formatterData as $alias => $data) { + $headers = []; + + foreach ($this->formatterData as $alias => $data) { if (!array_key_exists($alias, $this->aggregatorsData)) { - throw new \RuntimeException("the formatter wants to use the " - . "aggregator with alias $alias, but the export do not " - . "contains data about it"); + throw new RuntimeException('the formatter wants to use the ' + . "aggregator with alias {$alias}, but the export do not " + . 'contains data about it'); } $aggregator = $this->aggregators[$alias]; @@ -369,95 +450,4 @@ class CSVFormatter implements FormatterInterface return $headers; } - - /** - * - * @param mixed $result - * @param \Chill\MainBundle\Export\AggregatorInterface[] $aggregators - */ - protected function gatherLabels() - { - return array_merge( - $this->gatherLabelsFromAggregators(), - $this->gatherLabelsFromExport() - ); - } - - protected function gatherLabelsFromAggregators() - { - $labels = array(); - /* @var $aggretator \Chill\MainBundle\Export\AggregatorInterface */ - foreach ($this->aggregators as $alias => $aggregator) { - $keys = $aggregator->getQueryKeys($this->aggregatorsData[$alias]); - - // gather data in an array - foreach($keys as $key) { - $values = array_map(function($row) use ($key, $alias) { - if (!array_key_exists($key, $row)) { - throw new \LogicException("the key '".$key."' is declared by " - . "the aggregator with alias '".$alias."' but is not " - . "present in results"); - } - - return $row[$key]; - }, $this->result); - $labels[$key] = $aggregator->getLabels($key, array_unique($values), - $this->aggregatorsData[$alias]); - } - } - - return $labels; - } - - protected function gatherLabelsFromExport() - { - $labels = array(); - $export = $this->export; - $keys = $this->export->getQueryKeys($this->exportData); - - foreach($keys as $key) { - $values = array_map(function($row) use ($key, $export) { - if (!array_key_exists($key, $row)) { - throw new \LogicException("the key '".$key."' is declared by " - . "the export with title '".$export->getTitle()."' but is not " - . "present in results"); - } - - return $row[$key]; - }, $this->result); - $labels[$key] = $this->export->getLabels($key, array_unique($values), - $this->exportData); - } - - return $labels; - } - - public function gatherFiltersDescriptions() - { - $descriptions = array(); - - foreach($this->filtersData as $key => $filterData) { - - $statement = $this->exportManager - ->getFilter($key) - ->describeAction($filterData); - - if ($statement === null) { - continue; - } - - if (is_array($statement)) { - $descriptions[] = $this->translator->trans( - $statement[0], - $statement[1], - isset($statement[2]) ? $statement[2] : null, - isset($statement[3]) ? $statement[3] : null); - } else { - $descriptions[] = $statement; - } - } - - return $descriptions; - } - } diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php index b3d004819..94c8c5ee3 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php @@ -1,68 +1,61 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export\Formatter; -use Symfony\Component\HttpFoundation\Response; -use Chill\MainBundle\Export\FormatterInterface; -use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\FormatterInterface; +use LogicException; +use OutOfBoundsException; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Translation\TranslatorInterface; +use function array_key_exists; +use function array_keys; +use function array_map; +use function count; +use function implode; // command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff /** - * Create a CSV List for the export - * - * @author Champs-Libres + * Create a CSV List for the export. */ class CSVListFormatter implements FormatterInterface { - + protected $exportAlias; + + protected $exportData; + /** - * This variable cache the labels internally - * - * @var string[] - */ - protected $labelsCache = null; - - protected $result = null; - - protected $exportAlias = null; - - protected $exportData = null; - - protected $formatterData = null; - - /** - * * @var ExportManager */ protected $exportManager; - + + protected $formatterData; + /** + * This variable cache the labels internally. * + * @var string[] + */ + protected $labelsCache; + + protected $result; + + /** * @var TranslatorInterface */ protected $translator; - public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager) { @@ -70,152 +63,130 @@ class CSVListFormatter implements FormatterInterface $this->exportManager = $exportManager; } - - public function getType() - { - return FormatterInterface::TYPE_LIST; + /** + * build a form, which will be used to collect data required for the execution + * of this formatter. + * + * @uses appendAggregatorForm + * + * @param type $exportAlias + */ + public function buildForm( + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ) { + $builder->add('numerotation', ChoiceType::class, [ + 'choices' => [ + 'yes' => true, + 'no' => false, + ], + 'expanded' => true, + 'multiple' => false, + 'label' => 'Add a number on first column', + 'data' => true, + ]); } - + public function getName() { return 'CSV vertical list'; } - + /** - * build a form, which will be used to collect data required for the execution - * of this formatter. - * - * @uses appendAggregatorForm - * @param FormBuilderInterface $builder - * @param type $exportAlias - * @param array $aggregatorAliases - */ - public function buildForm( - FormBuilderInterface $builder, - $exportAlias, - array $aggregatorAliases - ){ - $builder->add('numerotation', ChoiceType::class, array( - 'choices' => array( - 'yes' => true, - 'no' => false - ), - 'expanded' => true, - 'multiple' => false, - 'label' => "Add a number on first column", - 'data' => true - )); - } - - /** - * Generate a response from the data collected on differents ExportElementInterface - * + * Generate a response from the data collected on differents ExportElementInterface. + * * @param mixed[] $result The result, as given by the ExportInterface * @param mixed[] $formatterData collected from the current form * @param string $exportAlias the id of the current export * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * * @return \Symfony\Component\HttpFoundation\Response The response to be shown */ public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, array $aggregatorsData ) { $this->result = $result; $this->exportAlias = $exportAlias; $this->exportData = $exportData; $this->formatterData = $formatterData; - - $output = fopen('php://output', 'w'); - + + $output = fopen('php://output', 'wb'); + $this->prepareHeaders($output); - + $i = 1; + foreach ($result as $row) { - $line = array(); - - if ($this->formatterData['numerotation'] === true) { + $line = []; + + if (true === $this->formatterData['numerotation']) { $line[] = $i; } - + foreach ($row as $key => $value) { $line[] = $this->getLabel($key, $value); } - + fputcsv($output, $line); - - $i++; + + ++$i; } - + $csvContent = stream_get_contents($output); fclose($output); - + $response = new Response(); $response->setStatusCode(200); $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); //$response->headers->set('Content-Disposition','attachment; filename="export.csv"'); $response->setContent($csvContent); - + return $response; } - - /** - * add the headers to the csv file - * - * @param resource $output - */ - protected function prepareHeaders($output) + + public function getType() { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); - // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = count($this->result) > 0 ? $this->result[0] : array(); - $header_line = array(); - - if ($this->formatterData['numerotation'] === true) { - $header_line[] = $this->translator->trans('Number'); - } - - foreach ($first_row as $key => $value) { - $header_line[] = $this->translator->trans( - $this->getLabel($key, '_header')); - } - - if (count($header_line) > 0) { - fputcsv($output, $header_line); - } + return FormatterInterface::TYPE_LIST; } - + /** - * Give the label corresponding to the given key and value. - * + * Give the label corresponding to the given key and value. + * * @param string $key * @param string $value + * + * @throws LogicException if the label is not found + * * @return string - * @throws \LogicException if the label is not found */ protected function getLabel($key, $value) { - - if ($this->labelsCache === null) { + if (null === $this->labelsCache) { $this->prepareCacheLabels(); } - - if (!\array_key_exists($key, $this->labelsCache)){ - throw new \OutOfBoundsException(sprintf("The key \"%s\" " - . "is not present in the list of keys handled by " - . "this query. Check your `getKeys` and `getLabels` " - . "methods. Available keys are %s.", $key, - \implode(", ", \array_keys($this->labelsCache)))); + + if (!array_key_exists($key, $this->labelsCache)) { + throw new OutOfBoundsException(sprintf( + 'The key "%s" ' + . 'is not present in the list of keys handled by ' + . 'this query. Check your `getKeys` and `getLabels` ' + . 'methods. Available keys are %s.', + $key, + implode(', ', array_keys($this->labelsCache)) + )); } - + return $this->labelsCache[$key]($value); } - + /** * Prepare the label cache which will be used by getLabel. This function * should be called only once in the generation lifecycle. @@ -224,14 +195,39 @@ class CSVListFormatter implements FormatterInterface { $export = $this->exportManager->getExport($this->exportAlias); $keys = $export->getQueryKeys($this->exportData); - - foreach($keys as $key) { + + foreach ($keys as $key) { // get an array with all values for this key if possible - $values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result); + $values = array_map(static function ($v) use ($key) { return $v[$key]; }, $this->result); // store the label in the labelsCache property $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); } } - - + + /** + * add the headers to the csv file. + * + * @param resource $output + */ + protected function prepareHeaders($output) + { + $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + // we want to keep the order of the first row. So we will iterate on the first row of the results + $first_row = count($this->result) > 0 ? $this->result[0] : []; + $header_line = []; + + if (true === $this->formatterData['numerotation']) { + $header_line[] = $this->translator->trans('Number'); + } + + foreach ($first_row as $key => $value) { + $header_line[] = $this->translator->trans( + $this->getLabel($key, '_header') + ); + } + + if (count($header_line) > 0) { + fputcsv($output, $header_line); + } + } } diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php index 7d3087752..7e3334340 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php @@ -1,126 +1,109 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export\Formatter; -use Symfony\Component\HttpFoundation\Response; -use Chill\MainBundle\Export\FormatterInterface; -use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\FormatterInterface; +use LogicException; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Translation\TranslatorInterface; +use function array_map; +use function count; /** - * Create a CSV List for the export where the header are printed on the + * Create a CSV List for the export where the header are printed on the * first column, and the result goes from left to right. - * - * @author Champs-Libres */ class CSVPivotedListFormatter implements FormatterInterface { - + protected $exportAlias; + + protected $exportData; + /** - * This variable cache the labels internally - * - * @var string[] - */ - protected $labelsCache = null; - - protected $result = null; - - protected $exportAlias = null; - - protected $exportData = null; - - protected $formatterData = null; - - /** - * * @var ExportManager */ protected $exportManager; - + + protected $formatterData; + /** + * This variable cache the labels internally. * + * @var string[] + */ + protected $labelsCache; + + protected $result; + + /** * @var TranslatorInterface */ protected $translator; - - public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager) { $this->translator = $translatorInterface; $this->exportManager = $exportManager; } - - public function getType() - { - return FormatterInterface::TYPE_LIST; + + /** + * build a form, which will be used to collect data required for the execution + * of this formatter. + * + * @uses appendAggregatorForm + * + * @param type $exportAlias + */ + public function buildForm( + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ) { + $builder->add('numerotation', ChoiceType::class, [ + 'choices' => [ + 'yes' => true, + 'no' => false, + ], + 'expanded' => true, + 'multiple' => false, + 'label' => 'Add a number on first column', + 'data' => true, + ]); } - + public function getName() { return 'CSV horizontal list'; } - + /** - * build a form, which will be used to collect data required for the execution - * of this formatter. - * - * @uses appendAggregatorForm - * @param FormBuilderInterface $builder - * @param type $exportAlias - * @param array $aggregatorAliases - */ - public function buildForm( - FormBuilderInterface $builder, - $exportAlias, - array $aggregatorAliases - ){ - $builder->add('numerotation', ChoiceType::class, array( - 'choices' => array( - 'yes' => true, - 'no' => false - ), - 'expanded' => true, - 'multiple' => false, - 'label' => "Add a number on first column", - 'data' => true - )); - } - - /** - * Generate a response from the data collected on differents ExportElementInterface - * + * Generate a response from the data collected on differents ExportElementInterface. + * * @param mixed[] $result The result, as given by the ExportInterface * @param mixed[] $formatterData collected from the current form * @param string $exportAlias the id of the current export * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * * @return \Symfony\Component\HttpFoundation\Response The response to be shown */ public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, + $result, + $formatterData, + $exportAlias, + array $exportData, array $filtersData, array $aggregatorsData ) { @@ -128,85 +111,70 @@ class CSVPivotedListFormatter implements FormatterInterface $this->exportAlias = $exportAlias; $this->exportData = $exportData; $this->formatterData = $formatterData; - - $output = fopen('php://output', 'w'); - + + $output = fopen('php://output', 'wb'); + $i = 1; - $lines = array(); + $lines = []; $this->prepareHeaders($lines); - + foreach ($result as $row) { $j = 0; - - if ($this->formatterData['numerotation'] === true) { + + if (true === $this->formatterData['numerotation']) { $lines[$j][] = $i; - $j++; + ++$j; } - + foreach ($row as $key => $value) { $lines[$j][] = $this->getLabel($key, $value); - $j++; + ++$j; } - $i++; + ++$i; } - + //adding the lines to the csv output - foreach($lines as $line) { + foreach ($lines as $line) { fputcsv($output, $line); } - + $csvContent = stream_get_contents($output); fclose($output); - + $response = new Response(); $response->setStatusCode(200); $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - $response->headers->set('Content-Disposition','attachment; filename="export.csv"'); + $response->headers->set('Content-Disposition', 'attachment; filename="export.csv"'); $response->setContent($csvContent); - + return $response; } - - /** - * add the headers to lines array - * - * @param array $lines the lines where the header will be added - */ - protected function prepareHeaders(array &$lines) + + public function getType() { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); - // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = count($this->result) > 0 ? $this->result[0] : array(); - $header_line = array(); - - if ($this->formatterData['numerotation'] === true) { - $lines[] = array($this->translator->trans('Number')); - } - - foreach ($first_row as $key => $value) { - $lines[] = array($this->getLabel($key, '_header')); - } + return FormatterInterface::TYPE_LIST; } - + /** - * Give the label corresponding to the given key and value. - * + * Give the label corresponding to the given key and value. + * * @param string $key * @param string $value + * + * @throws LogicException if the label is not found + * * @return string - * @throws \LogicException if the label is not found */ protected function getLabel($key, $value) { - - if ($this->labelsCache === null) { + if (null === $this->labelsCache) { $this->prepareCacheLabels(); } - + return $this->labelsCache[$key]($value); } - + /** * Prepare the label cache which will be used by getLabel. This function * should be called only once in the generation lifecycle. @@ -215,14 +183,33 @@ class CSVPivotedListFormatter implements FormatterInterface { $export = $this->exportManager->getExport($this->exportAlias); $keys = $export->getQueryKeys($this->exportData); - - foreach($keys as $key) { + + foreach ($keys as $key) { // get an array with all values for this key if possible - $values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result); + $values = array_map(static function ($v) use ($key) { return $v[$key]; }, $this->result); // store the label in the labelsCache property $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); } } - - + + /** + * add the headers to lines array. + * + * @param array $lines the lines where the header will be added + */ + protected function prepareHeaders(array &$lines) + { + $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + // we want to keep the order of the first row. So we will iterate on the first row of the results + $first_row = count($this->result) > 0 ? $this->result[0] : []; + $header_line = []; + + if (true === $this->formatterData['numerotation']) { + $lines[] = [$this->translator->trans('Number')]; + } + + foreach ($first_row as $key => $value) { + $lines[] = [$this->getLabel($key, '_header')]; + } + } } diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php index d14ecb9ad..590e79a41 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php @@ -1,96 +1,43 @@ - * - * 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\MainBundle\Export\Formatter; - -use Symfony\Component\HttpFoundation\Response; -use Chill\MainBundle\Export\FormatterInterface; -use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Chill\MainBundle\Export\ExportManager; -use Symfony\Component\Form\Extension\Core\Type\FormType; -use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; /** + * Chill is a software for social workers * - * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Export\Formatter; + +use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\FormatterInterface; +use LogicException; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Translation\TranslatorInterface; +use function array_map; +use function array_merge; +use function array_multisort; +use function array_unique; +use function call_user_func; +use function count; +use function fopen; +use function is_array; +use function stream_get_contents; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; + class SpreadSheetFormatter implements FormatterInterface { - /** - * - * @var TranslatorInterface - */ - protected $translator; - - /** - * - * @var ExportManager - */ - protected $exportManager; - - /** - * The result, as returned by the export - * - * replaced when `getResponse` is called. - * - * @var type - */ - protected $result; - - /** - * - * replaced when `getResponse` is called. - * - * @var type - */ - protected $formatterData; - - /** - * The export - * - * replaced when `getResponse` is called. - * - * @var \Chill\MainBundle\Export\ExportInterface - */ - protected $export; - - /** - * - * replaced when `getResponse` is called. - * - * @var type - */ - //protected $aggregators; - - /** - * array containing value of export form - * - * replaced when `getResponse` is called. - * - * @var array - */ - protected $exportData; - /** * an array where keys are the aggregators aliases and - * values are the data + * values are the data. * * replaced when `getResponse` is called. * @@ -99,7 +46,36 @@ class SpreadSheetFormatter implements FormatterInterface protected $aggregatorsData; /** + * The export. * + * replaced when `getResponse` is called. + * + * @var \Chill\MainBundle\Export\ExportInterface + */ + protected $export; + + /** + * replaced when `getResponse` is called. + * + * @var type + */ + //protected $aggregators; + + /** + * array containing value of export form. + * + * replaced when `getResponse` is called. + * + * @var array + */ + protected $exportData; + + /** + * @var ExportManager + */ + protected $exportManager; + + /** * replaced when `getResponse` is called. * * @var array @@ -107,7 +83,22 @@ class SpreadSheetFormatter implements FormatterInterface protected $filtersData; /** + * replaced when `getResponse` is called. * + * @var type + */ + protected $formatterData; + + /** + * The result, as returned by the export. + * + * replaced when `getResponse` is called. + * + * @var type + */ + protected $result; + + /** * replaced when `getResponse` is called. * * @var array @@ -115,12 +106,17 @@ class SpreadSheetFormatter implements FormatterInterface //protected $labels; /** - * temporary file to store spreadsheet + * temporary file to store spreadsheet. * * @var string */ protected $tempfile; + /** + * @var TranslatorInterface + */ + protected $translator; + /** * cache for displayable result. * @@ -137,7 +133,7 @@ class SpreadSheetFormatter implements FormatterInterface /** * Whethe `cacheDisplayableResult` is initialized or not. * - * @var boolean + * @var bool */ private $cacheDisplayableResultIsInitialized = false; @@ -147,58 +143,35 @@ class SpreadSheetFormatter implements FormatterInterface $this->exportManager = $exportManager; } - public function buildForm( FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases ) { // choosing between formats - $builder->add('format', ChoiceType::class, array( - 'choices' => array( + $builder->add('format', ChoiceType::class, [ + 'choices' => [ 'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods', 'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx', - 'Comma separated values (.csv)' => 'csv' - ), - 'placeholder' => 'Choose the format' - )); + 'Comma separated values (.csv)' => 'csv', + ], + 'placeholder' => 'Choose the format', + ]); // ordering aggregators $aggregators = $this->exportManager->getAggregators($aggregatorAliases); $nb = count($aggregatorAliases); foreach ($aggregators as $alias => $aggregator) { - $builderAggregator = $builder->create($alias, FormType::class, array( - 'label' => $aggregator->getTitle(), - 'block_name' => '_aggregator_placement_spreadsheet_formatter' - )); + $builderAggregator = $builder->create($alias, FormType::class, [ + 'label' => $aggregator->getTitle(), + 'block_name' => '_aggregator_placement_spreadsheet_formatter', + ]); $this->appendAggregatorForm($builderAggregator, $nb); $builder->add($builderAggregator); } } - /** - * append a form line by aggregator on the formatter form. - * - * This form allow to choose the aggregator position (row or column) and - * the ordering - * - * @param FormBuilderInterface $builder - * @param string $nbAggregators - */ - private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) - { - $builder->add('order', ChoiceType::class, array( - 'choices' => array_combine( - range(1, $nbAggregators), - range(1, $nbAggregators) - ), - 'multiple' => false, - 'expanded' => false - )); - - } - public function getName() { return 'SpreadSheet (xlsx, ods)'; @@ -221,69 +194,123 @@ class SpreadSheetFormatter implements FormatterInterface $this->aggregatorsData = $aggregatorsData; // reset cache - $this->cacheDisplayableResult = array(); + $this->cacheDisplayableResult = []; $this->cacheDisplayableResultIsInitialized = false; $response = new Response(); - $response->headers->set('Content-Type', - $this->getContentType($this->formatterData['format'])); + $response->headers->set( + 'Content-Type', + $this->getContentType($this->formatterData['format']) + ); - $this->tempfile = \tempnam(\sys_get_temp_dir(), ''); - $this->generatecontent(); + $this->tempfile = tempnam(sys_get_temp_dir(), ''); + $this->generateContent(); - $f = \fopen($this->tempfile, 'r'); - $response->setContent(\stream_get_contents($f)); + $f = fopen($this->tempfile, 'rb'); + $response->setContent(stream_get_contents($f)); fclose($f); // remove the temp file from disk - \unlink($this->tempfile); + unlink($this->tempfile); return $response; } - /** - * Generate the content and write it to php://temp - */ - protected function generateContent() + public function getType() { - list($spreadsheet, $worksheet) = $this->createSpreadsheet(); + return 'tabular'; + } - $this->addTitleToWorkSheet($worksheet); - $line = $this->addFiltersDescription($worksheet); + protected function addContentTable( + Worksheet $worksheet, + $sortedResults, + $line + ) { + $worksheet->fromArray( + $sortedResults, + null, + 'A' . $line + ); - // at this point, we are going to sort retsults for an easier manipulation - list($sortedResult, $exportKeys, $aggregatorKeys, $globalKeys) = - $this->sortResult(); - - $line = $this->addHeaders($worksheet, $globalKeys, $line); - - $line = $this->addContentTable($worksheet, $sortedResult, $line); - - switch ($this->formatterData['format']) - { - case 'ods': - $writer = \PhpOffice\PhpSpreadsheet\IOFactory - ::createWriter($spreadsheet, 'Ods'); - break; - case 'xlsx': - $writer = \PhpOffice\PhpSpreadsheet\IOFactory - ::createWriter($spreadsheet, 'Xlsx'); - break; - case 'csv': - $writer = \PhpOffice\PhpSpreadsheet\IOFactory - ::createWriter($spreadsheet, 'Csv'); - break; - default: - // this should not happen - // throw an exception to ensure that the error is catched - throw new \LogicException(); - } - - $writer->save($this->tempfile); + return $line + count($sortedResults) + 1; } /** - * Create a spreadsheet and a working worksheet + * Add filter description since line 3. + * + * return the line number after the last description + * + * @return int the line number after the last description + */ + protected function addFiltersDescription(Worksheet &$worksheet) + { + $line = 3; + + foreach ($this->filtersData as $alias => $data) { + $filter = $this->exportManager->getFilter($alias); + $description = $filter->describeAction($data, 'string'); + + if (is_array($description)) { + $description = $this->translator + ->trans( + $description[0], + $description[1] ?? [] + ); + } + + $worksheet->setCellValue('A' . $line, $description); + ++$line; + } + + return $line; + } + + /** + * add headers to worksheet. + * + * return the line number where the next content (i.e. result) should + * be appended. + * + * @param int $line + * + * @return int + */ + protected function addHeaders( + Worksheet &$worksheet, + array $globalKeys, + $line + ) { + // get the displayable form of headers + $displayables = []; + + foreach ($globalKeys as $key) { + $displayables[] = $this->translator->trans( + $this->getDisplayableResult($key, '_header') + ); + } + + // add headers on worksheet + $worksheet->fromArray( + $displayables, + null, + 'A' . $line + ); + + return $line + 1; + } + + /** + * Add the title to the worksheet and merge the cell containing + * the title. + */ + protected function addTitleToWorkSheet(Worksheet &$worksheet) + { + $worksheet->setCellValue('A1', $this->getTitle()); + $worksheet->mergeCells('A1:G1'); + } + + /** + * Create a spreadsheet and a working worksheet. * * @return array where 1st member is spreadsheet, 2nd is worksheet */ @@ -301,46 +328,176 @@ class SpreadSheetFormatter implements FormatterInterface } /** - * Add the title to the worksheet and merge the cell containing - * the title - * - * @param Worksheet $worksheet + * Generate the content and write it to php://temp. */ - protected function addTitleToWorkSheet(Worksheet &$worksheet) + protected function generateContent() { - $worksheet->setCellValue('A1', $this->getTitle()); - $worksheet->mergeCells('A1:G1'); + [$spreadsheet, $worksheet] = $this->createSpreadsheet(); + + $this->addTitleToWorkSheet($worksheet); + $line = $this->addFiltersDescription($worksheet); + + // at this point, we are going to sort retsults for an easier manipulation + [$sortedResult, $exportKeys, $aggregatorKeys, $globalKeys] = + $this->sortResult(); + + $line = $this->addHeaders($worksheet, $globalKeys, $line); + + $line = $this->addContentTable($worksheet, $sortedResult, $line); + + switch ($this->formatterData['format']) { + case 'ods': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods'); + + break; + + case 'xlsx': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'); + + break; + + case 'csv': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Csv'); + + break; + + default: + // this should not happen + // throw an exception to ensure that the error is catched + throw new LogicException(); + } + + $writer->save($this->tempfile); } /** - * Add filter description since line 3. + * get an array of aggregator keys. The keys are sorted as asked + * by user in the form. * - * return the line number after the last description - * - * @param Worksheet $worksheet - * @return int the line number after the last description + * @return string[] an array containing the keys of aggregators */ - protected function addFiltersDescription(Worksheet &$worksheet) + protected function getAggregatorKeysSorted() { - $line = 3; + // empty array for aggregators keys + $keys = []; + // this association between key and aggregator alias will be used + // during sorting + $aggregatorKeyAssociation = []; - foreach ($this->filtersData as $alias => $data) { - $filter = $this->exportManager->getFilter($alias); - $description = $filter->describeAction($data, 'string'); - - if (is_array($description)) { - $description = $this->translator - ->trans( - $description[0], - isset($description[1]) ? $description[1] : [] - ); + foreach ($this->aggregatorsData as $alias => $data) { + $aggregator = $this->exportManager->getAggregator($alias); + $aggregatorsKeys = $aggregator->getQueryKeys($data); + // append the keys from aggregator to the $keys existing array + $keys = array_merge($keys, $aggregatorsKeys); + // append the key with the alias, which will be use later for sorting + foreach ($aggregatorsKeys as $key) { + $aggregatorKeyAssociation[$key] = $alias; } - - $worksheet->setCellValue('A'.$line, $description); - $line ++; } - return $line; + // sort the result using the form + usort($keys, function ($a, $b) use ($aggregatorKeyAssociation) { + $A = $this->formatterData[$aggregatorKeyAssociation[$a]]['order']; + $B = $this->formatterData[$aggregatorKeyAssociation[$b]]['order']; + + if ($A === $B) { + return 0; + } + + if ($A > $B) { + return 1; + } + + return -1; + }); + + return $keys; + } + + protected function getContentType($format) + { + switch ($format) { + case 'csv': + return 'text/csv'; + + case 'ods': + return 'application/vnd.oasis.opendocument.spreadsheet'; + + case 'xlsx': + return 'application/vnd.openxmlformats-officedocument.' + . 'spreadsheetml.sheet'; + } + } + + /** + * Get the displayable result. + * + * @param string $key + * @param string $value + * + * @return string + */ + protected function getDisplayableResult($key, $value) + { + if (false === $this->cacheDisplayableResultIsInitialized) { + $this->initializeCache($key); + } + + return call_user_func($this->cacheDisplayableResult[$key], $value); + } + + protected function getTitle() + { + return $this->translator->trans($this->export->getTitle()); + } + + protected function initializeCache($key) + { + /* + * this function follows the following steps : + * + * 1. associate all keys used in result with their export element + * (export or aggregator) and data; + * 2. associate all keys used in result with all the possible values : + * this array will be necessary to call `getLabels` + * 3. store the `callable` in an associative array, in cache + */ + // 1. create an associative array with key and export / aggregator + $keysExportElementAssociation = []; + // keys for export + foreach ($this->export->getQueryKeys($this->exportData) as $key) { + $keysExportElementAssociation[$key] = [$this->export, + $this->exportData, ]; + } + // keys for aggregator + foreach ($this->aggregatorsData as $alias => $data) { + $aggregator = $this->exportManager->getAggregator($alias); + + foreach ($aggregator->getQueryKeys($data) as $key) { + $keysExportElementAssociation[$key] = [$aggregator, $data]; + } + } + + // 2. collect all the keys before iteration + $keys = array_keys($keysExportElementAssociation); + + $allValues = []; + // store all the values in an array + foreach ($this->result as $row) { + foreach ($keys as $key) { + $allValues[$key][] = $row[$key]; + } + } + + // 3. iterate on `keysExportElementAssociation` to store the callable + // in cache + foreach ($keysExportElementAssociation as $key => [$element, $data]) { + $this->cacheDisplayableResult[$key] = + $element->getLabels($key, array_unique($allValues[$key]), $data); + } + + // the cache is initialized ! + $this->cacheDisplayableResultIsInitialized = true; } /** @@ -348,8 +505,7 @@ class SpreadSheetFormatter implements FormatterInterface * - 0 => sorted results * - 1 => export keys * - 2 => aggregator keys - * - 3 => global keys (aggregator keys and export keys) - * + * - 3 => global keys (aggregator keys and export keys). * * Example, assuming that the result contains two aggregator keys : * @@ -378,7 +534,6 @@ class SpreadSheetFormatter implements FormatterInterface * array( 5, 6, 4 ) * ) * ``` - * */ protected function sortResult() { @@ -386,198 +541,40 @@ class SpreadSheetFormatter implements FormatterInterface $exportKeys = $this->export->getQueryKeys($this->exportData); $aggregatorKeys = $this->getAggregatorKeysSorted(); - $globalKeys = \array_merge($aggregatorKeys, $exportKeys); + $globalKeys = array_merge($aggregatorKeys, $exportKeys); - $sortedResult = \array_map(function ($row) use ($globalKeys) { - $newRow = array(); + $sortedResult = array_map(function ($row) use ($globalKeys) { + $newRow = []; - foreach ($globalKeys as $key) { - $newRow[] = $this->getDisplayableResult($key, $row[$key]); - } + foreach ($globalKeys as $key) { + $newRow[] = $this->getDisplayableResult($key, $row[$key]); + } - return $newRow; - }, $this->result); + return $newRow; + }, $this->result); - \array_multisort($sortedResult); + array_multisort($sortedResult); - return array($sortedResult, $exportKeys, $aggregatorKeys, $globalKeys); + return [$sortedResult, $exportKeys, $aggregatorKeys, $globalKeys]; } /** - * get an array of aggregator keys. The keys are sorted as asked - * by user in the form. + * append a form line by aggregator on the formatter form. * - * @return string[] an array containing the keys of aggregators + * This form allow to choose the aggregator position (row or column) and + * the ordering + * + * @param string $nbAggregators */ - protected function getAggregatorKeysSorted() + private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) { - // empty array for aggregators keys - $keys = array(); - // this association between key and aggregator alias will be used - // during sorting - $aggregatorKeyAssociation = array(); - - foreach ($this->aggregatorsData as $alias => $data) { - $aggregator = $this->exportManager->getAggregator($alias); - $aggregatorsKeys = $aggregator->getQueryKeys($data); - // append the keys from aggregator to the $keys existing array - $keys = \array_merge($keys, $aggregatorsKeys); - // append the key with the alias, which will be use later for sorting - foreach ($aggregatorsKeys as $key) { - $aggregatorKeyAssociation[$key] = $alias; - } - } - - // sort the result using the form - usort($keys, function ($a, $b) use ($aggregatorKeyAssociation) { - $A = $this->formatterData[$aggregatorKeyAssociation[$a]]['order']; - $B = $this->formatterData[$aggregatorKeyAssociation[$b]]['order']; - - if ($A === $B) { - return 0; - } elseif ($A > $B) { - return 1; - } else { - return -1; - } - - }); - - return $keys; - } - - /** - * add headers to worksheet - * - * return the line number where the next content (i.e. result) should - * be appended. - * - * @param Worksheet $worksheet - * @param array $aggregatorKeys - * @param array $exportKeys - * @param int $line - * @return int - */ - protected function addHeaders( - Worksheet &$worksheet, - array $globalKeys, - $line - ) { - // get the displayable form of headers - $displayables = array(); - foreach ($globalKeys as $key) { - $displayables[] = $this->translator->trans( - $this->getDisplayableResult($key, '_header') - ); - } - - // add headers on worksheet - $worksheet->fromArray( - $displayables, - NULL, - 'A'.$line); - - return $line + 1; - } - - protected function addContentTable(Worksheet $worksheet, - $sortedResults, - $line - ) { - $worksheet->fromArray( - $sortedResults, - NULL, - 'A'.$line); - - return $line + count($sortedResults) + 1; - } - - protected function getTitle() - { - return $this->translator->trans($this->export->getTitle()); - } - - /** - * Get the displayable result. - * - * @param string $key - * @param string $value - * @return string - */ - protected function getDisplayableResult($key, $value) - { - if ($this->cacheDisplayableResultIsInitialized === false) { - $this->initializeCache($key); - } - - return call_user_func($this->cacheDisplayableResult[$key], $value); - } - - protected function initializeCache($key) - { - /* - * this function follows the following steps : - * - * 1. associate all keys used in result with their export element - * (export or aggregator) and data; - * 2. associate all keys used in result with all the possible values : - * this array will be necessary to call `getLabels` - * 3. store the `callable` in an associative array, in cache - */ - // 1. create an associative array with key and export / aggregator - $keysExportElementAssociation = array(); - // keys for export - foreach ($this->export->getQueryKeys($this->exportData) as $key) { - $keysExportElementAssociation[$key] = [$this->export, - $this->exportData]; - } - // keys for aggregator - foreach ($this->aggregatorsData as $alias => $data) { - $aggregator = $this->exportManager->getAggregator($alias); - - foreach ($aggregator->getQueryKeys($data) as $key) { - $keysExportElementAssociation[$key] = [$aggregator, $data]; - } - } - - // 2. collect all the keys before iteration - $keys = array_keys($keysExportElementAssociation); - - $allValues = array(); - // store all the values in an array - foreach ($this->result as $row) { - foreach ($keys as $key) { - $allValues[$key][] = $row[$key]; - } - } - - // 3. iterate on `keysExportElementAssociation` to store the callable - // in cache - foreach ($keysExportElementAssociation as $key => list($element, $data)) { - $this->cacheDisplayableResult[$key] = - $element->getLabels($key, \array_unique($allValues[$key]), $data); - } - - // the cache is initialized ! - $this->cacheDisplayableResultIsInitialized = true; - } - - protected function getContentType($format) - { - switch ($format) - { - case 'csv': - return 'text/csv'; - case 'ods': - return 'application/vnd.oasis.opendocument.spreadsheet'; - case 'xlsx': - return 'application/vnd.openxmlformats-officedocument.' - . 'spreadsheetml.sheet'; - } - } - - public function getType() - { - return 'tabular'; + $builder->add('order', ChoiceType::class, [ + 'choices' => array_combine( + range(1, $nbAggregators), + range(1, $nbAggregators) + ), + 'multiple' => false, + 'expanded' => false, + ]); } } diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php index 221375f0b..e31535602 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php @@ -1,163 +1,160 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export\Formatter; -use Symfony\Component\HttpFoundation\Response; -use Chill\MainBundle\Export\FormatterInterface; -use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Export\ExportManager; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use Chill\MainBundle\Export\FormatterInterface; +use DateTimeInterface; +use LogicException; +use OutOfBoundsException; use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Translation\TranslatorInterface; +use function array_key_exists; +use function array_keys; +use function array_map; +use function count; +use function fopen; +use function implode; +use function stream_get_contents; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; // command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff /** - * Create a CSV List for the export - * - * @author Champs-Libres + * Create a CSV List for the export. */ class SpreadsheetListFormatter implements FormatterInterface { + protected $exportAlias; + + protected $exportData; + /** - * This variable cache the labels internally - * - * @var string[] - */ - protected $labelsCache = null; - - protected $result = null; - - protected $exportAlias = null; - - protected $exportData = null; - - protected $formatterData = null; - - /** - * * @var ExportManager */ protected $exportManager; - + + protected $formatterData; + /** + * This variable cache the labels internally. * + * @var string[] + */ + protected $labelsCache; + + protected $result; + + /** * @var TranslatorInterface */ protected $translator; - - public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager) { $this->translator = $translatorInterface; $this->exportManager = $exportManager; } - - public function getType() - { - return FormatterInterface::TYPE_LIST; - } - + /** * build a form, which will be used to collect data required for the execution * of this formatter. - * + * * @uses appendAggregatorForm - * @param FormBuilderInterface $builder + * * @param type $exportAlias - * @param array $aggregatorAliases */ public function buildForm( - FormBuilderInterface $builder, - $exportAlias, + FormBuilderInterface $builder, + $exportAlias, array $aggregatorAliases - ){ + ) { $builder - ->add('format', ChoiceType::class, array( - 'choices' => array( + ->add('format', ChoiceType::class, [ + 'choices' => [ 'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods', - 'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx' - ), - 'placeholder' => 'Choose the format' - )) - ->add('numerotation', ChoiceType::class, array( - 'choices' => array( + 'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx', + ], + 'placeholder' => 'Choose the format', + ]) + ->add('numerotation', ChoiceType::class, [ + 'choices' => [ 'yes' => true, - 'no' => false - ), + 'no' => false, + ], 'expanded' => true, 'multiple' => false, - 'label' => "Add a number on first column", - 'data' => true - )); + 'label' => 'Add a number on first column', + 'data' => true, + ]); } - + public function getName() { return 'Spreadsheet list formatter (.xlsx, .ods)'; } - + /** - * Generate a response from the data collected on differents ExportElementInterface - * + * Generate a response from the data collected on differents ExportElementInterface. + * * @param mixed[] $result The result, as given by the ExportInterface * @param mixed[] $formatterData collected from the current form * @param string $exportAlias the id of the current export * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * * @return \Symfony\Component\HttpFoundation\Response The response to be shown */ public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, array $aggregatorsData ) { $this->result = $result; $this->exportAlias = $exportAlias; $this->exportData = $exportData; $this->formatterData = $formatterData; - + $spreadsheet = new Spreadsheet(); $worksheet = $spreadsheet->getActiveSheet(); - + $this->prepareHeaders($worksheet); - + $i = 1; + foreach ($result as $row) { - $line = array(); - - if ($this->formatterData['numerotation'] === true) { - $worksheet->setCellValue('A'.($i+1), (string) $i); + $line = []; + + if (true === $this->formatterData['numerotation']) { + $worksheet->setCellValue('A' . ($i + 1), (string) $i); } - + $a = $this->formatterData['numerotation'] ? 'B' : 'A'; + foreach ($row as $key => $value) { - $row = $a.($i+1); - if ($value instanceof \DateTimeInterface) { + $row = $a . ($i + 1); + + if ($value instanceof DateTimeInterface) { $worksheet->setCellValue($row, Date::PHPToExcel($value)); $worksheet->getStyle($row) ->getNumberFormat() @@ -165,105 +162,90 @@ class SpreadsheetListFormatter implements FormatterInterface } else { $worksheet->setCellValue($row, $this->getLabel($key, $value)); } - $a ++; + ++$a; } - - $i++; + + ++$i; } - switch ($this->formatterData['format']) - { + switch ($this->formatterData['format']) { case 'ods': - $writer = \PhpOffice\PhpSpreadsheet\IOFactory - ::createWriter($spreadsheet, 'Ods'); - $contentType = "application/vnd.oasis.opendocument.spreadsheet"; + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods'); + $contentType = 'application/vnd.oasis.opendocument.spreadsheet'; + break; + case 'xlsx': - $writer = \PhpOffice\PhpSpreadsheet\IOFactory - ::createWriter($spreadsheet, 'Xlsx'); + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'); $contentType = 'application/vnd.openxmlformats-officedocument.' . 'spreadsheetml.sheet'; + break; + case 'csv': - $writer = \PhpOffice\PhpSpreadsheet\IOFactory - ::createWriter($spreadsheet, 'Csv'); + $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Csv'); $contentType = 'text/csv'; + break; + default: // this should not happen // throw an exception to ensure that the error is catched - throw new \OutOfBoundsException("The format ".$this->formatterData['format']. - " is not supported"); + throw new OutOfBoundsException('The format ' . $this->formatterData['format'] . + ' is not supported'); } - + $response = new Response(); $response->headers->set('content-type', $contentType); - - $tempfile = \tempnam(\sys_get_temp_dir(), ''); + + $tempfile = tempnam(sys_get_temp_dir(), ''); $writer->save($tempfile); - - $f = \fopen($tempfile, 'r'); - $response->setContent(\stream_get_contents($f)); + + $f = fopen($tempfile, 'rb'); + $response->setContent(stream_get_contents($f)); fclose($f); // remove the temp file from disk - \unlink($tempfile); - + unlink($tempfile); + return $response; } - - /** - * add the headers to the csv file - * - * @param Worksheet $worksheet - */ - protected function prepareHeaders(Worksheet $worksheet) + + public function getType() { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); - // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = count($this->result) > 0 ? $this->result[0] : array(); - $header_line = array(); - - if ($this->formatterData['numerotation'] === true) { - $header_line[] = $this->translator->trans('Number'); - } - - foreach ($first_row as $key => $value) { - $header_line[] = $this->translator->trans( - $this->getLabel($key, '_header')); - } - - if (count($header_line) > 0) { - $worksheet->fromArray($header_line, NULL, 'A1'); - } + return FormatterInterface::TYPE_LIST; } - + /** - * Give the label corresponding to the given key and value. - * + * Give the label corresponding to the given key and value. + * * @param string $key * @param string $value + * + * @throws LogicException if the label is not found + * * @return string - * @throws \LogicException if the label is not found */ protected function getLabel($key, $value) { - - if ($this->labelsCache === null) { + if (null === $this->labelsCache) { $this->prepareCacheLabels(); } - - if (!\array_key_exists($key, $this->labelsCache)){ - throw new \OutOfBoundsException(sprintf("The key \"%s\" " - . "is not present in the list of keys handled by " - . "this query. Check your `getKeys` and `getLabels` " - . "methods. Available keys are %s.", $key, - \implode(", ", \array_keys($this->labelsCache)))); + + if (!array_key_exists($key, $this->labelsCache)) { + throw new OutOfBoundsException(sprintf( + 'The key "%s" ' + . 'is not present in the list of keys handled by ' + . 'this query. Check your `getKeys` and `getLabels` ' + . 'methods. Available keys are %s.', + $key, + implode(', ', array_keys($this->labelsCache)) + )); } - + return $this->labelsCache[$key]($value); } - + /** * Prepare the label cache which will be used by getLabel. This function * should be called only once in the generation lifecycle. @@ -272,14 +254,37 @@ class SpreadsheetListFormatter implements FormatterInterface { $export = $this->exportManager->getExport($this->exportAlias); $keys = $export->getQueryKeys($this->exportData); - - foreach($keys as $key) { + + foreach ($keys as $key) { // get an array with all values for this key if possible - $values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result); + $values = array_map(static function ($v) use ($key) { return $v[$key]; }, $this->result); // store the label in the labelsCache property $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); } } - - + + /** + * add the headers to the csv file. + */ + protected function prepareHeaders(Worksheet $worksheet) + { + $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + // we want to keep the order of the first row. So we will iterate on the first row of the results + $first_row = count($this->result) > 0 ? $this->result[0] : []; + $header_line = []; + + if (true === $this->formatterData['numerotation']) { + $header_line[] = $this->translator->trans('Number'); + } + + foreach ($first_row as $key => $value) { + $header_line[] = $this->translator->trans( + $this->getLabel($key, '_header') + ); + } + + if (count($header_line) > 0) { + $worksheet->fromArray($header_line, null, 'A1'); + } + } } diff --git a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php index c507a594f..2e4e569fc 100644 --- a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php +++ b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php @@ -1,71 +1,60 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export; use Symfony\Component\Form\FormBuilderInterface; -/** - * - * @author Julien Fastré - */ interface FormatterInterface { - const TYPE_TABULAR = 'tabular'; - const TYPE_LIST = 'list'; - - public function getType(); - - public function getName(); - + public const TYPE_LIST = 'list'; + + public const TYPE_TABULAR = 'tabular'; + /** * build a form, which will be used to collect data required for the execution * of this formatter. - * + * * @uses appendAggregatorForm - * @param FormBuilderInterface $builder - * @param String $exportAlias Alias of the export which is being executed. An export gets the data and implements the \Chill\MainBundle\Export\ExportInterface + * + * @param string $exportAlias Alias of the export which is being executed. An export gets the data and implements the \Chill\MainBundle\Export\ExportInterface * @param Array(String) $aggregatorAliases Array of the aliases of the aggregators. An aggregator do the "group by" on the data. $aggregatorAliases */ public function buildForm( - FormBuilderInterface $builder, - $exportAlias, - array $aggregatorAliases - ); - + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ); + + public function getName(); + /** - * Generate a response from the data collected on differents ExportElementInterface - * + * Generate a response from the data collected on differents ExportElementInterface. + * * @param mixed[] $result The result, as given by the ExportInterface * @param mixed[] $formatterData collected from the current form * @param string $exportAlias the id of the current export * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * * @return \Symfony\Component\HttpFoundation\Response The response to be shown */ public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData - ); - + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData + ); + + public function getType(); } diff --git a/src/Bundle/ChillMainBundle/Export/GroupedExportInterface.php b/src/Bundle/ChillMainBundle/Export/GroupedExportInterface.php index 9330fd32b..6a0e7722a 100644 --- a/src/Bundle/ChillMainBundle/Export/GroupedExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/GroupedExportInterface.php @@ -1,14 +1,21 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Export; use Doctrine\ORM\QueryBuilder; /** * Modifiers modify the export's query. - * - * Known subclasses : AggregatorInterface and FilterInterface * - * @author Julien Fastré + * Known subclasses : AggregatorInterface and FilterInterface */ interface ModifierInterface extends ExportElementInterface { /** - * The role required for executing this Modifier - * + * The role required for executing this Modifier. + * * If null, will used the ExportInterface::requiredRole role from * the current executing export. - * - * @return NULL|\Symfony\Component\Security\Core\Role\Role A role required to execute this ModifiersInterface + * + * @return \Symfony\Component\Security\Core\Role\Role|null A role required to execute this ModifiersInterface */ public function addRole(); - + /** - * On which type of Export this ModifiersInterface may apply. - * - * @return string the type on which the Modifiers apply - */ - public function applyOn(); - - /** - * Alter the query initiated by the export, to add the required statements - * (`GROUP BY`, `SELECT`, `WHERE`) - * + * Alter the query initiated by the export, to add the required statements + * (`GROUP BY`, `SELECT`, `WHERE`). + * * @param QueryBuilder $qb the QueryBuilder initiated by the Export (and eventually modified by other Modifiers) * @param mixed[] $data the data from the Form (builded by buildForm) */ public function alterQuery(QueryBuilder $qb, $data); + + /** + * On which type of Export this ModifiersInterface may apply. + * + * @return string the type on which the Modifiers apply + */ + public function applyOn(); } diff --git a/src/Bundle/ChillMainBundle/Form/AdvancedSearchType.php b/src/Bundle/ChillMainBundle/Form/AdvancedSearchType.php index a70a34b31..e1d94728e 100644 --- a/src/Bundle/ChillMainBundle/Form/AdvancedSearchType.php +++ b/src/Bundle/ChillMainBundle/Form/AdvancedSearchType.php @@ -1,59 +1,44 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form; +use Chill\MainBundle\Search\SearchProvider; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\MainBundle\Search\SearchProvider; -/** - * - * - * @author Julien Fastré - */ class AdvancedSearchType extends AbstractType { /** - * * @var SearchProvider */ protected $searchProvider; - + public function __construct(SearchProvider $searchProvider) { $this->searchProvider = $searchProvider; } - public function buildForm(FormBuilderInterface $builder, array $options) { $this->searchProvider ->getHasAdvancedFormByName($options['search_service']) - ->createSearchForm($builder) - ; + ->createSearchForm($builder); } - + public function configureOptions(OptionsResolver $resolver) { $resolver ->setRequired('search_service') - ->setAllowedTypes('search_service', [ 'string' ]) - ; + ->setAllowedTypes('search_service', ['string']); } } diff --git a/src/Bundle/ChillMainBundle/Form/CenterType.php b/src/Bundle/ChillMainBundle/Form/CenterType.php index 9ab037304..494df50e9 100644 --- a/src/Bundle/ChillMainBundle/Form/CenterType.php +++ b/src/Bundle/ChillMainBundle/Form/CenterType.php @@ -1,23 +1,27 @@ add('name', TextType::class) - ; + ->add('name', TextType::class); } /** @@ -25,17 +29,16 @@ class CenterType extends AbstractType */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( - 'data_class' => 'Chill\MainBundle\Entity\Center' - )); + $resolver->setDefaults([ + 'data_class' => 'Chill\MainBundle\Entity\Center', + ]); } /** * @return string */ - public function getBlockPrefix() + public function getBlockPrefix() { - return 'chill_mainbundle_center'; - } - + return 'chill_mainbundle_center'; + } } diff --git a/src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php b/src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php index dfc2caa1f..7e0702de1 100644 --- a/src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php +++ b/src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php @@ -1,82 +1,68 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\ChoiceLoader; -use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; -use Symfony\Component\Form\ChoiceList\ChoiceListInterface; -use Chill\MainBundle\Repository\PostalCodeRepository; use Chill\MainBundle\Entity\PostalCode; +use Chill\MainBundle\Repository\PostalCodeRepository; +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use function call_user_func; /** - * Class PostalCodeChoiceLoader - * - * @package Chill\MainBundle\Form\ChoiceLoader - * @author Julien Fastré + * Class PostalCodeChoiceLoader. */ class PostalCodeChoiceLoader implements ChoiceLoaderInterface { - /** - * @var PostalCodeRepository - */ - protected $postalCodeRepository; - /** * @var array */ protected $lazyLoadedPostalCodes = []; - + + /** + * @var PostalCodeRepository + */ + protected $postalCodeRepository; + /** * PostalCodeChoiceLoader constructor. - * - * @param PostalCodeRepository $postalCodeRepository */ public function __construct(PostalCodeRepository $postalCodeRepository) { $this->postalCodeRepository = $postalCodeRepository; } - + /** * @param null $value - * @return ChoiceListInterface */ public function loadChoiceList($value = null): ChoiceListInterface { - $list = new \Symfony\Component\Form\ChoiceList\ArrayChoiceList( - $this->lazyLoadedPostalCodes, - function(PostalCode $pc = null) use ($value) { - return \call_user_func($value, $pc); - }); - - return $list; + return new \Symfony\Component\Form\ChoiceList\ArrayChoiceList( + $this->lazyLoadedPostalCodes, + static function (?PostalCode $pc = null) use ($value) { + return call_user_func($value, $pc); + } + ); } - + /** - * @param array $values * @param null $value + * * @return array */ public function loadChoicesForValues(array $values, $value = null) { $choices = []; - - foreach($values as $value) { + + foreach ($values as $value) { if (empty($value)) { $choices[] = null; } else { @@ -86,27 +72,28 @@ class PostalCodeChoiceLoader implements ChoiceLoaderInterface return $choices; } - + /** - * @param array $choices * @param null $value + * * @return array|string[] */ public function loadValuesForChoices(array $choices, $value = null) { $values = []; - + foreach ($choices as $choice) { - if (NULL === $choice) { + if (null === $choice) { $values[] = null; + continue; } - - $id = \call_user_func($value, $choice); + + $id = call_user_func($value, $choice); $values[] = $id; $this->lazyLoadedPostalCodes[$id] = $choice; } - + return $values; } } diff --git a/src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php b/src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php index 36383f7e3..73a61c998 100644 --- a/src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php +++ b/src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php @@ -1,68 +1,72 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form\DataMapper; -use Symfony\Component\Form\DataMapperInterface; use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\PostalCode; -use Symfony\Component\Form\FormInterface; +use Iterator; +use Symfony\Component\Form\DataMapperInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\FormInterface; /** * Add a data mapper to Address. - * + * * If the address is incomplete, the data mapper returns null */ class AddressDataMapper implements DataMapperInterface { /** - * * @param Address $address - * @param \Iterator $forms + * @param Iterator $forms */ public function mapDataToForms($address, $forms) { - if (NULL === $address) { + if (null === $address) { return; } - + if (!$address instanceof Address) { throw new UnexpectedTypeException($address, Address::class); } - + foreach ($forms as $key => $form) { /** @var FormInterface $form */ switch ($key) { case 'streetAddress1': $form->setData($address->getStreetAddress1()); + break; + case 'streetAddress2': $form->setData($address->getStreetAddress2()); + break; + case 'postCode': $form->setData($address->getPostcode()); + break; + case 'validFrom': $form->setData($address->getValidFrom()); + break; + case 'isNoAddress': $form->setData($address->isNoAddress()); + break; + default: break; } @@ -70,8 +74,7 @@ class AddressDataMapper implements DataMapperInterface } /** - * - * @param \Iterator $forms + * @param Iterator $forms * @param Address $address */ public function mapFormsToData($forms, &$address) @@ -79,40 +82,53 @@ class AddressDataMapper implements DataMapperInterface if (!$address instanceof Address) { $address = new Address(); } - + $isNoAddress = false; + foreach ($forms as $key => $form) { - if ($key === 'isNoAddress') { + if ('isNoAddress' === $key) { $isNoAddress = $form->get('isNoAddress')->getData(); } } - + foreach ($forms as $key => $form) { /** @var FormInterface $form */ - switch($key) { + switch ($key) { case 'postCode': if (!$form->getData() instanceof PostalCode && !$isNoAddress) { $address = null; + return; } $address->setPostcode($form->getData()); + break; + case 'streetAddress1': if (empty($form->getData()) && !$isNoAddress) { $address = null; + return; } $address->setStreetAddress1($form->getData()); + break; + case 'streetAddress2': $address->setStreetAddress2($form->getData()); + break; + case 'validFrom': $address->setValidFrom($form->getData()); + break; + case 'isNoAddress': $address->setIsNoAddress($form->getData()); + break; + default: break; } diff --git a/src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php b/src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php index 816f62827..7284b9b3a 100644 --- a/src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php +++ b/src/Bundle/ChillMainBundle/Form/DataMapper/ScopePickerDataMapper.php @@ -1,10 +1,18 @@ scope = $scope; } @@ -24,6 +32,7 @@ class ScopePickerDataMapper implements DataMapperInterface if ($this->scope instanceof Scope) { $forms['scope']->setData($this->scope); + return; } diff --git a/src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php b/src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php index 43fab6f62..776a8d80c 100644 --- a/src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php +++ b/src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php @@ -1,36 +1,39 @@ type = $type; $this->builder = $builder; } - /** - * @return string - */ - public function getType(): string - { - return $this->type; - } - - /** - * @return FormBuilderInterface - */ public function getBuilder(): FormBuilderInterface { return $this->builder; } + + public function getType(): string + { + return $this->type; + } } diff --git a/src/Bundle/ChillMainBundle/Form/LocationFormType.php b/src/Bundle/ChillMainBundle/Form/LocationFormType.php index 83d1c4242..ecb4a76ea 100644 --- a/src/Bundle/ChillMainBundle/Form/LocationFormType.php +++ b/src/Bundle/ChillMainBundle/Form/LocationFormType.php @@ -1,5 +1,14 @@ translatableStringHelper = $translatableStringHelper; - // } + public function __construct(TranslatableStringHelper $translatableStringHelper) + { + $this->translatableStringHelper = $translatableStringHelper; + } public function buildForm(FormBuilderInterface $builder, array $options) { - $builder ->add('locationType', EntityType::class, [ 'class' => EntityLocationType::class, - 'choice_attr' => function (EntityLocationType $entity) { + 'choice_attr' => static function (EntityLocationType $entity) { return [ 'data-address' => $entity->getAddressRequired(), 'data-contact' => $entity->getContactData(), ]; }, 'choice_label' => function (EntityLocationType $entity) { - //return $this->translatableStringHelper->localize($entity->getTitle()); //TODO not working. Cannot pass smthg in the constructor - return $entity->getTitle()['fr']; + return $this->translatableStringHelper->localize($entity->getTitle()); }, ]) ->add('name', TextType::class) @@ -53,25 +56,27 @@ final class LocationFormType extends AbstractType 'use_valid_to' => false, 'mapped' => false, ]) - ->add('active', ChoiceType::class, - [ - 'choices' => [ - 'Yes' => true, - 'No' => false - ], - 'expanded' => true - ]); + ->add( + 'active', + ChoiceType::class, + [ + 'choices' => [ + 'Yes' => true, + 'No' => false, + ], + 'expanded' => true, + ] + ); } - /** * @param OptionsResolverInterface $resolver */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( - 'data_class' => 'Chill\MainBundle\Entity\Location' - )); + $resolver->setDefaults([ + 'data_class' => 'Chill\MainBundle\Entity\Location', + ]); } /** @@ -81,5 +86,4 @@ final class LocationFormType extends AbstractType { return 'chill_mainbundle_location'; } - } diff --git a/src/Bundle/ChillMainBundle/Form/LocationTypeType.php b/src/Bundle/ChillMainBundle/Form/LocationTypeType.php index dded13327..aa096b3e6 100644 --- a/src/Bundle/ChillMainBundle/Form/LocationTypeType.php +++ b/src/Bundle/ChillMainBundle/Form/LocationTypeType.php @@ -1,5 +1,14 @@ add('title', TranslatableStringFormType::class, - [ - 'label' => 'Name', - ]) - ->add('availableForUsers', ChoiceType::class, - [ - 'choices' => [ - 'Yes' => true, - 'No' => false - ], - 'expanded' => true - ]) - ->add('addressRequired', ChoiceType::class, - [ - 'choices' => [ - 'optional' => LocationType::STATUS_OPTIONAL, - 'required' => LocationType::STATUS_REQUIRED, - 'never' => LocationType::STATUS_NEVER, - ], - 'expanded' => true - ]) - ->add('contactData', ChoiceType::class, - [ - 'choices' => [ - 'optional' => LocationType::STATUS_OPTIONAL, - 'required' => LocationType::STATUS_REQUIRED, - 'never' => LocationType::STATUS_NEVER, - ], - 'expanded' => true - ]) - ->add('active', ChoiceType::class, - [ - 'choices' => [ - 'Yes' => true, - 'No' => false - ], - 'expanded' => true - ]); + $builder->add( + 'title', + TranslatableStringFormType::class, + [ + 'label' => 'Name', + ] + ) + ->add( + 'availableForUsers', + ChoiceType::class, + [ + 'choices' => [ + 'Yes' => true, + 'No' => false, + ], + 'expanded' => true, + ] + ) + ->add( + 'addressRequired', + ChoiceType::class, + [ + 'choices' => [ + 'optional' => LocationType::STATUS_OPTIONAL, + 'required' => LocationType::STATUS_REQUIRED, + 'never' => LocationType::STATUS_NEVER, + ], + 'expanded' => true, + ] + ) + ->add( + 'contactData', + ChoiceType::class, + [ + 'choices' => [ + 'optional' => LocationType::STATUS_OPTIONAL, + 'required' => LocationType::STATUS_REQUIRED, + 'never' => LocationType::STATUS_NEVER, + ], + 'expanded' => true, + ] + ) + ->add( + 'active', + ChoiceType::class, + [ + 'choices' => [ + 'Yes' => true, + 'No' => false, + ], + 'expanded' => true, + ] + ) + ->add( + 'defaultFor', + ChoiceType::class, + [ + 'choices' => [ + 'none' => null, + 'person' => LocationType::DEFAULT_FOR_PERSON, + 'thirdparty' => LocationType::DEFAULT_FOR_3PARTY, + ], + 'expanded' => true, + ] + ); } } diff --git a/src/Bundle/ChillMainBundle/Form/PermissionsGroupType.php b/src/Bundle/ChillMainBundle/Form/PermissionsGroupType.php index d9b4ab730..e798a262e 100644 --- a/src/Bundle/ChillMainBundle/Form/PermissionsGroupType.php +++ b/src/Bundle/ChillMainBundle/Form/PermissionsGroupType.php @@ -1,75 +1,66 @@ add('name', TextType::class) - ; - - $flags = $this->getFlags(); - - if (count($flags) > 0) { - $builder - ->add('flags', ChoiceType::class, [ - 'choices' => \array_combine($flags, $flags), - 'multiple' => true, - 'expanded' => true, - 'required' => false - ]); - } - } - - /** - * - * @return array - */ - protected function getFlags(): array - { - $flags = []; - - foreach ($this->flagProviders as $flagProvider) { - $flags = \array_merge($flags, $flagProvider->getPermissionsGroupFlags()); - } - - return $flags; - } - + public function addFlagProvider(PermissionsGroupFlagProvider $provider) { $this->flagProviders[] = $provider; } + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('name', TextType::class); + + $flags = $this->getFlags(); + + if (count($flags) > 0) { + $builder + ->add('flags', ChoiceType::class, [ + 'choices' => array_combine($flags, $flags), + 'multiple' => true, + 'expanded' => true, + 'required' => false, + ]); + } + } + /** * @param OptionsResolverInterface $resolver */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( - 'data_class' => 'Chill\MainBundle\Entity\PermissionsGroup' - )); + $resolver->setDefaults([ + 'data_class' => 'Chill\MainBundle\Entity\PermissionsGroup', + ]); } /** @@ -79,4 +70,15 @@ class PermissionsGroupType extends AbstractType { return 'chill_mainbundle_permissionsgroup'; } + + protected function getFlags(): array + { + $flags = []; + + foreach ($this->flagProviders as $flagProvider) { + $flags = array_merge($flags, $flagProvider->getPermissionsGroupFlags()); + } + + return $flags; + } } diff --git a/src/Bundle/ChillMainBundle/Form/ScopeType.php b/src/Bundle/ChillMainBundle/Form/ScopeType.php index 12eb9fec4..c0c08ffbf 100644 --- a/src/Bundle/ChillMainBundle/Form/ScopeType.php +++ b/src/Bundle/ChillMainBundle/Form/ScopeType.php @@ -1,23 +1,27 @@ add('name', TranslatableStringFormType::class) - ; + ->add('name', TranslatableStringFormType::class); } /** @@ -25,9 +29,9 @@ class ScopeType extends AbstractType */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( - 'data_class' => 'Chill\MainBundle\Entity\Scope' - )); + $resolver->setDefaults([ + 'data_class' => 'Chill\MainBundle\Entity\Scope', + ]); } /** diff --git a/src/Bundle/ChillMainBundle/Form/Type/AddressType.php b/src/Bundle/ChillMainBundle/Form/Type/AddressType.php index f17721881..24872650f 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/AddressType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/AddressType.php @@ -1,36 +1,26 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Chill\MainBundle\Entity\Address; -use Chill\MainBundle\Form\Type\PostalCodeType; -use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\DataMapper\AddressDataMapper; +use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; /** - * A type to create/update Address entity + * A type to create/update Address entity. * * Options: * @@ -45,25 +35,27 @@ class AddressType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('street', TextType::class, array( - 'required' => !$options['has_no_address'] // true if has no address is false - )) - ->add('streetNumber', TextType::class, array( - 'required' => false - )) - ->add('postCode', PostalCodeType::class, array( - 'label' => 'Postal code', - 'placeholder' => 'Choose a postal code', - 'required' => !$options['has_no_address'] // true if has no address is false - )) - ; + ->add('street', TextType::class, [ + 'required' => !$options['has_no_address'], // true if has no address is false + ]) + ->add('streetNumber', TextType::class, [ + 'required' => false, + ]) + ->add('postCode', PostalCodeType::class, [ + 'label' => 'Postal code', + 'placeholder' => 'Choose a postal code', + 'required' => !$options['has_no_address'], // true if has no address is false + ]); if ($options['has_valid_from']) { $builder - ->add('validFrom', ChillDateType::class, array( - 'required' => true, - ) - ); + ->add( + 'validFrom', + ChillDateType::class, + [ + 'required' => true, + ] + ); } if ($options['has_no_address']) { @@ -72,13 +64,13 @@ class AddressType extends AbstractType 'required' => true, 'choices' => [ 'address.consider homeless' => true, - 'address.real address' => false + 'address.real address' => false, ], - 'label' => 'address.address_homeless' + 'label' => 'address.address_homeless', ]); } - if ($options['null_if_empty'] === TRUE) { + if (true === $options['null_if_empty']) { $builder->setDataMapper(new AddressDataMapper()); } } @@ -95,7 +87,6 @@ class AddressType extends AbstractType ->setAllowedTypes('has_no_address', 'bool') ->setDefined('null_if_empty') ->setDefault('null_if_empty', false) - ->setAllowedTypes('null_if_empty', 'bool') - ; + ->setAllowedTypes('null_if_empty', 'bool'); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/AppendScopeChoiceTypeTrait.php b/src/Bundle/ChillMainBundle/Form/Type/AppendScopeChoiceTypeTrait.php index 8de9134f1..3391fc845 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/AppendScopeChoiceTypeTrait.php +++ b/src/Bundle/ChillMainBundle/Form/Type/AppendScopeChoiceTypeTrait.php @@ -1,37 +1,28 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Form\Type\DataTransformer\ScopeTransformer; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\Persistence\ObjectManager; -use Symfony\Component\Form\FormEvents; -use Symfony\Component\Form\FormEvent; -use Chill\MainBundle\Entity\User; -use Chill\MainBundle\Entity\Center; -use Symfony\Component\Security\Core\Role\Role; -use Chill\MainBundle\Form\Type\DataTransformer\ScopeTransformer; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Role\Role; /** * Trait to add an input with reachable scope for a given center and role. @@ -78,75 +69,72 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; * * } * ``` - * - * @author Julien Fastré - * @author Champs Libres */ trait AppendScopeChoiceTypeTrait { - /** - * Append a scope choice field, with the scopes reachable by given - * user for the given role and center. - * - * The field is added on event FormEvents::PRE_SET_DATA - * - * @param FormBuilderInterface $builder - * @param Role $role - * @param Center $center - * @param User $user - * @param AuthorizationHelper $authorizationHelper - * @param TranslatableStringHelper $translatableStringHelper - * @param string $name - */ - protected function appendScopeChoices(FormBuilderInterface $builder, - Role $role, Center $center, User $user, - AuthorizationHelper $authorizationHelper, - TranslatableStringHelper $translatableStringHelper, - ObjectManager $om, $name = 'scope') - { - $reachableScopes = $authorizationHelper - ->getReachableScopes($user, $role, $center); - - $choices = array(); - foreach($reachableScopes as $scope) { - $choices[$scope->getId()] = $translatableStringHelper - ->localize($scope->getName()); - } - - $dataTransformer = new ScopeTransformer($om); - - $builder->addEventListener(FormEvents::PRE_SET_DATA, - function (FormEvent $event) use ($choices, $name, $dataTransformer, $builder) { - $form = $event->getForm(); - $form->add( - $builder - ->create($name, ChoiceType::class, array( - 'choices' => array_combine(array_values($choices),array_keys($choices)), - 'auto_initialize' => false - ) - ) - ->addModelTransformer($dataTransformer) - ->getForm() - ); - }); - } - /** * Append a `role` and `center` option to the form. * * The allowed types are : * - Chill\MainBundle\Entity\Center for center * - Symfony\Component\Security\Core\Role\Role for role - * - * @param OptionsResolver $resolver */ public function appendScopeChoicesOptions(OptionsResolver $resolver) { $resolver - ->setRequired(array('center', 'role')) - ->setAllowedTypes('center', 'Chill\MainBundle\Entity\Center') - ->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role') - ; + ->setRequired(['center', 'role']) + ->setAllowedTypes('center', 'Chill\MainBundle\Entity\Center') + ->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role'); } + /** + * Append a scope choice field, with the scopes reachable by given + * user for the given role and center. + * + * The field is added on event FormEvents::PRE_SET_DATA + * + * @param string $name + */ + protected function appendScopeChoices( + FormBuilderInterface $builder, + Role $role, + Center $center, + User $user, + AuthorizationHelper $authorizationHelper, + TranslatableStringHelper $translatableStringHelper, + ObjectManager $om, + $name = 'scope' + ) { + $reachableScopes = $authorizationHelper + ->getReachableScopes($user, $role, $center); + + $choices = []; + + foreach ($reachableScopes as $scope) { + $choices[$scope->getId()] = $translatableStringHelper + ->localize($scope->getName()); + } + + $dataTransformer = new ScopeTransformer($om); + + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + static function (FormEvent $event) use ($choices, $name, $dataTransformer, $builder) { + $form = $event->getForm(); + $form->add( + $builder + ->create( + $name, + ChoiceType::class, + [ + 'choices' => array_combine(array_values($choices), array_keys($choices)), + 'auto_initialize' => false, + ] + ) + ->addModelTransformer($dataTransformer) + ->getForm() + ); + } + ); + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php index 2b20dbd98..88d5d3ce0 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php @@ -1,40 +1,42 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormInterface; /** - * Available options : + * Available options :. * * - `button_add_label` * - `button_remove_label` * - `identifier`: an identifier to identify the kind of collecton. Useful if some * javascript should be launched associated to `add_entry`, `remove_entry` events. * - `empty_collection_explain` - * - * */ class ChillCollectionType extends AbstractType { + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['button_add_label'] = $options['button_add_label']; + $view->vars['button_remove_label'] = $options['button_remove_label']; + $view->vars['allow_delete'] = (int) $options['allow_delete']; + $view->vars['allow_add'] = (int) $options['allow_add']; + $view->vars['identifier'] = $options['identifier']; + $view->vars['empty_collection_explain'] = $options['empty_collection_explain']; + } + public function configureOptions(OptionsResolver $resolver) { $resolver @@ -46,16 +48,6 @@ class ChillCollectionType extends AbstractType ]); } - public function buildView(FormView $view, FormInterface $form, array $options) - { - $view->vars['button_add_label'] = $options['button_add_label']; - $view->vars['button_remove_label'] = $options['button_remove_label']; - $view->vars['allow_delete'] = (int) $options['allow_delete']; - $view->vars['allow_add'] = (int) $options['allow_add']; - $view->vars['identifier'] = $options['identifier']; - $view->vars['empty_collection_explain'] = $options['empty_collection_explain']; - } - public function getParent() { return \Symfony\Component\Form\Extension\Core\Type\CollectionType::class; diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillDateTimeType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillDateTimeType.php index 07adc0cb8..f0d9d5eae 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ChillDateTimeType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillDateTimeType.php @@ -1,20 +1,14 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; use Symfony\Component\Form\AbstractType; @@ -23,11 +17,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver; /** * Display the date in a date picker. - * + * * Extends the symfony `Symfony\Component\Form\Extension\Core\Type\DateType` * to automatically create a date picker. - * - * @author Mathieu Jaumotte */ class ChillDateTimeType extends AbstractType { @@ -39,10 +31,9 @@ class ChillDateTimeType extends AbstractType ->setDefault('time_widget', 'choice') ->setDefault('minutes', range(0, 59, 5)) ->setDefault('hours', range(8, 22)) - ->setDefault('html5', true) - ; + ->setDefault('html5', true); } - + public function getParent() { return DateTimeType::class; diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillDateType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillDateType.php index 8502febbb..1b7f51115 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ChillDateType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillDateType.php @@ -1,20 +1,14 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; use Symfony\Component\Form\AbstractType; @@ -23,11 +17,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver; /** * Display the date in a date picker. - * + * * Extends the symfony `Symfony\Component\Form\Extension\Core\Type\DateType` * to automatically create a date picker. - * - * @author Julien Fastré */ class ChillDateType extends AbstractType { @@ -35,10 +27,9 @@ class ChillDateType extends AbstractType { $resolver ->setDefault('widget', 'single_text') - ->setDefault('html5', true) - ; + ->setDefault('html5', true); } - + public function getParent() { return DateType::class; diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillTextareaType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillTextareaType.php index 37c436f7c..18bfd885b 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ChillTextareaType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillTextareaType.php @@ -1,45 +1,40 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; /** - * Create a Textarea - * - * By default, add a WYSIWYG editor. - * - * Options: - * - * * `disable_editor`: set true to disable editor + * Create a Textarea. * + * By default, add a WYSIWYG editor. + * + * Options: + * + * * `disable_editor`: set true to disable editor */ final class ChillTextareaType extends AbstractType { - public function getParent() + public function buildView(FormView $view, FormInterface $form, array $options) { - return TextareaType::class; + if (!$options['disable_editor']) { + $view->vars['attr']['ckeditor'] = true; + } } - + public function configureOptions(OptionsResolver $resolver) { $resolver @@ -47,11 +42,9 @@ final class ChillTextareaType extends AbstractType ->setDefault('disable_editor', false) ->setAllowedTypes('disable_editor', 'bool'); } - - public function buildView(FormView $view, FormInterface $form, array $options) + + public function getParent() { - if (!$options['disable_editor']) { - $view->vars['attr']['ckeditor'] = true; - } + return TextareaType::class; } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/CommentType.php b/src/Bundle/ChillMainBundle/Form/Type/CommentType.php index afcd061e6..3adca5eca 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/CommentType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/CommentType.php @@ -1,26 +1,19 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; +use DateTime; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Chill\MainBundle\Form\Type\ChillTextareaType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; @@ -47,15 +40,14 @@ class CommentType extends AbstractType ->add('comment', ChillTextareaType::class, [ 'disable_editor' => $options['disable_editor'], 'label' => $options['label'], - ]) - ; + ]); $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { $data = $event->getForm()->getData(); $comment = $event->getData() ?? ['comment' => '']; - if ($data->getComment() !== $comment['comment']) { - $data->setDate(new \DateTime()); + if (null !== $data && $data->getComment() !== $comment['comment']) { + $data->setDate(new DateTime()); $data->setUserId($this->user->getId()); $event->getForm()->setData($data); } @@ -65,7 +57,8 @@ class CommentType extends AbstractType public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace( - $view->vars, [ + $view->vars, + [ 'hideLabel' => true, ] ); @@ -78,7 +71,7 @@ class CommentType extends AbstractType ->setAllowedTypes('disable_editor', 'bool') ->setDefaults([ 'data_class' => CommentEmbeddable::class, - 'disable_editor' => false + 'disable_editor' => false, ]); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/ComposedGroupCenterType.php b/src/Bundle/ChillMainBundle/Form/Type/ComposedGroupCenterType.php index 0fe628401..1398a8ce2 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ComposedGroupCenterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ComposedGroupCenterType.php @@ -1,54 +1,39 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; - -use Chill\MainBundle\Entity\PermissionsGroup; use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\PermissionsGroup; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; + +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; -/** - * - * - * @author Julien Fastré - */ class ComposedGroupCenterType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->add('permissionsgroup', EntityType::class, array( + $builder->add('permissionsgroup', EntityType::class, [ 'class' => 'Chill\MainBundle\Entity\PermissionsGroup', - 'choice_label' => function(PermissionsGroup $group) { + 'choice_label' => static function (PermissionsGroup $group) { return $group->getName(); - } - ))->add('center', EntityType::class, array( + }, + ])->add('center', EntityType::class, [ 'class' => 'Chill\MainBundle\Entity\Center', - 'choice_label' => function(Center $center) { + 'choice_label' => static function (Center $center) { return $center->getName(); - } - )) - ; + }, + ]); } public function configureOptions(OptionsResolver $resolver) @@ -60,5 +45,4 @@ class ComposedGroupCenterType extends AbstractType { return 'composed_groupcenter'; } - } diff --git a/src/Bundle/ChillMainBundle/Form/Type/ComposedRoleScopeType.php b/src/Bundle/ChillMainBundle/Form/Type/ComposedRoleScopeType.php index c06394845..bf5e2d4f8 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ComposedRoleScopeType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ComposedRoleScopeType.php @@ -1,71 +1,53 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; - -use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\RoleProvider; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use function in_array; /** * Form to Edit/create a role scope. If the role scope does not * exists in the database, he is generated. - * - * @author Julien Fastré - * @author Champs Libres */ class ComposedRoleScopeType extends AbstractType { /** - * - * @var string[] - */ - private $roles = array(); - - /** - * - * @var string[] - */ - private $rolesWithoutScope = array(); - - /** - * - * @var TranslatableStringHelper - */ - private $translatableStringHelper; - - /** - * * @var RoleProvider */ private $roleProvider; + /** + * @var string[] + */ + private $roles = []; + + /** + * @var string[] + */ + private $rolesWithoutScope = []; + + /** + * @var TranslatableStringHelper + */ + private $translatableStringHelper; + public function __construct( TranslatableStringHelper $translatableStringHelper, RoleProvider $roleProvider @@ -83,40 +65,39 @@ class ComposedRoleScopeType extends AbstractType $rolesWithoutScopes = $this->rolesWithoutScope; //build roles - $values = array(); + $values = []; + foreach ($this->roles as $role) { $values[$role] = $role; } $builder - ->add('role', ChoiceType::class, array( - 'choices' => array_combine(array_values($values),array_keys($values)), - 'placeholder' => 'Choose amongst roles', - 'choice_attr' => function($role) use ($rolesWithoutScopes) { - if (in_array($role, $rolesWithoutScopes)) { - return array('data-has-scope' => '0'); - } else { - return array('data-has-scope' => '1'); + ->add('role', ChoiceType::class, [ + 'choices' => array_combine(array_values($values), array_keys($values)), + 'placeholder' => 'Choose amongst roles', + 'choice_attr' => static function ($role) use ($rolesWithoutScopes) { + if (in_array($role, $rolesWithoutScopes, true)) { + return ['data-has-scope' => '0']; } - }, - 'group_by' => function($role, $key, $index) { + + return ['data-has-scope' => '1']; + }, + 'group_by' => function ($role, $key, $index) { return $this->roleProvider->getRoleTitle($role); - } - )) - ->add('scope', EntityType::class, array( + }, + ]) + ->add('scope', EntityType::class, [ 'class' => 'ChillMainBundle:Scope', - 'choice_label' => function(Scope $scope) use ($translatableStringHelper) { + 'choice_label' => static function (Scope $scope) use ($translatableStringHelper) { return $translatableStringHelper->localize($scope->getName()); }, 'required' => false, - 'data' => null - )); - + 'data' => null, + ]); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefault('data_class', 'Chill\MainBundle\Entity\RoleScope'); } - } diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php index 1931ded47..76f217df6 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php @@ -1,5 +1,14 @@ addressRepository->find($value); - if (NULL === $address) { - $failure = new TransformationFailedException(sprintf("Address with id %s does not exists", $value)); + if (null === $address) { + $failure = new TransformationFailedException(sprintf('Address with id %s does not exists', $value)); $failure - ->setInvalidMessage("The given {{ value }} is not a valid address id", [ '{{ value }}' => $value]); + ->setInvalidMessage('The given {{ value }} is not a valid address id', ['{{ value }}' => $value]); throw $failure; } @@ -36,7 +45,7 @@ final class AddressToIdDataTransformer implements DataTransformerInterface public function transform($value) { - if (NULL === $value) { + if (null === $value) { return ''; } diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/CenterTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/CenterTransformer.php index c088edeba..a0f609d46 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/CenterTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/CenterTransformer.php @@ -1,36 +1,29 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\DataTransformer; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Repository\CenterRepository; use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Traversable; +use function count; class CenterTransformer implements DataTransformerInterface { private CenterRepository $centerRepository; + private bool $multiple = false; public function __construct( @@ -43,57 +36,63 @@ class CenterTransformer implements DataTransformerInterface public function reverseTransform($id) { - if ($id === NULL) { + if (null === $id) { if ($this->multiple) { return new ArrayCollection(); - } else { - return NULL; } + + return null; } + $ids = []; + if ($this->multiple) { - $ids = \explode(',', $id); + $ids = explode(',', $id); } else { $ids[] = (int) $id; } $centers = $this ->centerRepository - ->findBy(['id' => $ids ]); + ->findBy(['id' => $ids]); if ([] === $centers || count($ids) > count($centers)) { throw new TransformationFailedException(sprintf( - 'No center found for one of those ids: %s', implode(',', $ids))); + 'No center found for one of those ids: %s', + implode(',', $ids) + )); } if ($this->multiple) { return new ArrayCollection($centers); - } else { - return $centers[0]; } + + return $centers[0]; } public function transform($center) { - if ($center === NULL) { + if (null === $center) { return ''; } if ($this->multiple) { if (!is_iterable($center)) { - throw new UnexpectedTypeException($center, \Traversable::class); + throw new UnexpectedTypeException($center, Traversable::class); } $ids = []; + foreach ($center as $c) { $ids[] = $c->getId(); } return implode(',', $ids); - } else { - if (!$center instanceof Center) { - throw new UnexpectedTypeException($center, Center::class); - } - return (string) $center->getId(); } + + if (!$center instanceof Center) { + throw new UnexpectedTypeException($center, Center::class); + } + + return (string) $center->getId(); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/DateIntervalTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/DateIntervalTransformer.php index 29a81447c..f41223d17 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/DateIntervalTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/DateIntervalTransformer.php @@ -1,92 +1,84 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\DataTransformer; +use DateInterval; +use Exception; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\UnexpectedTypeException; -/** - * - * - * @author Julien Fastré - */ class DateIntervalTransformer implements DataTransformerInterface { - /** - * - * @param \DateInterval $value - * @throws UnexpectedTypeException - */ + public function reverseTransform($value) + { + if (empty($value) || empty($value['n'])) { + return null; + } + + $string = 'P' . $value['n'] . $value['unit']; + + try { + return new DateInterval($string); + } catch (Exception $e) { + throw new TransformationFailedException( + 'Could not transform value into DateInterval', + 1542, + $e + ); + } + } + public function transform($value) { if (empty($value)) { return null; } - - if (!$value instanceof \DateInterval) { - throw new UnexpectedTypeException($value, \DateInterval::class); + + if (!$value instanceof DateInterval) { + throw new UnexpectedTypeException($value, DateInterval::class); } - - if ($value->d > 0) { + + if (0 < $value->d) { // we check for weeks (weeks are converted to 7 days) if ($value->d % 7 === 0) { return [ 'n' => $value->d / 7, - 'unit' => 'W' - ]; - } else { - return [ - 'n' => $value->d, - 'unit' => 'D' + 'unit' => 'W', ]; } - } elseif ($value->m > 0) { + + return [ + 'n' => $value->d, + 'unit' => 'D', + ]; + } + + if (0 < $value->m) { return [ 'n' => $value->m, - 'unit' => 'M' + 'unit' => 'M', ]; - } elseif ($value->y > 0) { + } + + if (0 < $value->y) { return [ 'n' => $value->y, - 'unit' => 'Y' + 'unit' => 'Y', ]; } - - throw new TransformationFailedException('the date interval does not ' - . 'contains any days, months or years'); - } - public function reverseTransform($value) - { - if (empty($value) or empty($value['n'])) { - return null; - } - - $string = 'P'.$value['n'].$value['unit']; - - try { - return new \DateInterval($string); - } catch (\Exception $e) { - throw new TransformationFailedException("Could not transform value " - . "into DateInterval", 1542, $e); - } - - + throw new TransformationFailedException( + 'The date interval does not contains any days, months or years.' + ); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php index 00ee26fff..48ef5cdf0 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php @@ -1,37 +1,26 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\DataTransformer; -use Symfony\Component\Form\DataTransformerInterface; -use Symfony\Component\Form\Exception\TransformationFailedException; -use Doctrine\Persistence\ObjectManager; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Form\DataTransformerInterface; class MultipleObjectsToIdTransformer implements DataTransformerInterface { - private EntityManagerInterface $em; - private ?string $class; + private EntityManagerInterface $em; + public function __construct(EntityManagerInterface $em, ?string $class = null) { $this->em = $em; @@ -39,28 +28,12 @@ class MultipleObjectsToIdTransformer implements DataTransformerInterface } /** - * Transforms an object (use) to a string (id). - * - * @param array $array - * @return ArrayCollection - */ - public function transform($array) - { - $ret = array(); - - foreach ($array as $el) { - $ret[] = ($el->getId()); - } - - return $ret; - } - - /** - * Transforms a string (id) to an object (item). - * - * @param string $id - * @return ArrayCollection - */ + * Transforms a string (id) to an object (item). + * + * @param mixed $array + * + * @return ArrayCollection + */ public function reverseTransform($array) { $ret = new ArrayCollection(); @@ -72,6 +45,25 @@ class MultipleObjectsToIdTransformer implements DataTransformerInterface ->find($el) ); } + return $ret; } -} \ No newline at end of file + + /** + * Transforms an object (use) to a string (id). + * + * @param array $array + * + * @return ArrayCollection + */ + public function transform($array) + { + $ret = []; + + foreach ($array as $el) { + $ret[] = ($el->getId()); + } + + return $ret; + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php index 82cdf6c21..145ab0441 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php @@ -1,36 +1,26 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\DataTransformer; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; -use Doctrine\Persistence\ObjectManager; class ObjectToIdTransformer implements DataTransformerInterface { - private EntityManagerInterface $em; - private ?string $class; + private EntityManagerInterface $em; + public function __construct(EntityManagerInterface $em, ?string $class = null) { $this->em = $em; @@ -38,26 +28,13 @@ class ObjectToIdTransformer implements DataTransformerInterface } /** - * Transforms an object to a string (id) - * - * @param Object|null $Object - * @return string - */ - public function transform($object) - { - if (!$object) { - return ""; - } - - return $object->getId(); - } - - /** - * Transforms a string (id) to an object + * Transforms a string (id) to an object. * * @param string $id - * @return Object|null + * * @throws TransformationFailedException if object is not found. + * + * @return object|null */ public function reverseTransform($id) { @@ -67,13 +44,28 @@ class ObjectToIdTransformer implements DataTransformerInterface $object = $this->em ->getRepository($this->class) - ->find($id) - ; + ->find($id); - if (! $object) { + if (!$object) { throw new TransformationFailedException(); } return $object; } -} \ No newline at end of file + + /** + * Transforms an object to a string (id). + * + * @param mixed $object + * + * @return string + */ + public function transform($object) + { + if (!$object) { + return ''; + } + + return $object->getId(); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ScopeTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ScopeTransformer.php index bd1185333..5f6c3ed65 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ScopeTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ScopeTransformer.php @@ -1,30 +1,20 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\DataTransformer; use Chill\MainBundle\Entity\Scope; -use Symfony\Component\Form\DataTransformerInterface; -use Doctrine\Persistence\ObjectManager; -use Symfony\Component\Form\Exception\TransformationFailedException; -use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; class ScopeTransformer implements DataTransformerInterface { @@ -35,19 +25,10 @@ class ScopeTransformer implements DataTransformerInterface $this->em = $em; } - public function transform($scope) - { - if ($scope === NULL) { - return NULL; - } - - return $scope->getId(); - } - public function reverseTransform($id) { - if ($id == NULL) { - return NULL; + if (null === $id) { + return null; } $scope = $this @@ -55,12 +36,20 @@ class ScopeTransformer implements DataTransformerInterface ->getRepository(Scope::class) ->find($id); - if ($scope === NULL) { - throw new TransformationFailedException(sprintf("The scope with id " + if (null === $scope) { + throw new TransformationFailedException(sprintf('The scope with id ' . "'%d' were not found", $id)); } return $scope; } + public function transform($scope) + { + if (null === $scope) { + return null; + } + + return $scope->getId(); + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/DateIntervalType.php b/src/Bundle/ChillMainBundle/Form/Type/DateIntervalType.php index 5879bc525..66ae4d846 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DateIntervalType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DateIntervalType.php @@ -1,40 +1,38 @@ - * - * 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\MainBundle\Form\Type; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\Extension\Core\Type\IntegerType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Chill\MainBundle\Form\Type\DataTransformer\DateIntervalTransformer; -use Symfony\Component\Validator\Constraints\GreaterThan; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; - /** - * Show a dateInterval type - * + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Form\Type; + +use Chill\MainBundle\Form\Type\DataTransformer\DateIntervalTransformer; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\GreaterThan; +use function array_diff; +use function array_values; +use function count; +use function implode; +use function is_array; + +/** + * Show a dateInterval type. + * * Options: - * - * - `unit_choices`: an array of available units choices. - * - * The oiriginal `unit_choices` are : + * + * - `unit_choices`: an array of available units choices. + * + * The oiriginal `unit_choices` are : * ``` * [ * 'Days' => 'D', @@ -43,11 +41,11 @@ use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; * 'Years' => 'Y' * ] * ``` - * + * * You can remove one or more entries: - * + * * ``` - * $builder + * $builder * ->add('duration', DateIntervalType::class, array( * 'unit_choices' => [ * 'Years' => 'Y', @@ -55,7 +53,6 @@ use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; * ] * )); * ``` - * */ class DateIntervalType extends AbstractType { @@ -66,18 +63,17 @@ class DateIntervalType extends AbstractType 'scale' => 0, 'constraints' => [ new GreaterThan([ - 'value' => 0 - ]) - ] + 'value' => 0, + ]), + ], ]) ->add('unit', ChoiceType::class, [ 'choices' => $options['unit_choices'], - ]) - ; - + ]); + $builder->addModelTransformer(new DateIntervalTransformer()); } - + public function configureOptions(OptionsResolver $resolver) { $resolver @@ -86,23 +82,24 @@ class DateIntervalType extends AbstractType 'Days' => 'D', 'Weeks' => 'W', 'Months' => 'M', - 'Years' => 'Y' + 'Years' => 'Y', ]) - ->setAllowedValues('unit_choices', function($values) { - if (FALSE === is_array($values)) { - throw new InvalidOptionsException("The value `unit_choice` should be an array"); + ->setAllowedValues('unit_choices', static function ($values) { + if (false === is_array($values)) { + throw new InvalidOptionsException('The value `unit_choice` should be an array'); } - - $diff = \array_diff(\array_values($values), ['D', 'W', 'M', 'Y']); - if (count($diff) == 0) { - return true; - } else { - throw new InvalidOptionsException(sprintf("The values of the " - . "units should be 'D', 'W', 'M', 'Y', those are invalid: %s", - \implode(', ', $diff))); - } - }) - ; - } + $diff = array_diff(array_values($values), ['D', 'W', 'M', 'Y']); + + if (count($diff) === 0) { + return true; + } + + throw new InvalidOptionsException(sprintf( + 'The values of the ' + . "units should be 'D', 'W', 'M', 'Y', those are invalid: %s", + implode(', ', $diff) + )); + }); + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php index dd2125546..9a5d97083 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php @@ -1,40 +1,24 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\Export; use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\MainBundle\Export\ExportManager; - -/** - * - * - * @author Julien Fastré - */ class AggregatorType extends AbstractType { - public function __construct() { } @@ -45,30 +29,27 @@ class AggregatorType extends AbstractType $aggregator = $exportManager->getAggregator($options['aggregator_alias']); $builder - ->add('enabled', CheckboxType::class, array( - 'value' => true, - 'required' => false, - 'data' => false - )); + ->add('enabled', CheckboxType::class, [ + 'value' => true, + 'required' => false, + 'data' => false, + ]); - $filterFormBuilder = $builder->create('form', FormType::class, array( - 'compound' => true, - 'required' => false, - 'error_bubbling' => false - )); + $filterFormBuilder = $builder->create('form', FormType::class, [ + 'compound' => true, + 'required' => false, + 'error_bubbling' => false, + ]); $aggregator->buildForm($filterFormBuilder); $builder->add($filterFormBuilder); - } public function configureOptions(OptionsResolver $resolver) { $resolver->setRequired('aggregator_alias') - ->setRequired('export_manager') - ->setDefault('compound', true) - ->setDefault('error_bubbling', false) - ; + ->setRequired('export_manager') + ->setDefault('compound', true) + ->setDefault('error_bubbling', false); } - } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php index 24dd63dd3..4b0c0694b 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php @@ -1,57 +1,38 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\Export; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Export\ExportManager; -use Chill\MainBundle\Form\Type\Export\FilterType; -use Chill\MainBundle\Form\Type\Export\AggregatorType; -use Symfony\Component\Form\Extension\Core\Type\FormType; use Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint; -use Chill\MainBundle\Export\ExportElementWithValidationInterface; -use Symfony\Component\Validator\Constraints as Assert; -use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; -/** - * - * - * @author Julien Fastré - */ class ExportType extends AbstractType { + public const AGGREGATOR_KEY = 'aggregators'; + + public const EXPORT_KEY = 'export'; + + public const FILTER_KEY = 'filters'; + + public const PICK_FORMATTER_KEY = 'pick_formatter'; + /** - * * @var ExportManager */ protected $exportManager; - const FILTER_KEY = 'filters'; - const AGGREGATOR_KEY = 'aggregators'; - const PICK_FORMATTER_KEY = 'pick_formatter'; - const EXPORT_KEY = 'export'; - public function __construct(ExportManager $exportManager) { $this->exportManager = $exportManager; @@ -61,12 +42,11 @@ class ExportType extends AbstractType { $export = $this->exportManager->getExport($options['export_alias']); - $exportOptions = array( + $exportOptions = [ 'compound' => true, - 'constraints' => array( - - ) - ); + 'constraints' => [ + ], + ]; // add a contraint if required by export $exportBuilder = $builder->create(self::EXPORT_KEY/*, FormType::class, $exportOptions*/); @@ -78,66 +58,65 @@ class ExportType extends AbstractType if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { //add filters $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); - $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, array('compound' => true)); + $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]); - foreach($filters as $alias => $filter) { - $filterBuilder->add($alias, FilterType::class, array( + foreach ($filters as $alias => $filter) { + $filterBuilder->add($alias, FilterType::class, [ 'filter_alias' => $alias, 'export_manager' => $this->exportManager, 'label' => $filter->getTitle(), - 'constraints' => array( - new ExportElementConstraint(['element' => $filter]) - ) - )); + 'constraints' => [ + new ExportElementConstraint(['element' => $filter]), + ], + ]); } $builder->add($filterBuilder); //add aggregators $aggregators = $this->exportManager - ->getAggregatorsApplyingOn($export, $options['picked_centers']); - $aggregatorBuilder = $builder->create(self::AGGREGATOR_KEY, FormType::class, - array('compound' => true)); + ->getAggregatorsApplyingOn($export, $options['picked_centers']); + $aggregatorBuilder = $builder->create( + self::AGGREGATOR_KEY, + FormType::class, + ['compound' => true] + ); - foreach($aggregators as $alias => $aggregator) { - $aggregatorBuilder->add($alias, AggregatorType::class, array( + foreach ($aggregators as $alias => $aggregator) { + $aggregatorBuilder->add($alias, AggregatorType::class, [ 'aggregator_alias' => $alias, 'export_manager' => $this->exportManager, 'label' => $aggregator->getTitle(), - 'constraints' => array( - new ExportElementConstraint(['element' => $aggregator]) - ) - )); + 'constraints' => [ + new ExportElementConstraint(['element' => $aggregator]), + ], + ]); } $builder->add($aggregatorBuilder); } // add export form - $exportBuilder = $builder->create(self::EXPORT_KEY, FormType::class, array('compound' => true)); + $exportBuilder = $builder->create(self::EXPORT_KEY, FormType::class, ['compound' => true]); $this->exportManager->getExport($options['export_alias']) - ->buildForm($exportBuilder); + ->buildForm($exportBuilder); $builder->add($exportBuilder); if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { - $builder->add(self::PICK_FORMATTER_KEY, PickFormatterType::class, array( - 'export_alias' => $options['export_alias'] - )); + $builder->add(self::PICK_FORMATTER_KEY, PickFormatterType::class, [ + 'export_alias' => $options['export_alias'], + ]); } - } - public function configureOptions(OptionsResolver $resolver) { - $resolver->setRequired(array('export_alias', 'picked_centers')) - ->setAllowedTypes('export_alias', array('string')) - ->setDefault('compound', true) - ->setDefault('constraints', array( - //new \Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint() - )) - ; - + $resolver->setRequired(['export_alias', 'picked_centers']) + ->setAllowedTypes('export_alias', ['string']) + ->setDefault('compound', true) + ->setDefault('constraints', [ + //new \Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint() + ]); } public function getParent() diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php index 21f1bcd48..8612dd690 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php @@ -1,42 +1,25 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\Export; use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormBuilderInterface; -use Chill\MainBundle\Export\ExportManager; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; -use Chill\MainBundle\Export\ExportElementWithValidationInterface; -use Symfony\Component\Validator\Constraints as Assert; -use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; -/** - * - * - * @author Julien Fastré - */ class FilterType extends AbstractType { - const ENABLED_FIELD = 'enabled'; + public const ENABLED_FIELD = 'enabled'; public function __construct() { @@ -44,35 +27,31 @@ class FilterType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options) { - $exportManager = $options['export_manager']; $filter = $exportManager->getFilter($options['filter_alias']); $builder - ->add(self::ENABLED_FIELD, CheckboxType::class, array( - 'value' => true, - 'data' => false, - 'required' => false - )); + ->add(self::ENABLED_FIELD, CheckboxType::class, [ + 'value' => true, + 'data' => false, + 'required' => false, + ]); - $filterFormBuilder = $builder->create('form', FormType::class, array( + $filterFormBuilder = $builder->create('form', FormType::class, [ 'compound' => true, 'error_bubbling' => false, 'required' => false, - )); + ]); $filter->buildForm($filterFormBuilder); $builder->add($filterFormBuilder); - } public function configureOptions(OptionsResolver $resolver) { $resolver->setRequired('filter_alias') - ->setRequired('export_manager') - ->setDefault('compound', true) - ->setDefault('error_bubbling', false) - ; + ->setRequired('export_manager') + ->setDefault('compound', true) + ->setDefault('error_bubbling', false); } - } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/FormatterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/FormatterType.php index 023822d12..c7c87e949 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/FormatterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/FormatterType.php @@ -1,59 +1,47 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\Export; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; -/** - * - * - * @author Julien Fastré - */ class FormatterType extends AbstractType { /** - * * @var ExportManager */ protected $exportManager; - - public function __construct(ExportManager $manager) + + public function __construct(ExportManager $manager) { $this->exportManager = $manager; } - - public function configureOptions(OptionsResolver $resolver) - { - $resolver->setRequired(array('formatter_alias', 'export_alias', - 'aggregator_aliases')); - } - + public function buildForm(FormBuilderInterface $builder, array $options) { $formatter = $this->exportManager->getFormatter($options['formatter_alias']); - - $formatter->buildForm($builder, $options['export_alias'], - $options['aggregator_aliases']); + + $formatter->buildForm( + $builder, + $options['export_alias'], + $options['aggregator_aliases'] + ); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired(['formatter_alias', 'export_alias', + 'aggregator_aliases', ]); } - } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php index 7f7143a4e..daf33eb57 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php @@ -1,182 +1,178 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\Export; +use Chill\MainBundle\Center\GroupingCenterInterface; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Doctrine\ORM\EntityRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Chill\MainBundle\Export\ExportManager; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Doctrine\ORM\EntityRepository; -use Chill\MainBundle\Entity\Center; -use Chill\MainBundle\Center\GroupingCenterInterface; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\CallbackTransformer; -use Doctrine\Common\Collections\Collection; +use function array_intersect; +use function array_key_exists; +use function array_merge; +use function array_unique; +use function count; +use function in_array; /** - * Pick centers amongst available centers for the user - * + * Pick centers amongst available centers for the user. */ class PickCenterType extends AbstractType { + public const CENTERS_IDENTIFIERS = 'c'; + /** - * - * @var \Symfony\Component\Security\Core\User\UserInterface - */ - protected $user; - - /** - * - * @var ExportManager - */ - protected $exportManager; - - /** - * - * @var GroupingCenterInterface[] - */ - protected $groupingCenters = []; - - const CENTERS_IDENTIFIERS = 'c'; - - /** - * * @var AuthorizationHelper */ protected $authorizationHelper; - - public function __construct(TokenStorageInterface $tokenStorage, - ExportManager $exportManager, AuthorizationHelper $authorizationHelper) - { + + /** + * @var ExportManager + */ + protected $exportManager; + + /** + * @var GroupingCenterInterface[] + */ + protected $groupingCenters = []; + + /** + * @var \Symfony\Component\Security\Core\User\UserInterface + */ + protected $user; + + public function __construct( + TokenStorageInterface $tokenStorage, + ExportManager $exportManager, + AuthorizationHelper $authorizationHelper + ) { $this->exportManager = $exportManager; $this->user = $tokenStorage->getToken()->getUser(); $this->authorizationHelper = $authorizationHelper; } - - - public function configureOptions(OptionsResolver $resolver) + + public function addGroupingCenter(GroupingCenterInterface $grouping) { - $resolver->setRequired('export_alias') - ; + $this->groupingCenters[md5($grouping->getName())] = $grouping; } - + public function buildForm(FormBuilderInterface $builder, array $options) { $export = $this->exportManager->getExport($options['export_alias']); - $centers = $this->authorizationHelper->getReachableCenters($this->user, - $export->requiredRole()); - - $builder->add(self::CENTERS_IDENTIFIERS, EntityType::class, array( - 'class' => 'ChillMainBundle:Center', - 'query_builder' => function(EntityRepository $er) use ($centers) { + $centers = $this->authorizationHelper->getReachableCenters( + $this->user, + $export->requiredRole() + ); + + $builder->add(self::CENTERS_IDENTIFIERS, EntityType::class, [ + 'class' => 'ChillMainBundle:Center', + 'query_builder' => static function (EntityRepository $er) use ($centers) { $qb = $er->createQueryBuilder('c'); - $ids = array_map(function(Center $el) { return $el->getId(); }, - $centers); + $ids = array_map( + static function (Center $el) { return $el->getId(); }, + $centers + ); + return $qb->where($qb->expr()->in('c.id', $ids)); - }, - 'multiple' => true, - 'expanded' => true, - 'choice_label' => function(Center $c) { return $c->getName(); }, - 'data' => count($this->groupingCenters) > 0 ? null : $centers - )); - + }, + 'multiple' => true, + 'expanded' => true, + 'choice_label' => static function (Center $c) { return $c->getName(); }, + 'data' => count($this->groupingCenters) > 0 ? null : $centers, + ]); + if (count($this->groupingCenters) > 0) { $groupingBuilder = $builder->create('g', null, [ - 'compound' => true + 'compound' => true, ]); foreach ($this->groupingCenters as $key => $gc) { $choices = $this->buildChoices($centers, $gc); - + if (count($choices) > 0) { $groupingBuilder->add($key, ChoiceType::class, [ 'choices' => $choices, 'multiple' => true, 'expanded' => true, 'label' => $gc->getName(), - 'required' => false + 'required' => false, ]); } } - + if ($groupingBuilder->count() > 0) { $builder->add($groupingBuilder); } } - + $builder->addModelTransformer(new CallbackTransformer( - function($data) use ($centers) { return $this->transform($data, $centers); }, - function($data) use ($centers) { return $this->reverseTransform($data, $centers); } - )); + function ($data) use ($centers) { return $this->transform($data, $centers); }, + function ($data) use ($centers) { return $this->reverseTransform($data, $centers); } + )); } - - public function addGroupingCenter(GroupingCenterInterface $grouping) + + public function configureOptions(OptionsResolver $resolver) { - $this->groupingCenters[md5($grouping->getName())] = $grouping; + $resolver->setRequired('export_alias'); } - + protected function buildChoices($reachablesCenters, GroupingCenterInterface $gc) { $result = []; - + foreach ($gc->getGroups() as $group) { foreach ($gc->getCentersForGroup($group) as $center) { - if (\in_array($center, $reachablesCenters)) { + if (in_array($center, $reachablesCenters, true)) { $result[$group] = $group; } } } - + return $result; } - + + protected function reverseTransform($data, $centers) + { + $picked = $data[self::CENTERS_IDENTIFIERS] + instanceof \Doctrine\Common\Collections\Collection ? + $data[self::CENTERS_IDENTIFIERS]->toArray() + : + $data[self::CENTERS_IDENTIFIERS]; + + if (array_key_exists('g', $data)) { + foreach ($data['g'] as $gcid => $group) { + $picked = + array_merge( + array_intersect( + $this->groupingCenters[$gcid]->getCentersForGroup($group), + $centers + ), + $picked + ); + } + } + + return array_unique($picked); + } + protected function transform($data, $centers) { return $data; } - - protected function reverseTransform($data, $centers) - { - $picked = $data[self::CENTERS_IDENTIFIERS] - instanceof \Doctrine\Common\Collections\Collection ? - $data[self::CENTERS_IDENTIFIERS]->toArray() - : - $data[self::CENTERS_IDENTIFIERS]; - - if (\array_key_exists('g', $data)) { - foreach($data['g'] as $gcid => $group) { - $picked = - \array_merge( - \array_intersect( - $this->groupingCenters[$gcid] ->getCentersForGroup($group), - $centers - ), - $picked - ) - ; - } - } - - return \array_unique($picked); - } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/PickFormatterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/PickFormatterType.php index 44726d915..2b1c8e093 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/PickFormatterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/PickFormatterType.php @@ -1,36 +1,25 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type\Export; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; - use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; + +use Symfony\Component\OptionsResolver\OptionsResolver; /** - * Choose a formatter amongst the available formatters - * - * - * @author Julien Fastré + * Choose a formatter amongst the available formatters. */ class PickFormatterType extends AbstractType { @@ -45,26 +34,26 @@ class PickFormatterType extends AbstractType { $export = $this->exportManager->getExport($options['export_alias']); $allowedFormatters = $this->exportManager - ->getFormattersByTypes($export->getAllowedFormattersTypes()); + ->getFormattersByTypes($export->getAllowedFormattersTypes()); //build choices - $choices = array(); - foreach($allowedFormatters as $alias => $formatter) { + $choices = []; + + foreach ($allowedFormatters as $alias => $formatter) { $choices[$formatter->getName()] = $alias; } - $builder->add('alias', ChoiceType::class, array( + $builder->add('alias', ChoiceType::class, [ 'choices' => $choices, 'multiple' => false, - 'placeholder' => 'Choose a format' - )); + 'placeholder' => 'Choose a format', + ]); //$builder->get('type')->addModelTransformer($transformer); } public function configureOptions(OptionsResolver $resolver) { - $resolver->setRequired(array('export_alias')); + $resolver->setRequired(['export_alias']); } - } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php index 3976625a1..488246a50 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php @@ -1,12 +1,27 @@ hasSearchBox()) { $builder->add('q', SearchType::class, [ 'label' => false, - 'required' => false + 'required' => false, ]); } + $checkboxesBuilder = $builder->create('checkboxes', null, ['compound' => true]); + + foreach ($helper->getCheckboxes() as $name => $c) { + $choices = array_combine( + array_map(static function ($c, $t) { + if (null !== $t) { + return $t; + } + + return $c; + }, $c['choices'], $c['trans']), + $c['choices'] + ); + + $checkboxesBuilder->add($name, ChoiceType::class, [ + 'choices' => $choices, + 'expanded' => true, + 'multiple' => true, + ]); + } + + if (0 < count($helper->getCheckboxes())) { + $builder->add($checkboxesBuilder); + } + foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) { - switch($key) { + switch ($key) { case 'q': + case 'checkboxes' . $key: break; + case 'page': $builder->add($key, HiddenType::class, [ - 'data' => 1 + 'data' => 1, ]); + break; + default: $builder->add($key, HiddenType::class, [ - 'data' => $value + 'data' => $value, ]); + break; } } } + public function buildView(FormView $view, FormInterface $form, array $options) + { + /** @var FilterOrderHelper $helper */ + $helper = $options['helper']; + $view->vars['has_search_box'] = $helper->hasSearchBox(); + $view->vars['checkboxes'] = []; + + foreach ($helper->getCheckboxes() as $name => $c) { + $view->vars['checkboxes'][$name] = []; + } + } + public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver) { - $resolver->setRequired('helper') - ->setAllowedTypes('helper', FilterOrderHelper::class); + $resolver->setRequired('helper') + ->setAllowedTypes('helper', FilterOrderHelper::class); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php b/src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php index e613fdf65..647c45553 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php @@ -1,5 +1,14 @@ vars['uniqid'] = $view->vars['attr']['data-input-address'] =\uniqid('input_address_'); + $view->vars['uniqid'] = $view->vars['attr']['data-input-address'] = uniqid('input_address_'); $view->vars['attr']['data-use-valid-from'] = (int) $options['use_valid_from']; $view->vars['attr']['data-use-valid-to'] = (int) $options['use_valid_to']; $view->vars['attr']['data-button-text-create'] = $this->translator->trans($options['button_text_create']); diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php b/src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php index 07c3d5c67..5ccf217a0 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickCenterType.php @@ -1,56 +1,48 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; use Chill\MainBundle\Repository\CenterRepository; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; +use RuntimeException; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Chill\MainBundle\Entity\Center; -use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Security\Core\Security; +use function array_merge; +use function array_values; +use function count; /** - * Pick a center + * Pick a center. * * For a given role and, eventually, scopes, show a dropdown (if more than * one reachable center) or a HiddenType (if one or zero center). - * - * */ class PickCenterType extends AbstractType { protected AuthorizationHelperInterface $authorizationHelper; - protected Security $security; - protected CenterRepository $centerRepository; + protected Security $security; + public function __construct( AuthorizationHelperInterface $authorizationHelper, Security $security, @@ -61,13 +53,64 @@ class PickCenterType extends AbstractType $this->centerRepository = $centerRepository; } + /** + * add a data transformer if user can reach only one center. + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $centers = $this->getReachableCenters($options['role'], $options['scopes']); + + if (count($centers) <= 1) { + $multiple = $options['choice_options']['multiple'] ?? false; + $builder->add('center', HiddenType::class); + $builder->get('center')->addModelTransformer( + new CenterTransformer($this->centerRepository, $multiple) + ); + } else { + $builder->add( + 'center', + EntityType::class, + array_merge( + $options['choice_options'], + [ + 'class' => Center::class, + 'choices' => $centers, + ] + ) + ); + } + + $builder + ->addModelTransformer(new CallbackTransformer( + static function ($data) { + if (null === $data) { + return ['center' => null]; + } + + return ['center' => $data]; + }, + static function ($data) { + return $data['center']; + } + )); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['is_hidden'] = count($this->getReachableCenters( + $options['role'], + $options['scopes'] + )) <= 1; + } + /** * return a 'hidden' field if only one center is available. * * Return a 'choice' field if more than one center is available. * + * @throws RuntimeException if the user is not associated with any center + * * @return string - * @throws \RuntimeException if the user is not associated with any center */ /* public function getParent() @@ -80,71 +123,26 @@ class PickCenterType extends AbstractType return EntityType::class; } } - */ + */ /** * configure default options, i.e. add choices if user can reach multiple * centers. - * - * @param OptionsResolver $resolver */ public function configureOptions(OptionsResolver $resolver) { $resolver ->setDefault('class', Center::class) ->setRequired('role') - ->setAllowedTypes('role', [ 'string' ]) + ->setAllowedTypes('role', ['string']) ->setDefault('scopes', []) ->setAllowedTypes('scopes', ['iterable']) - ->setDefault('choice_options', []) - ; + ->setDefault('choice_options', []); /* ->setDefault('choices', $this->reachableCenters) ->setDefault('placeholder', 'Pick a center') ; - */ - } - - /** - * add a data transformer if user can reach only one center - * - * @param FormBuilderInterface $builder - * @param array $options - */ - public function buildForm(FormBuilderInterface $builder, array $options) - { - $centers = $this->getReachableCenters($options['role'], $options['scopes']); - - if (count($centers) <= 1) { - $multiple = $options['choice_options']['multiple'] ?? false; - $builder->add('center', HiddenType::class); - $builder->get('center')->addModelTransformer( - new CenterTransformer($this->centerRepository, $multiple) - ); - } else { - $builder->add('center', EntityType::class, - \array_merge( - $options['choice_options'], - [ - 'class' => Center::class, - 'choices' => $centers - ] - ) - ); - } - - $builder - ->addModelTransformer(new CallbackTransformer( - function($data) { - if (NULL === $data) { - return ['center' => null]; - } - return ['center' => $data]; - }, - function($data) { - return $data['center']; - } - )); + */ } private function getReachableCenters(string $role, iterable $scopes): array @@ -152,23 +150,17 @@ class PickCenterType extends AbstractType if (0 < count($scopes)) { $centers = []; - foreach($scopes as $scope) { + foreach ($scopes as $scope) { foreach ($this->authorizationHelper - ->getReachableCenters($this->security->getUser(), $role, $scope) as $center) { + ->getReachableCenters($this->security->getUser(), $role, $scope) as $center) { $centers[spl_object_hash($center)] = $center; } } - return \array_values($centers); - } else { - return $this->authorizationHelper - ->getReachableCenters($this->security->getUser(), $role); + return array_values($centers); } - } - public function buildView(FormView $view, FormInterface $form, array $options) - { - $view->vars['is_hidden'] = count($this->getReachableCenters($options['role'], - $options['scopes'])) <= 1; + return $this->authorizationHelper + ->getReachableCenters($this->security->getUser(), $role); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/PostalCodeType.php b/src/Bundle/ChillMainBundle/Form/Type/PostalCodeType.php index 1c1242a5a..debd56ad4 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PostalCodeType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PostalCodeType.php @@ -1,67 +1,52 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Entity\PostalCode; +use Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader; use Symfony\Component\Translation\TranslatorInterface; /** - * A form to pick between PostalCode - * - * @author Julien Fastré - * @author Champs Libres + * A form to pick between PostalCode. */ class PostalCodeType extends AbstractType { /** - * - * @var TranslatableStringHelper - */ - protected $translatableStringHelper; - - /** - * - * @var UrlGeneratorInterface - */ - protected $urlGenerator; - - /** - * * @var PostalCodeChoiceLoader */ protected $choiceLoader; - + + /** + * @var TranslatableStringHelper + */ + protected $translatableStringHelper; + /** - * * @var TranslatorInterface */ protected $translator; + /** + * @var UrlGeneratorInterface + */ + protected $urlGenerator; + public function __construct( TranslatableStringHelper $helper, UrlGeneratorInterface $urlGenerator, @@ -74,27 +59,6 @@ class PostalCodeType extends AbstractType $this->translator = $translator; } - - public function getParent() - { - return EntityType::class; - } - - public function configureOptions(OptionsResolver $resolver) - { - // create a local copy for usage in Closure - $helper = $this->translatableStringHelper; - $resolver - ->setDefault('class', PostalCode::class) - ->setDefault('choice_label', function(PostalCode $code) use ($helper) { - return $code->getCode().' '.$code->getName().' ['. - $helper->localize($code->getCountry()->getName()).']'; - }) - ->setDefault('choice_loader', $this->choiceLoader) - ->setDefault('placeholder', 'Select a postal code') - ; - } - public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['attr']['data-postal-code'] = 'data-postal-code'; @@ -107,4 +71,22 @@ class PostalCodeType extends AbstractType $view->vars['attr']['data-searching-label'] = $this->translator->trans('select2.searching'); } + public function configureOptions(OptionsResolver $resolver) + { + // create a local copy for usage in Closure + $helper = $this->translatableStringHelper; + $resolver + ->setDefault('class', PostalCode::class) + ->setDefault('choice_label', static function (PostalCode $code) use ($helper) { + return $code->getCode() . ' ' . $code->getName() . ' [' . + $helper->localize($code->getCountry()->getName()) . ']'; + }) + ->setDefault('choice_loader', $this->choiceLoader) + ->setDefault('placeholder', 'Select a postal code'); + } + + public function getParent() + { + return EntityType::class; + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php index ebf474657..4aa8948ec 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php @@ -1,21 +1,14 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; use Chill\MainBundle\Entity\Center; @@ -23,9 +16,8 @@ use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper; use Chill\MainBundle\Repository\ScopeRepository; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; @@ -36,6 +28,9 @@ use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Core\Security; +use function array_map; +use function count; /** * Allow to pick amongst available scope for the current @@ -45,47 +40,49 @@ use Symfony\Component\Security\Core\Role\Role; * * - `center`: the center of the entity * - `role` : the role of the user - * - * @author Julien Fastré */ class ScopePickerType extends AbstractType { + protected AuthorizationHelperInterface $authorizationHelper; + /** - * @var AuthorizationHelper + * @var ScopeRepository */ - protected $authorizationHelper; + protected $scopeRepository; + + protected Security $security; /** * @var TokenStorageInterface */ protected $tokenStorage; - /** - * @var ScopeRepository - */ - protected $scopeRepository; - /** * @var TranslatableStringHelper */ protected $translatableStringHelper; public function __construct( - AuthorizationHelper $authorizationHelper, + AuthorizationHelperInterface $authorizationHelper, TokenStorageInterface $tokenStorage, ScopeRepository $scopeRepository, + Security $security, TranslatableStringHelper $translatableStringHelper ) { $this->authorizationHelper = $authorizationHelper; $this->tokenStorage = $tokenStorage; $this->scopeRepository = $scopeRepository; + $this->security = $security; $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder, array $options) { - $query = $this->buildAccessibleScopeQuery($options['center'], $options['role']); - $items = $query->getQuery()->execute(); + $items = $this->authorizationHelper->getReachableScopes( + $this->security->getUser(), + $options['role']->getRole(), + $options['center'] + ); if (1 !== count($items)) { $builder->add('scope', EntityType::class, [ @@ -94,9 +91,7 @@ class ScopePickerType extends AbstractType 'choice_label' => function (Scope $c) { return $this->translatableStringHelper->localize($c->getName()); }, - 'query_builder' => function () use ($options) { - return $this->buildAccessibleScopeQuery($options['center'], $options['role']); - }, + 'choices' => $items, ]); $builder->setDataMapper(new ScopePickerDataMapper()); } else { @@ -110,7 +105,8 @@ class ScopePickerType extends AbstractType public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace( - $view->vars, [ + $view->vars, + [ 'hideLabel' => true, ] ); @@ -121,19 +117,23 @@ class ScopePickerType extends AbstractType $resolver // create `center` option ->setRequired('center') - ->setAllowedTypes('center', [Center::class]) + ->setAllowedTypes('center', [Center::class, 'array', 'null']) // create ``role` option ->setRequired('role') ->setAllowedTypes('role', ['string', Role::class]); } /** + * @param array|Center|Center[] $center + * @param string $role + * * @return \Doctrine\ORM\QueryBuilder */ - protected function buildAccessibleScopeQuery(Center $center, Role $role) + protected function buildAccessibleScopeQuery($center, $role) { $roles = $this->authorizationHelper->getParentRoles($role); $roles[] = $role; + $centers = $center instanceof Center ? [$center] : $center; $qb = $this->scopeRepository->createQueryBuilder('s'); $qb @@ -142,14 +142,14 @@ class ScopePickerType extends AbstractType ->join('rs.permissionsGroups', 'pg') ->join('pg.groupCenters', 'gc') // add center constraint - ->where($qb->expr()->eq('IDENTITY(gc.center)', ':center')) - ->setParameter('center', $center->getId()) + ->where($qb->expr()->in('IDENTITY(gc.center)', ':centers')) + ->setParameter('centers', array_map(static fn (Center $c) => $c->getId(), $centers)) // role constraints ->andWhere($qb->expr()->in('rs.role', ':roles')) ->setParameter('roles', $roles) // user contraint - ->andWhere(':user MEMBER OF gc.users') - ->setParameter('user', $this->tokenStorage->getToken()->getUser()); + ->andWhere(':user MEMBER OF gc.users') + ->setParameter('user', $this->tokenStorage->getToken()->getUser()); return $qb; } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Select2ChoiceType.php b/src/Bundle/ChillMainBundle/Form/Type/Select2ChoiceType.php index faadb3154..ae5ab075e 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Select2ChoiceType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Select2ChoiceType.php @@ -1,36 +1,34 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\OptionsResolver\OptionsResolver; /** - * Extends choice to allow adding select2 library on widget - * - * @author Julien Fastré + * Extends choice to allow adding select2 library on widget. */ class Select2ChoiceType extends AbstractType { + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults( + [ + 'attr' => ['class' => 'select2 '], + ] + ); + } + public function getBlockPrefix() { return 'select2_choice'; @@ -40,12 +38,4 @@ class Select2ChoiceType extends AbstractType { return ChoiceType::class; } - - public function configureOptions(OptionsResolver $resolver) - { - $resolver->setDefaults( - array( - 'attr' => array('class' => 'select2 '), - )); - } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php b/src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php index 617d1afce..bed90ad18 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php @@ -1,67 +1,84 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer; -use Doctrine\Persistence\ObjectManager; -use Chill\MainBundle\Form\Type\Select2ChoiceType; use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\Persistence\ObjectManager; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\OptionsResolver\OptionsResolver; +use const SORT_FLAG_CASE; +use const SORT_STRING; /** - * Extends choice to allow adding select2 library on widget - * - * @author Julien Fastré - * @author Marc Ducobu + * Extends choice to allow adding select2 library on widget. */ class Select2CountryType extends AbstractType { - /** - * @var RequestStack - */ - private $requestStack; + protected ParameterBagInterface $parameterBag; - /** - * - * @var TranslatableStringHelper - */ - protected $translatableStringHelper; + protected TranslatableStringHelper $translatableStringHelper; - /** - * @var ObjectManager - */ - private $em; + private ObjectManager $em; + + private RequestStack $requestStack; public function __construct( RequestStack $requestStack, ObjectManager $em, - TranslatableStringHelper $translatableStringHelper - ) - { + TranslatableStringHelper $translatableStringHelper, + ParameterBagInterface $parameterBag + ) { $this->requestStack = $requestStack; $this->em = $em; $this->translatableStringHelper = $translatableStringHelper; + $this->parameterBag = $parameterBag; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $transformer = new ObjectToIdTransformer($this->em, 'Chill\MainBundle\Entity\Country'); + $builder->addModelTransformer($transformer); + } + + public function configureOptions(OptionsResolver $resolver) + { + $countries = $this->em->getRepository('Chill\MainBundle\Entity\Country')->findAll(); + $choices = []; + $preferredCountries = $this->parameterBag->get('chill_main.available_countries'); + $preferredChoices = []; + + foreach ($countries as $c) { + $choices[$c->getId()] = $this->translatableStringHelper->localize($c->getName()); + } + + foreach ($preferredCountries as $pc) { + foreach ($countries as $c) { + if ($c->getCountryCode() === $pc) { + $preferredChoices[$c->getId()] = $this->translatableStringHelper->localize($c->getName()); + } + } + } + + asort($choices, SORT_STRING | SORT_FLAG_CASE); + + $resolver->setDefaults([ + 'class' => 'Chill\MainBundle\Entity\Country', + 'choices' => array_combine(array_values($choices), array_keys($choices)), + 'preferred_choices' => array_combine(array_values($preferredChoices), array_keys($preferredChoices)), + ]); } public function getBlockPrefix() @@ -69,32 +86,8 @@ class Select2CountryType extends AbstractType return 'select2_chill_country'; } - public function buildForm(FormBuilderInterface $builder, array $options) - { - $transformer = new ObjectToIdTransformer($this->em,'Chill\MainBundle\Entity\Country'); - $builder->addModelTransformer($transformer); - } - public function getParent() { return Select2ChoiceType::class; } - - public function configureOptions(OptionsResolver $resolver) - { - $locale = $this->requestStack->getCurrentRequest()->getLocale(); - $countries = $this->em->getRepository('Chill\MainBundle\Entity\Country')->findAll(); - $choices = array(); - - foreach ($countries as $c) { - $choices[$c->getId()] = $this->translatableStringHelper->localize($c->getName()); - } - - asort($choices, SORT_STRING | SORT_FLAG_CASE); - - $resolver->setDefaults(array( - 'class' => 'Chill\MainBundle\Entity\Country', - 'choices' => array_combine(array_values($choices),array_keys($choices)) - )); - } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php b/src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php index e72bf0d8e..b4db6393e 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Select2EntityType.php @@ -1,36 +1,32 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; /** - * Extends choice to allow adding select2 library on widget - * - * @author Julien Fastré + * Extends choice to allow adding select2 library on widget. */ class Select2EntityType extends AbstractType { + public function configureOptions(OptionsResolver $resolver) + { + $resolver->replaceDefaults( + ['attr' => ['class' => 'select2 ']] + ); + } + public function getBlockPrefix() { return 'select2_entity'; @@ -40,11 +36,4 @@ class Select2EntityType extends AbstractType { return EntityType::class; } - - public function configureOptions(OptionsResolver $resolver) - { - $resolver->replaceDefaults( - array('attr' => array('class' => 'select2 ')) - ); - } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php b/src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php index 2c8dea857..9fad2bdb3 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php @@ -1,64 +1,80 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Form\Type\DataTransformer\MultipleObjectsToIdTransformer; -use Doctrine\Persistence\ObjectManager; -use Chill\MainBundle\Form\Type\Select2ChoiceType; use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\Persistence\ObjectManager; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\OptionsResolver\OptionsResolver; +use const SORT_FLAG_CASE; +use const SORT_STRING; /** - * Extends choice to allow adding select2 library on widget for languages (multiple) + * Extends choice to allow adding select2 library on widget for languages (multiple). */ class Select2LanguageType extends AbstractType { - /** - * @var RequestStack - */ - private $requestStack; + protected ParameterBagInterface $parameterBag; - /** - * @var ObjectManager - */ - private $em; + protected TranslatableStringHelper $translatableStringHelper; - /** - * - * @var TranslatableStringHelper - */ - protected $translatableStringHelper; + private ObjectManager $em; + + private RequestStack $requestStack; public function __construct( RequestStack $requestStack, ObjectManager $em, - TranslatableStringHelper $translatableStringHelper - ) - { + TranslatableStringHelper $translatableStringHelper, + ParameterBagInterface $parameterBag + ) { $this->requestStack = $requestStack; $this->em = $em; $this->translatableStringHelper = $translatableStringHelper; + $this->parameterBag = $parameterBag; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $transformer = new MultipleObjectsToIdTransformer($this->em, 'Chill\MainBundle\Entity\Language'); + $builder->addModelTransformer($transformer); + } + + public function configureOptions(OptionsResolver $resolver) + { + $languages = $this->em->getRepository('Chill\MainBundle\Entity\Language')->findAll(); + $preferredLanguages = $this->parameterBag->get('chill_main.available_languages'); + $choices = []; + $preferredChoices = []; + + foreach ($languages as $l) { + $choices[$l->getId()] = $this->translatableStringHelper->localize($l->getName()); + } + + foreach ($preferredLanguages as $l) { + $preferredChoices[$l] = $choices[$l]; + } + + asort($choices, SORT_STRING | SORT_FLAG_CASE); + + $resolver->setDefaults([ + 'class' => 'Chill\MainBundle\Entity\Language', + 'choices' => array_combine(array_values($choices), array_keys($choices)), + 'preferred_choices' => array_combine(array_values($preferredChoices), array_keys($preferredChoices)), + ]); } public function getBlockPrefix() @@ -66,32 +82,8 @@ class Select2LanguageType extends AbstractType return 'select2_chill_language'; } - public function buildForm(FormBuilderInterface $builder, array $options) - { - $transformer = new MultipleObjectsToIdTransformer($this->em,'Chill\MainBundle\Entity\Language'); - $builder->addModelTransformer($transformer); - } - public function getParent() { return Select2ChoiceType::class; } - - public function configureOptions(OptionsResolver $resolver) - { - $locale = $this->requestStack->getCurrentRequest()->getLocale(); - $languages = $this->em->getRepository('Chill\MainBundle\Entity\Language')->findAll(); - $choices = array(); - - foreach ($languages as $l) { - $choices[$l->getId()] = $this->translatableStringHelper->localize($l->getName()); - } - - asort($choices, SORT_STRING | SORT_FLAG_CASE); - - $resolver->setDefaults(array( - 'class' => 'Chill\MainBundle\Entity\Language', - 'choices' => array_combine(array_values($choices),array_keys($choices)) - )); - } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/TranslatableStringFormType.php b/src/Bundle/ChillMainBundle/Form/Type/TranslatableStringFormType.php index 18a0a6e9f..3f44a4e97 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/TranslatableStringFormType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/TranslatableStringFormType.php @@ -1,5 +1,14 @@ availableLanguages = $availableLanguages; $this->frameworkTranslatorFallback = $translator->getFallbackLocales(); } - public function buildForm(FormBuilderInterface $builder, array $options) { + public function buildForm(FormBuilderInterface $builder, array $options) + { foreach ($this->availableLanguages as $lang) { - $builder->add($lang, TextType::class, - array('required' => (in_array($lang, - $this->frameworkTranslatorFallback)))); + $builder->add( + $lang, + TextType::class, + ['required' => (in_array( + $lang, + $this->frameworkTranslatorFallback, + true + ))] + ); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php b/src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php index fece2d6d4..0141438f6 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php @@ -1,35 +1,28 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\Form\AbstractType; +use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface; +use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Doctrine\ORM\EntityRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Chill\MainBundle\Entity\User; -use Chill\MainBundle\Repository\UserRepository; -use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Security\Core\Role\Role; - /** * Pick a user available for the given role and center. * @@ -37,69 +30,66 @@ use Symfony\Component\Security\Core\Role\Role; * * - `role` : the role the user can reach * - `center`: the center a user can reach - * - * @author Julien Fastré */ class UserPickerType extends AbstractType { /** - * * @var AuthorizationHelper */ protected $authorizationHelper; /** - * * @var TokenStorageInterface */ protected $tokenStorage; + protected UserACLAwareRepositoryInterface $userACLAwareRepository; + protected UserRepository $userRepository; public function __construct( AuthorizationHelper $authorizationHelper, TokenStorageInterface $tokenStorage, - UserRepository $userRepository + UserRepository $userRepository, + UserACLAwareRepositoryInterface $userACLAwareRepository ) { $this->authorizationHelper = $authorizationHelper; $this->tokenStorage = $tokenStorage; $this->userRepository = $userRepository; + $this->userACLAwareRepository = $userACLAwareRepository; } - public function configureOptions(OptionsResolver $resolver) { $resolver // create `center` option ->setRequired('center') - ->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class ]) + ->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class, 'null', 'array']) // create ``role` option ->setRequired('role') - ->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ]) - ; + ->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class]); $resolver ->setDefault('having_permissions_group_flag', null) ->setAllowedTypes('having_permissions_group_flag', ['string', 'null']) ->setDefault('class', User::class) ->setDefault('placeholder', 'Choose an user') - ->setDefault('choice_label', function(User $u) { + ->setDefault('choice_label', static function (User $u) { return $u->getUsername(); }) - ->setNormalizer('choices', function(Options $options) { - - $users = $this->authorizationHelper - ->findUsersReaching($options['role'], $options['center']); - - if (NULL !== $options['having_permissions_group_flag']) { + ->setDefault('scope', null) + ->setAllowedTypes('scope', [Scope::class, 'array', 'null']) + ->setNormalizer('choices', function (Options $options) { + $users = $this->userACLAwareRepository + ->findUsersByReachedACL($options['role']->getRole(), $options['center'], $options['scope'], true); + + if (null !== $options['having_permissions_group_flag']) { return $this->userRepository - ->findUsersHavingFlags($options['having_permissions_group_flag'], $users) - ; + ->findUsersHavingFlags($options['having_permissions_group_flag'], $users); } - + return $users; - }) - ; + }); } public function getParent() diff --git a/src/Bundle/ChillMainBundle/Form/UserCurrentLocationType.php b/src/Bundle/ChillMainBundle/Form/UserCurrentLocationType.php new file mode 100644 index 000000000..4bdf61604 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/UserCurrentLocationType.php @@ -0,0 +1,48 @@ +translatableStringHelper = $translatableStringHelper; + $this->locationRepository = $locationRepository; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('currentLocation', EntityType::class, [ + 'class' => Location::class, + 'choices' => $this->locationRepository->findByPublicLocations(), + 'choice_label' => function (Location $entity) { + return $entity->getName() ? + $entity->getName() . ' (' . $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()) . ')' : + $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()); + }, + 'placeholder' => 'Pick a location', + 'required' => false, + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/UserJobType.php b/src/Bundle/ChillMainBundle/Form/UserJobType.php index eec718261..74d69bd07 100644 --- a/src/Bundle/ChillMainBundle/Form/UserJobType.php +++ b/src/Bundle/ChillMainBundle/Form/UserJobType.php @@ -1,5 +1,14 @@ add('label', TranslatableStringFormType::class, [ 'label' => 'Label', - 'required' => true + 'required' => true, ]) ->add('active', ChoiceType::class, [ 'choices' => [ 'Active' => true, - 'Inactive' => false - ] - ]) - ; + 'Inactive' => false, + ], + ]); } - } diff --git a/src/Bundle/ChillMainBundle/Form/UserPasswordType.php b/src/Bundle/ChillMainBundle/Form/UserPasswordType.php index 4486b5643..d2e60c1ef 100644 --- a/src/Bundle/ChillMainBundle/Form/UserPasswordType.php +++ b/src/Bundle/ChillMainBundle/Form/UserPasswordType.php @@ -1,104 +1,104 @@ passwordEncoder = $passwordEncoder; $this->chillLogger = $chillLogger; } - - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('new_password', RepeatedType::class, array( + ->add('new_password', RepeatedType::class, [ 'type' => PasswordType::class, 'required' => false, - 'options' => array(), - 'first_options' => array( - 'label' => 'Password' - ), - 'second_options' => array( - 'label' => 'Repeat the password' - ), - 'invalid_message' => "The password fields must match", - 'constraints' => array( - new Length(array( + 'options' => [], + 'first_options' => [ + 'label' => 'Password', + ], + 'second_options' => [ + 'label' => 'Repeat the password', + ], + 'invalid_message' => 'The password fields must match', + 'constraints' => [ + new Length([ 'min' => 9, - 'minMessage' => 'The password must be greater than {{ limit }} characters' - )), + 'minMessage' => 'The password must be greater than {{ limit }} characters', + ]), new NotBlank(), - new Regex(array( - 'pattern' => "/((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%!,;:+\"'-\/{}~=µ\(\)£]).{6,})/", - 'message' => "The password must contains one letter, one " - . "capitalized letter, one number and one special character " - . "as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed." - )) - ) - )) + new Regex([ + 'pattern' => "/((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%!,;:+\"'-\\/{}~=µ\\(\\)£]).{6,})/", + 'message' => 'The password must contains one letter, one ' + . 'capitalized letter, one number and one special character ' + . "as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed.", + ]), + ], + ]) ->add('actual_password', PasswordType::class, [ 'label' => 'Your actual password', 'mapped' => false, 'constraints' => [ new Callback([ - 'callback' => function($password, ExecutionContextInterface $context, $payload) use ($options) { - if (TRUE === $this->passwordEncoder->isPasswordValid($options['user'], $password)) { + 'callback' => function ($password, ExecutionContextInterface $context, $payload) use ($options) { + if (true === $this->passwordEncoder->isPasswordValid($options['user'], $password)) { return; } - + // password problem :-) $this->chillLogger - ->notice("incorrect password when trying to change password", [ - 'username' => $options['user']->getUsername() + ->notice('incorrect password when trying to change password', [ + 'username' => $options['user']->getUsername(), ]); $context->addViolation('Incorrect password'); - } - ]) - ] - ]) - ; + }, + ]), + ], + ]); } - + public function configureOptions(OptionsResolver $resolver) { $resolver ->setRequired('user') - ->setAllowedTypes('user', \Chill\MainBundle\Entity\User::class) - ; + ->setAllowedTypes('user', \Chill\MainBundle\Entity\User::class); } /** diff --git a/src/Bundle/ChillMainBundle/Form/UserType.php b/src/Bundle/ChillMainBundle/Form/UserType.php index bf056cec5..58f053ac1 100644 --- a/src/Bundle/ChillMainBundle/Form/UserType.php +++ b/src/Bundle/ChillMainBundle/Form/UserType.php @@ -1,5 +1,14 @@ translatableStringHelper = $translatableStringHelper; } - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('username') ->add('email', EmailType::class, [ - 'required' => true + 'required' => true, ]) ->add('label', TextType::class) ->add('mainCenter', EntityType::class, [ @@ -51,12 +51,12 @@ class UserType extends AbstractType 'required' => false, 'placeholder' => 'Choose a main center', 'class' => Center::class, - 'query_builder' => function (EntityRepository $er) { + 'query_builder' => static function (EntityRepository $er) { $qb = $er->createQueryBuilder('c'); $qb->addOrderBy('c.name'); return $qb; - } + }, ]) ->add('mainScope', EntityType::class, [ 'label' => 'Main scope', @@ -75,47 +75,47 @@ class UserType extends AbstractType 'choice_label' => function (UserJob $c) { return $this->translatableStringHelper->localize($c->getLabel()); }, - ]) - ; + ]); + if ($options['is_creation']) { - $builder->add('plainPassword', RepeatedType::class, array( + $builder->add('plainPassword', RepeatedType::class, [ 'mapped' => false, 'type' => PasswordType::class, 'required' => false, - 'options' => array(), - 'first_options' => array( - 'label' => 'Password' - ), - 'second_options' => array( - 'label' => 'Repeat the password' - ), - 'invalid_message' => "The password fields must match", - 'constraints' => array( - new Length(array( + 'options' => [], + 'first_options' => [ + 'label' => 'Password', + ], + 'second_options' => [ + 'label' => 'Repeat the password', + ], + 'invalid_message' => 'The password fields must match', + 'constraints' => [ + new Length([ 'min' => 9, - 'minMessage' => 'The password must be greater than {{ limit }} characters' - )), + 'minMessage' => 'The password must be greater than {{ limit }} characters', + ]), new NotBlank(), - new Regex(array( - 'pattern' => "/((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%!,;:+\"'-\/{}~=µ\(\)£]).{6,})/", - 'message' => "The password must contains one letter, one " - . "capitalized letter, one number and one special character " - . "as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed." - )) - ) - )); - + new Regex([ + 'pattern' => "/((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%!,;:+\"'-\\/{}~=µ\\(\\)£]).{6,})/", + 'message' => 'The password must contains one letter, one ' + . 'capitalized letter, one number and one special character ' + . "as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed.", + ]), + ], + ]); } else { - $builder->add($builder - ->create('enabled', ChoiceType::class, array( - 'choices' => array( + $builder->add( + $builder + ->create('enabled', ChoiceType::class, [ + 'choices' => [ 'Disabled, the user is not allowed to login' => 0, - 'Enabled, the user is active' => 1 - ), + 'Enabled, the user is active' => 1, + ], 'expanded' => false, 'multiple' => false, - )) - ); + ]) + ); } } @@ -124,14 +124,13 @@ class UserType extends AbstractType */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( - 'data_class' => 'Chill\MainBundle\Entity\User' - )); + $resolver->setDefaults([ + 'data_class' => 'Chill\MainBundle\Entity\User', + ]); $resolver - ->setDefaults(array('is_creation' => false)) - ->addAllowedValues('is_creation', array(true, false)) - ; + ->setDefaults(['is_creation' => false]) + ->addAllowedValues('is_creation', [true, false]); } /** diff --git a/src/Bundle/ChillMainBundle/Form/Utils/PermissionsGroupFlagProvider.php b/src/Bundle/ChillMainBundle/Form/Utils/PermissionsGroupFlagProvider.php index 39ddb62d5..f4a91f100 100644 --- a/src/Bundle/ChillMainBundle/Form/Utils/PermissionsGroupFlagProvider.php +++ b/src/Bundle/ChillMainBundle/Form/Utils/PermissionsGroupFlagProvider.php @@ -1,17 +1,21 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Notification; use Chill\MainBundle\Entity\User; use Psr\Log\LoggerInterface; -use Symfony\Component\Mailer\MailerInterface; +use Swift_Mailer; +use Swift_Message; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Translation\TranslatorInterface; use Twig\Environment; +use function call_user_func; /** * Class Mailer * Classe d'aide pour l'envoi de notification. - * - * Héberge toutes les méthodes pour ré-écrire les URL en fonction de la langue de l'utilisateur. * - * @package Chill\MainBundle\Notification + * Héberge toutes les méthodes pour ré-écrire les URL en fonction de la langue de l'utilisateur. */ class Mailer { + /** + * @var Swift_Mailer + */ + protected $forcedMailer; + /** * @var LoggerInterface */ protected $logger; - + /** - * @var \Twig\Environment - */ - protected $twig; - - /** - * @var \Swift_Mailer + * @var Swift_Mailer */ protected $mailer; - - /** - * @var \Swift_Mailer - */ - protected $forcedMailer; - - /** - * @var RouterInterface - */ - protected $router; - - /** - * @var TranslatorInterface - */ - protected $translator; - + /** * @var array */ protected $routeParameters; - + + /** + * @var RouterInterface + */ + protected $router; + + /** + * @var TranslatorInterface + */ + protected $translator; + + /** + * @var \Twig\Environment + */ + protected $twig; + /** * Mailer constructor. * - * @param LoggerInterface $logger - * @param Environment $twig - * @param \Swift_Mailer $mailer - * @param RouterInterface $router - * @param TranslatorInterface $translator * @param $routeParameters */ public function __construct( - LoggerInterface $logger, + LoggerInterface $logger, Environment $twig, - \Swift_Mailer $mailer, + Swift_Mailer $mailer, // due to bug https://github.com/symfony/swiftmailer-bundle/issues/127 // \Swift_Transport $mailerTransporter, RouterInterface $router, - TranslatorInterface $translator, + TranslatorInterface $translator, $routeParameters ) { $this->logger = $logger; @@ -97,60 +86,40 @@ class Mailer $this->translator = $translator; $this->routeParameters = $routeParameters; } - + /** - * Envoie une notification à un utilisateur. - * - * @param \User $to - * @param array $subject Subject of the message [ 0 => $message (required), 1 => $parameters (optional), 3 => $domain (optional) ] - * @param array $bodies The bodies. An array where keys are the contentType and values the bodies - * @param callable $callback a callback to customize the message (add attachment, etc.) + * @param $template + * + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + * + * @return string */ - public function sendNotification( - $recipient, - array $subject, - array $bodies, - callable $callback = null, - $force = false - ) + public function renderContentToUser(User $to, $template, array $parameters = []) { - $fromEmail = $this->routeParameters['from_email']; - $fromName = $this->routeParameters['from_name']; - $to = $recipient instanceof User ? $recipient->getEmail() : $recipient; - - $subjectI18n = $this->translator->trans( - $subject[0], - $subject[1] ?? [], - $subject[2] ?? null - ); - - $message = (new \Swift_Message($subjectI18n)) - ->setFrom($fromEmail, $fromName) - ->setTo($to) - ; - - foreach ($bodies as $contentType => $content) { - $message->setBody($content, $contentType); - } - - if ($callback !== null) { - \call_user_func($callback, $message); - } - - $this->logger->info("[notification] Sending notification", [ - 'to' => $message->getTo(), - 'subject' => $message->getSubject() - ]); - - $this->sendMessage($message, $force); + $context = $this->router->getContext(); + $previousHost = $context->getHost(); + $previousScheme = $context->getScheme(); + + $context->setHost($this->routeParameters['host']); + $context->setScheme($this->routeParameters['scheme']); + + $content = $this->twig->render($template, $parameters); + + // reset the host + $context->setHost($previousHost); + $context->setScheme($previousScheme); + + return $content; } - + /** - * @param \Swift_Message $message * @param $force + * * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface */ - public function sendMessage(\Swift_Message $message, $force) + public function sendMessage(Swift_Message $message, $force) { if ($force) { $this->forcedMailer->send($message); @@ -158,31 +127,50 @@ class Mailer $this->mailer->send($message); } } - + /** - * @param User $to - * @param $template - * @param array $parameters - * @return string - * @throws \Twig\Error\LoaderError - * @throws \Twig\Error\RuntimeError - * @throws \Twig\Error\SyntaxError + * Envoie une notification à un utilisateur. + * + * @param array $subject Subject of the message [ 0 => $message (required), 1 => $parameters (optional), 3 => $domain (optional) ] + * @param array $bodies The bodies. An array where keys are the contentType and values the bodies + * @param callable $callback a callback to customize the message (add attachment, etc.) + * @param mixed $recipient + * @param mixed $force */ - public function renderContentToUser(User $to, $template, array $parameters = array()) - { - $context = $this->router->getContext(); - $previousHost = $context->getHost(); - $previousScheme = $context->getScheme(); - - $context->setHost($this->routeParameters['host']); - $context->setScheme($this->routeParameters['scheme']); - - $content = $this->twig->render($template, $parameters); - - // reset the host - $context->setHost($previousHost); - $context->setScheme($previousScheme); - - return $content; + public function sendNotification( + $recipient, + array $subject, + array $bodies, + ?callable $callback = null, + $force = false + ) { + $fromEmail = $this->routeParameters['from_email']; + $fromName = $this->routeParameters['from_name']; + $to = $recipient instanceof User ? $recipient->getEmail() : $recipient; + + $subjectI18n = $this->translator->trans( + $subject[0], + $subject[1] ?? [], + $subject[2] ?? null + ); + + $message = (new Swift_Message($subjectI18n)) + ->setFrom($fromEmail, $fromName) + ->setTo($to); + + foreach ($bodies as $contentType => $content) { + $message->setBody($content, $contentType); + } + + if (null !== $callback) { + call_user_func($callback, $message); + } + + $this->logger->info('[notification] Sending notification', [ + 'to' => $message->getTo(), + 'subject' => $message->getSubject(), + ]); + + $this->sendMessage($message, $force); } } diff --git a/src/Bundle/ChillMainBundle/Notification/NotificationRenderer.php b/src/Bundle/ChillMainBundle/Notification/NotificationRenderer.php index d6f383b73..048dfc02f 100644 --- a/src/Bundle/ChillMainBundle/Notification/NotificationRenderer.php +++ b/src/Bundle/ChillMainBundle/Notification/NotificationRenderer.php @@ -1,10 +1,20 @@ renderers[] = $activityNotificationRenderer; } - private function getRenderer(Notification $notification) - { - foreach ($this->renderers as $renderer) { - if($renderer->supports($notification)) { - return $renderer; - } - } - - throw new \Exception('No renderer for '. $notification); - } - public function getTemplate(Notification $notification) { return $this->getRenderer($notification)->getTemplate(); @@ -41,4 +40,15 @@ final class NotificationRenderer { return $this->getRenderer($notification)->getTemplateData($notification); } + + private function getRenderer(Notification $notification) + { + foreach ($this->renderers as $renderer) { + if ($renderer->supports($notification)) { + return $renderer; + } + } + + throw new Exception('No renderer for ' . $notification); + } } diff --git a/src/Bundle/ChillMainBundle/Pagination/ChillItemsPerPageTwig.php b/src/Bundle/ChillMainBundle/Pagination/ChillItemsPerPageTwig.php index 2cfb45d4a..3a236f5c1 100644 --- a/src/Bundle/ChillMainBundle/Pagination/ChillItemsPerPageTwig.php +++ b/src/Bundle/ChillMainBundle/Pagination/ChillItemsPerPageTwig.php @@ -1,25 +1,14 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Pagination; use Twig\Environment; @@ -27,41 +16,37 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; /** - * add twig function to render pagination - * - * @author Mathieu Jaumotte - * @author Champs Libres + * add twig function to render pagination. */ class ChillItemsPerPageTwig extends AbstractExtension { + public function getFunctions() + { + return [ + new TwigFunction( + 'chill_items_per_page', + [$this, 'paginationRender'], + [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ] + ), + ]; + } + public function getName() { return 'chill_items_per_page'; } - public function getFunctions() - { - return array( - new TwigFunction( - 'chill_items_per_page', - array($this, 'paginationRender'), - array( - 'needs_environment' => true, - 'is_safe' => ['html'] - ) - ) - ); - } - public function paginationRender( - Environment $env, - PaginatorInterface $paginator, - $template = '@ChillMain/Pagination/items_per_page.html.twig' + Environment $env, + PaginatorInterface $paginator, + $template = '@ChillMain/Pagination/items_per_page.html.twig' ) { - - return $env->render($template, array( - 'paginator' => $paginator, - 'current' => $paginator->getItemsPerPage() - )); + return $env->render($template, [ + 'paginator' => $paginator, + 'current' => $paginator->getItemsPerPage(), + ]); } } diff --git a/src/Bundle/ChillMainBundle/Pagination/ChillPaginationTwig.php b/src/Bundle/ChillMainBundle/Pagination/ChillPaginationTwig.php index 4917216a1..ed540b0c2 100644 --- a/src/Bundle/ChillMainBundle/Pagination/ChillPaginationTwig.php +++ b/src/Bundle/ChillMainBundle/Pagination/ChillPaginationTwig.php @@ -1,25 +1,14 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Pagination; use Twig\Environment; @@ -27,54 +16,56 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; /** - * add twig function to render pagination - * - * @author Julien Fastré - * @author Champs Libres + * add twig function to render pagination. */ class ChillPaginationTwig extends AbstractExtension { - const LONG_TEMPLATE = '@ChillMain/Pagination/long.html.twig'; - const SHORT_TEMPLATE = '@ChillMain/Pagination/short.html.twig'; - + public const LONG_TEMPLATE = '@ChillMain/Pagination/long.html.twig'; + + public const SHORT_TEMPLATE = '@ChillMain/Pagination/short.html.twig'; + + public function getFunctions() + { + return [ + new TwigFunction( + 'chill_pagination', + [$this, 'paginationRender'], + [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ] + ), + ]; + } + public function getName() { return 'chill_pagination'; } - - public function getFunctions() - { - return array( - new TwigFunction( - 'chill_pagination', - array($this, 'paginationRender'), - array( - 'needs_environment' => true, - 'is_safe' => ['html'] - ) - ) - ); - } - + public function paginationRender( - Environment $env, - PaginatorInterface $paginator, - $template = '@ChillMain/Pagination/long.html.twig' + Environment $env, + PaginatorInterface $paginator, + $template = '@ChillMain/Pagination/long.html.twig' ) { switch ($template) { case 'long': $t = self::LONG_TEMPLATE; + break; + case 'short': $t = self::SHORT_TEMPLATE; + break; + default: $t = $template; } - - return $env->render($t, array( - 'paginator' => $paginator, - 'current' => $paginator->getCurrentPage()->getNumber() - )); + + return $env->render($t, [ + 'paginator' => $paginator, + 'current' => $paginator->getCurrentPage()->getNumber(), + ]); } } diff --git a/src/Bundle/ChillMainBundle/Pagination/Page.php b/src/Bundle/ChillMainBundle/Pagination/Page.php index e6b3943ed..c93af4508 100644 --- a/src/Bundle/ChillMainBundle/Pagination/Page.php +++ b/src/Bundle/ChillMainBundle/Pagination/Page.php @@ -1,84 +1,70 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Pagination; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** - * a page is an element of a pagination - * - * @author Julien Fastré - * @author Champs Libres + * a page is an element of a pagination. */ class Page implements PageInterface { /** - * - * @var UrlGeneratorInterface - */ - protected $urlGenerator; - - /** - * the number of the current page - * - * @var int - */ - protected $number; - - /** - * The route for the current page - * - * @var string - */ - protected $route; - - /** - * Parameters for the route to the current page - * - * @var array - */ - protected $routeParameters; - - /** - * the number of item per page + * the number of item per page. * * @var int */ protected $itemPerPage; - + /** - * The number of items in the whole iteration + * the number of the current page. + * + * @var int + */ + protected $number; + + /** + * The route for the current page. + * + * @var string + */ + protected $route; + + /** + * Parameters for the route to the current page. + * + * @var array + */ + protected $routeParameters; + + /** + * The number of items in the whole iteration. * * @var int */ protected $totalItems; + /** + * @var UrlGeneratorInterface + */ + protected $urlGenerator; public function __construct( - $number, - $itemPerPage, - UrlGeneratorInterface $urlGenerator, - $route, - array $routeParameters, - $totalItems + $number, + $itemPerPage, + UrlGeneratorInterface $urlGenerator, + $route, + array $routeParameters, + $totalItems ) { $this->urlGenerator = $urlGenerator; $this->number = $number; @@ -87,7 +73,7 @@ class Page implements PageInterface $this->routeParameters = $routeParameters; $this->totalItems = $totalItems; } - + public function generateUrl() { return $this->urlGenerator->generate($this->route, $this->routeParameters); @@ -101,7 +87,7 @@ class Page implements PageInterface public function getLastItemNumber() { $last = $this->number * $this->itemPerPage - 1; - + return $last < $this->totalItems ? $last : $this->totalItems; } @@ -109,5 +95,4 @@ class Page implements PageInterface { return $this->number; } - } diff --git a/src/Bundle/ChillMainBundle/Pagination/PageGenerator.php b/src/Bundle/ChillMainBundle/Pagination/PageGenerator.php index 26add87fa..d7da19708 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PageGenerator.php +++ b/src/Bundle/ChillMainBundle/Pagination/PageGenerator.php @@ -1,50 +1,32 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Pagination; +use Iterator; + /** - * PageGenerator associated with a Paginator - * - * @author Julien Fastré - * @author Champs Libres + * PageGenerator associated with a Paginator. */ -class PageGenerator implements \Iterator +class PageGenerator implements Iterator { - /** - * - * @var Paginator - */ - protected $paginator; - - /** - * - * @var int - */ - protected $current = 1; - - public function __construct(Paginator $paginator) + protected int $current = 1; + + protected Paginator $paginator; + + public function __construct(Paginator $paginator) { - $this->paginator = $paginator;; + $this->paginator = $paginator; } - + public function current() { return $this->paginator->getPage($current); @@ -57,7 +39,7 @@ class PageGenerator implements \Iterator public function next() { - $this->current++; + ++$this->current; } public function rewind() @@ -67,7 +49,7 @@ class PageGenerator implements \Iterator public function valid() { - return $this->current > 0 - && $this->current <= $this->paginator->countPages(); + return 0 < $this->current + && $this->paginator->countPages() >= $this->current; } } diff --git a/src/Bundle/ChillMainBundle/Pagination/PageInterface.php b/src/Bundle/ChillMainBundle/Pagination/PageInterface.php index aa87c289c..acca7301c 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PageInterface.php +++ b/src/Bundle/ChillMainBundle/Pagination/PageInterface.php @@ -1,43 +1,31 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Pagination; /** - * Represents a page included in a pagination - * - * @author Julien Fastré + * Represents a page included in a pagination. */ interface PageInterface { public function generateUrl(); - + + public function getFirstItemNumber(); + + public function getLastItemNumber(); + /** * get the page number. - * - * The first page number is 1. + * + * The first page number is 1. */ public function getNumber(); - - public function getFirstItemNumber(); - - public function getLastItemNumber(); - } diff --git a/src/Bundle/ChillMainBundle/Pagination/Paginator.php b/src/Bundle/ChillMainBundle/Pagination/Paginator.php index 82577d078..54fa36084 100644 --- a/src/Bundle/ChillMainBundle/Pagination/Paginator.php +++ b/src/Bundle/ChillMainBundle/Pagination/Paginator.php @@ -1,104 +1,92 @@ * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Pagination; +use RuntimeException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; - /** * Standard paginator class. * * Represent a set of paginated pages; - * - * @author Julien Fastré - * @author Champs Libres */ class Paginator implements PaginatorInterface { /** - * The number of total items - * - * @var int - */ - protected $totalItems; - - /** - * the number of items on a single page - * - * @var int - */ - protected $itemPerPage; - /** - * The number of the current page + * The number of the current page. * * @var int */ protected $currentPageNumber; - + /** - * the route of the pages + * the number of items on a single page. * - * @var string + * @var int */ - protected $route; - + protected $itemPerPage; + /** - * the parameters of the route - * - * @var string[] - */ - protected $routeParameters; - - /** - * the generator for url - * - * @var UrlGeneratorInterface - */ - protected $urlGenerator; - - /** - * the key in the GET parameter to indicate the page number in - * generated routes - * - * @var string - */ - protected $pageKey; - - /** - * the key in the GET parameter to indicate the number of item per page + * the key in the GET parameter to indicate the number of item per page. * * @var string */ protected $itemPerPageKey; + /** + * the key in the GET parameter to indicate the page number in + * generated routes. + * + * @var string + */ + protected $pageKey; + + /** + * the route of the pages. + * + * @var string + */ + protected $route; + + /** + * the parameters of the route. + * + * @var string[] + */ + protected $routeParameters; + + /** + * The number of total items. + * + * @var int + */ + protected $totalItems; + + /** + * the generator for url. + * + * @var UrlGeneratorInterface + */ + protected $urlGenerator; public function __construct( - $totalItems, - $itemPerPage, - $currentPageNumber, - $route, - array $routeParameters, - UrlGeneratorInterface $urlGenerator, - $pageKey, - $itemPerPageKey + $totalItems, + $itemPerPage, + $currentPageNumber, + $route, + array $routeParameters, + UrlGeneratorInterface $urlGenerator, + $pageKey, + $itemPerPageKey ) { $this->totalItems = $totalItems; $this->itemPerPage = $itemPerPage; @@ -115,8 +103,26 @@ class Paginator implements PaginatorInterface return $this->countPages(); } + public function countPages() + { + if (0 === $this->itemPerPage) { + return 1; + } + + if (0 === $this->totalItems) { + return 1; + } + + $nb = floor($this->totalItems / $this->itemPerPage); + + if ($this->totalItems % $this->itemPerPage > 0) { + ++$nb; + } + + return 0 === $nb ? 1 : (int) $nb; + } + /** - * * @return \Chill\MainBundle\Pagination\Page */ public function getCurrentPage() @@ -128,68 +134,78 @@ class Paginator implements PaginatorInterface { return $this->getCurrentPage()->getFirstItemNumber(); } - - public function isCurrentPage(PageInterface $page) - { - return $page->getNumber() === $this->currentPageNumber; - } - public function getItemsPerPage() + public function getItemsPerPage() { return $this->itemPerPage; } - - public function setItemsPerPage($itemPerPage) - { - $this->itemPerPage = $itemPerPage; - } - - public function getTotalItems() - { - return $this->totalItems; - } - - public function countPages() - { - $nb = floor($this->totalItems / $this->itemPerPage); - - if ($this->totalItems % $this->itemPerPage > 0) { - $nb++; - } - - return $nb == 0 ? 1 : (int) $nb; - } /** + * @throws RuntimeException if the next page does not exists * * @return \Chill\MainBundle\Pagination\Page - * @throws \RuntimeException if the next page does not exists */ public function getNextPage() { if (!$this->hasNextPage()) { - throw new \RuntimeException("this page has no next page"); + throw new RuntimeException('this page has no next page'); } - + return $this->getPage($this->currentPageNumber + 1); } /** + * @param type $number + * + * @return \Chill\MainBundle\Pagination\Page + */ + public function getPage($number) + { + if (!$this->hasPage($number)) { + throw new RuntimeException("The page with number {$number} does not " + . 'exists'); + } + + return new Page( + $number, + $this->itemPerPage, + $this->urlGenerator, + $this->route, + array_merge($this->routeParameters, [ + $this->pageKey => $number, + $this->itemPerPageKey => $this->itemPerPage, + ]), + $this->totalItems + ); + } + + public function getPagesGenerator() + { + for ($i = 1; $this->countPages() >= $i; ++$i) { + yield $this->getPage($i); + } + } + + /** + * @throws RuntimeException if the next page does not exists * * @return \Chill\MainBundle\Pagination\Page - * @throws \RuntimeException if the next page does not exists */ public function getPreviousPage() { if (!$this->hasPreviousPage()) { - throw new \RuntimeException("this page has no previous page"); + throw new RuntimeException('this page has no previous page'); } - + return $this->getPage($this->currentPageNumber - 1); } + public function getTotalItems() + { + return $this->totalItems; + } + /** - * * @return bool */ public function hasNextPage() @@ -197,51 +213,31 @@ class Paginator implements PaginatorInterface return $this->hasPage($this->currentPageNumber + 1); } + public function hasPage($number) + { + if (0 === $this->totalItems) { + return 1 === $number; + } + + return 0 < $number + && $this->countPages() >= $number; + } + /** - * * @return bool */ public function hasPreviousPage() { return $this->hasPage($this->currentPageNumber - 1); } - - public function hasPage($number) + + public function isCurrentPage(PageInterface $page) { - return $number > 0 and - $number <= $this->countPages(); + return $page->getNumber() === $this->currentPageNumber; } - - /** - * - * @param type $number - * @return \Chill\MainBundle\Pagination\Page - */ - public function getPage($number) { - if (!$this->hasPage($number)) { - throw new \RuntimeException("The page with number $number does not " - . "exists"); - } - - return new Page( - $number, - $this->itemPerPage, - $this->urlGenerator, - $this->route, - array_merge($this->routeParameters, array( - $this->pageKey => $number, - $this->itemPerPageKey => $this->itemPerPage - )), - $this->totalItems - ); - } - - public function getPagesGenerator() + public function setItemsPerPage($itemPerPage) { - for ($i = 1; $i <= $this->countPages(); $i++) { - yield $this->getPage($i); - } + $this->itemPerPage = $itemPerPage; } - } diff --git a/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php index c386a9757..e02729287 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php +++ b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php @@ -1,36 +1,30 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Pagination; -use Symfony\Component\Routing\RouterInterface; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\RouterInterface; /** - * Create paginator instances - * - * @author Julien Fastré - * @author Champs Libres + * Create paginator instances. */ class PaginatorFactory { + public const DEFAULT_CURRENT_PAGE_KEY = 'page'; + + public const DEFAULT_ITEM_PER_NUMBER_KEY = 'item_per_page'; + + public const DEFAULT_PAGE_NUMBER = 1; + /** * the default item per page. This may be overriden by * the request or inside the paginator. @@ -38,38 +32,33 @@ class PaginatorFactory * @var int */ private $itemPerPage; - + /** - * the router and generator for url - * - * @var RouterInterface - */ - private $router; - - /** - * the request stack + * the request stack. * * @var RequestStack */ private $requestStack; - - const DEFAULT_CURRENT_PAGE_KEY = 'page'; - const DEFAULT_ITEM_PER_NUMBER_KEY = 'item_per_page'; - const DEFAULT_PAGE_NUMBER = 1; - - + + /** + * the router and generator for url. + * + * @var RouterInterface + */ + private $router; + public function __construct( - RequestStack $requestStack, - RouterInterface $router, - $itemPerPage = 20 + RequestStack $requestStack, + RouterInterface $router, + $itemPerPage = 20 ) { $this->itemPerPage = $itemPerPage; $this->requestStack = $requestStack; $this->router = $router; } - + /** - * create a paginator instance + * create a paginator instance. * * The default route and route parameters are the current ones. If set, * thos route are overriden. @@ -77,70 +66,70 @@ class PaginatorFactory * @param int $totalItems * @param string|null $route the specific route to use in pages * @param array|null $routeParameters the specific route parameters to use in pages + * * @return PaginatorInterface */ public function create( - $totalItems, - $route = null, - array $routeParameters = null + $totalItems, + $route = null, + ?array $routeParameters = null ) { - return new Paginator( - $totalItems, - $this->getCurrentItemsPerPage(), - $this->getCurrentPageNumber(), - $route === null ? $this->getCurrentRoute() : $route, - $routeParameters === null ? $this->getCurrentRouteParameters() : + $totalItems, + $this->getCurrentItemsPerPage(), + $this->getCurrentPageNumber(), + null === $route ? $this->getCurrentRoute() : $route, + null === $routeParameters ? $this->getCurrentRouteParameters() : $routeParameters, - $this->router, - self::DEFAULT_CURRENT_PAGE_KEY, - self::DEFAULT_ITEM_PER_NUMBER_KEY); + $this->router, + self::DEFAULT_CURRENT_PAGE_KEY, + self::DEFAULT_ITEM_PER_NUMBER_KEY + ); } - - /** - * - * @return int - */ - public function getCurrentPageNumber() - { - return $this->requestStack - ->getCurrentRequest() - ->query - ->getInt(self::DEFAULT_CURRENT_PAGE_KEY, self::DEFAULT_PAGE_NUMBER); - } - + public function getCurrentItemsPerPage() { return $this->requestStack - ->getCurrentRequest() - ->query - ->getInt(self::DEFAULT_ITEM_PER_NUMBER_KEY, $this->itemPerPage); + ->getCurrentRequest() + ->query + ->getInt(self::DEFAULT_ITEM_PER_NUMBER_KEY, $this->itemPerPage); } - + public function getCurrentPageFirstItemNumber() { return ($this->getCurrentPageNumber() - 1) * $this->getCurrentItemsPerPage(); } - + + /** + * @return int + */ + public function getCurrentPageNumber() + { + return $this->requestStack + ->getCurrentRequest() + ->query + ->getInt(self::DEFAULT_CURRENT_PAGE_KEY, self::DEFAULT_PAGE_NUMBER); + } + protected function getCurrentRoute() { $request = $this->requestStack->getCurrentRequest(); - + return $request->get('_route'); } - + protected function getCurrentRouteParameters() { return array_merge( - $this->router->getContext()->getParameters(), + $this->router->getContext()->getParameters(), // get the route parameters $this->requestStack - ->getCurrentRequest() - ->attributes->get('_route_params'), + ->getCurrentRequest() + ->attributes->get('_route_params'), // get the query parameters $this->requestStack - ->getCurrentRequest()->query->all() - ); + ->getCurrentRequest()->query->all() + ); } } diff --git a/src/Bundle/ChillMainBundle/Pagination/PaginatorInterface.php b/src/Bundle/ChillMainBundle/Pagination/PaginatorInterface.php index b9ee9c8a6..bf7130516 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PaginatorInterface.php +++ b/src/Bundle/ChillMainBundle/Pagination/PaginatorInterface.php @@ -1,27 +1,22 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Pagination; +use Countable; +use Generator; +use RuntimeException; + /** - * Represent a set of numbered pages + * Represent a set of numbered pages. * * Allow to calculate and render pagination for a set of pages. * @@ -29,108 +24,108 @@ namespace Chill\MainBundle\Pagination; * into pages. Each page is numbered and count a given number of item per page. * * The first page number is 1, although the first result number is 0. - * - * @author Julien Fastré */ -interface PaginatorInterface extends \Countable +interface PaginatorInterface extends Countable { - /** - * get the number of results for this paginator - * - * @return int - */ - public function getTotalItems(); - - /** - * get the first result for the current page - * - * @return int - */ - public function getCurrentPageFirstItemNumber(); - - /* - * get the number of items per page - */ - public function getItemsPerPage(); - - /* - * set the number of items per page - */ - public function setItemsPerPage($itemsPerPage); - /** * get the number of pages for this pagination. * * @return int */ public function countPages(); - + /** - * get the current page + * get the current page. * * @return PageInterface */ public function getCurrentPage(); - + /** - * check if the given page is the current page + * get the first result for the current page. * - * @param \Chill\MainBundle\Pagination\PageInterface $page - * @return bool + * @return int */ - public function isCurrentPage(PageInterface $page); - + public function getCurrentPageFirstItemNumber(); + + /* + * get the number of items per page + */ + public function getItemsPerPage(); + /** - * check if the page with the given number exists + * get the next page. * - * @param int $number - */ - public function hasPage($number); - - /** - * get page by his number - * - * @param int $number - * @throws \RuntimeException if the pagination has no page with specified number - */ - public function getPage($number); - - /** - * get the next page + * @throws RuntimeException if the pagination has not next page * * @return PageInterface - * @throws \RuntimeException if the pagination has not next page */ public function getNextPage(); - + /** - * get the previous page + * get page by his number. + * + * @param int $number + * + * @throws RuntimeException if the pagination has no page with specified number + */ + public function getPage($number); + + /** + * get a generator to generate pages. + * + * @return Generator which return PageInterface elements + */ + public function getPagesGenerator(); + + /** + * get the previous page. + * + * @throws RuntimeException if the pagination has not previous page * * @return PageInterface - * @throws \RuntimeException if the pagination has not previous page */ public function getPreviousPage(); - + /** - * check if the current page has a next page + * get the number of results for this paginator. + * + * @return int + */ + public function getTotalItems(); + + /** + * check if the current page has a next page. * * @return bool */ public function hasNextPage(); - + /** - * check if the current page has a page before + * check if the page with the given number exists. + * + * @param int $number + */ + public function hasPage($number); + + /** + * check if the current page has a page before. * * @return bool */ public function hasPreviousPage(); - + /** - * get a generator to generate pages + * check if the given page is the current page. * - * @return \Generator which return PageInterface elements + * @param \Chill\MainBundle\Pagination\PageInterface $page + * + * @return bool */ - public function getPagesGenerator(); - - + public function isCurrentPage(PageInterface $page); + + /* + * set the number of items per page + */ + public function setItemsPerPage($itemsPerPage); } diff --git a/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php b/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php index c2ab59442..08243e560 100644 --- a/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php +++ b/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php @@ -1,28 +1,27 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Phonenumber; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Exception\ServerException; use GuzzleHttp\Exception\ConnectException; -use Psr\Log\LoggerInterface; +use GuzzleHttp\Exception\ServerException; use Psr\Cache\CacheItemPoolInterface; +use Psr\Log\LoggerInterface; +use function array_key_exists; +use function in_array; +use function json_decode; +use function preg_replace; +use function strlen; /** * Helper to some task linked to phonenumber. @@ -30,27 +29,26 @@ use Psr\Cache\CacheItemPoolInterface; * Currently, only Twilio is supported (https://www.twilio.com/lookup). A method * allow to check if the helper is configured for validation. This should be used * before doing some validation. - * - * */ class PhonenumberHelper { + public const FORMAT_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s'; + + public const LOOKUP_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s'; + + protected CacheItemPoolInterface $cachePool; + /** - * Twilio client - */ - protected Client $twilioClient; - - /** - * TRUE if the client is properly configured + * TRUE if the client is properly configured. */ protected bool $isConfigured = false; protected LoggerInterface $logger; - protected CacheItemPoolInterface $cachePool; - - const LOOKUP_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s'; - const FORMAT_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s'; + /** + * Twilio client. + */ + protected Client $twilioClient; public function __construct( CacheItemPoolInterface $cachePool, @@ -60,107 +58,18 @@ class PhonenumberHelper $this->logger = $logger; $this->cachePool = $cachePool; - if (\array_key_exists('twilio_sid', $config) + if (array_key_exists('twilio_sid', $config) && !empty($config['twilio_sid']) && strlen($config['twilio_sid']) > 2 - && \array_key_exists('twilio_secret', $config) + && array_key_exists('twilio_secret', $config) && !empty($config['twilio_secret']) && strlen($config['twilio_secret']) > 2 ) { - $this->twilioClient = new Client([ - 'auth' => [ $config['twilio_sid'], $config['twilio_secret'] ] + 'auth' => [$config['twilio_sid'], $config['twilio_secret']], ]); - $this->isConfigured = TRUE; + $this->isConfigured = true; } - - } - - /** - * Return true if the validation is configured and available. - * - * @return bool - */ - public function isPhonenumberValidationConfigured() : bool - { - return $this->isConfigured; - } - - /** - * REturn true if the phoennumber is a mobile phone. Return always true - * if the validation is not configured. - * - * @param string $phonenumber - * @return bool - */ - public function isValidPhonenumberMobile($phonenumber) : bool - { - if (FALSE === $this->isPhonenumberValidationConfigured()) { - return true; - } - - $validation = $this->performTwilioLookup($phonenumber); - - if (NULL === $validation) { - return true; - } - - return $validation === 'mobile'; - } - - /** - * Return true if the phonenumber is a landline or voip phone. Return always true - * if the validation is not configured. - * - * @param string $phonenumber - * @return bool - */ - public function isValidPhonenumberLandOrVoip($phonenumber) : bool - { - if (FALSE === $this->isPhonenumberValidationConfigured()) { - return true; - } - - $validation = $this->performTwilioLookup($phonenumber); - - if (NULL === $validation) { - return true; - } - - return \in_array($validation, [ 'landline', 'voip' ]); - } - - /** - * Return true if the phonenumber is a landline or voip phone. Return always true - * if the validation is not configured. - * - * @param string $phonenumber - * @return bool - */ - public function isValidPhonenumberAny($phonenumber) : bool - { - if (FALSE === $this->isPhonenumberValidationConfigured()) { - return true; - } - $validation = $this->performTwilioLookup($phonenumber); -; - if (NULL === $validation) { - return false; - } - - return \in_array($validation, [ 'landline', 'voip', 'mobile' ]); - } - - /** - * Get type (mobile, landline, ...) for phone number - * - * @param string $phonenumber - * - * @return string - */ - public function getType(string $phonenumber): string - { - return $this->performTwilioLookup($phonenumber) ?? 'unknown'; } public function format($phonenumber) @@ -168,63 +77,139 @@ class PhonenumberHelper return $this->performTwilioFormat($phonenumber); } + /** + * Get type (mobile, landline, ...) for phone number. + */ + public function getType(string $phonenumber): string + { + return $this->performTwilioLookup($phonenumber) ?? 'unknown'; + } + + /** + * Return true if the validation is configured and available. + */ + public function isPhonenumberValidationConfigured(): bool + { + return $this->isConfigured; + } + + /** + * Return true if the phonenumber is a landline or voip phone. Return always true + * if the validation is not configured. + * + * @param string $phonenumber + */ + public function isValidPhonenumberAny($phonenumber): bool + { + if (false === $this->isPhonenumberValidationConfigured()) { + return true; + } + $validation = $this->performTwilioLookup($phonenumber); + + if (null === $validation) { + return false; + } + + return in_array($validation, ['landline', 'voip', 'mobile'], true); + } + + /** + * Return true if the phonenumber is a landline or voip phone. Return always true + * if the validation is not configured. + * + * @param string $phonenumber + */ + public function isValidPhonenumberLandOrVoip($phonenumber): bool + { + if (false === $this->isPhonenumberValidationConfigured()) { + return true; + } + + $validation = $this->performTwilioLookup($phonenumber); + + if (null === $validation) { + return true; + } + + return in_array($validation, ['landline', 'voip'], true); + } + + /** + * REturn true if the phoennumber is a mobile phone. Return always true + * if the validation is not configured. + * + * @param string $phonenumber + */ + public function isValidPhonenumberMobile($phonenumber): bool + { + if (false === $this->isPhonenumberValidationConfigured()) { + return true; + } + + $validation = $this->performTwilioLookup($phonenumber); + + if (null === $validation) { + return true; + } + + return 'mobile' === $validation; + } + protected function performTwilioFormat($phonenumber) { - if (FALSE === $this->isPhonenumberValidationConfigured()) { + if (false === $this->isPhonenumberValidationConfigured()) { return $phonenumber; } // filter only number - $filtered = \preg_replace("/[^0-9]/", "", $phonenumber); + $filtered = preg_replace('/[^0-9]/', '', $phonenumber); - $item = $this->cachePool->getItem('pnum_format_nat_'.$filtered); + $item = $this->cachePool->getItem('pnum_format_nat_' . $filtered); if ($item->isHit()) { return $item->get(); } try { - $response = $this->twilioClient->get(sprintf(self::FORMAT_URI, '+'.$filtered), [ + $response = $this->twilioClient->get(sprintf(self::FORMAT_URI, '+' . $filtered), [ 'http_errors' => true, ]); - - } catch (ClientException $e) { + } catch (ClientException $e) { $response = $e->getResponse(); - $this->logger->error("[phonenumber helper] Could not format number " - . "due to client error", [ - "message" => $response->getBody()->getContents(), - "status_code" => $response->getStatusCode(), - "phonenumber" => $phonenumber - ]); + $this->logger->error('[phonenumber helper] Could not format number ' + . 'due to client error', [ + 'message' => $response->getBody()->getContents(), + 'status_code' => $response->getStatusCode(), + 'phonenumber' => $phonenumber, + ]); return $phonenumber; } catch (ServerException $e) { $response = $e->getResponse(); - $this->logger->error("[phonenumber helper] Could not format number " - . "due to server error", [ - "message" => $response->getBody()->getContents(), - "status_code" => $response->getStatusCode(), - "phonenumber" => $phonenumber - ]); + $this->logger->error('[phonenumber helper] Could not format number ' + . 'due to server error', [ + 'message' => $response->getBody()->getContents(), + 'status_code' => $response->getStatusCode(), + 'phonenumber' => $phonenumber, + ]); return null; } catch (ConnectException $e) { - $this->logger->error("[phonenumber helper] Could not format number " - . "due to connect error", [ - "message" => $e->getMessage(), - "phonenumber" => $phonenumber - ]); + $this->logger->error('[phonenumber helper] Could not format number ' + . 'due to connect error', [ + 'message' => $e->getMessage(), + 'phonenumber' => $phonenumber, + ]); return null; } - $format = \json_decode($response->getBody())->national_format; + $format = json_decode($response->getBody()->getContents())->national_format; $item ->set($format) // expires after 3d - ->expiresAfter(3600 * 24 * 3) - ; + ->expiresAfter(3600 * 24 * 3); $this->cachePool->save($item); @@ -233,55 +218,54 @@ class PhonenumberHelper protected function performTwilioLookup($phonenumber) { - if (FALSE === $this->isPhonenumberValidationConfigured()) { + if (false === $this->isPhonenumberValidationConfigured()) { return null; } // filter only number - $filtered = \preg_replace("/[^0-9]/", "", $phonenumber); + $filtered = preg_replace('/[^0-9]/', '', $phonenumber); - $item = $this->cachePool->getItem('pnum_'.$filtered); + $item = $this->cachePool->getItem('pnum_' . $filtered); if ($item->isHit()) { //return $item->get(); } try { - $response = $this->twilioClient->get(sprintf(self::LOOKUP_URI, '+'.$filtered), [ + $response = $this->twilioClient->get(sprintf(self::LOOKUP_URI, '+' . $filtered), [ 'http_errors' => true, 'query' => [ - 'Type' => 'carrier' - ] + 'Type' => 'carrier', + ], ]); - } catch (ClientException $e) { + } catch (ClientException $e) { return 'invalid'; } catch (ServerException $e) { $response = $e->getResponse(); - $this->logger->error("[phonenumber helper] Could not perform validation " - . "due to server error", [ - "message" => $response->getBody()->getContents(), - "status_code" => $response->getStatusCode(), - "phonenumber" => $phonenumber - ]); + $this->logger->error('[phonenumber helper] Could not perform validation ' + . 'due to server error', [ + 'message' => $response->getBody()->getContents(), + 'status_code' => $response->getStatusCode(), + 'phonenumber' => $phonenumber, + ]); return null; } catch (ConnectException $e) { - $this->logger->error("[phonenumber helper] Could not format number " - . "due to connect error", [ - "message" => $e->getMessage(), - "phonenumber" => $phonenumber - ]); + $this->logger->error('[phonenumber helper] Could not format number ' + . 'due to connect error', [ + 'message' => $e->getMessage(), + 'phonenumber' => $phonenumber, + ]); return null; } - $validation = \json_decode($response->getBody())->carrier->type; + $validation = json_decode($response->getBody()->getContents())->carrier->type; $item ->set($validation) // expires after 12h - ->expiresAfter(3600 * 12) - ; + ->expiresAfter(3600 * 12); $this->cachePool->save($item); diff --git a/src/Bundle/ChillMainBundle/Phonenumber/Templating.php b/src/Bundle/ChillMainBundle/Phonenumber/Templating.php index 45bfceae0..414732263 100644 --- a/src/Bundle/ChillMainBundle/Phonenumber/Templating.php +++ b/src/Bundle/ChillMainBundle/Phonenumber/Templating.php @@ -1,52 +1,40 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Phonenumber; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; -use Chill\MainBundle\Phonenumber\PhonenumberHelper; -/** - * - * - */ class Templating extends AbstractExtension { /** - * * @var PhonenumberHelper */ protected $phonenumberHelper; - + public function __construct(PhonenumberHelper $phonenumberHelper) { $this->phonenumberHelper = $phonenumberHelper; } - public function getFilters() - { - return [ - new TwigFilter('chill_format_phonenumber', [$this, 'formatPhonenumber']) - ]; - } - public function formatPhonenumber($phonenumber) { return $this->phonenumberHelper->format($phonenumber) ?? $phonenumber; } + + public function getFilters() + { + return [ + new TwigFilter('chill_format_phonenumber', [$this, 'formatPhonenumber']), + ]; + } } diff --git a/src/Bundle/ChillMainBundle/Redis/ChillRedis.php b/src/Bundle/ChillMainBundle/Redis/ChillRedis.php index a196d2b60..2ae13b2ac 100644 --- a/src/Bundle/ChillMainBundle/Redis/ChillRedis.php +++ b/src/Bundle/ChillMainBundle/Redis/ChillRedis.php @@ -1,26 +1,21 @@ - * - * 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\MainBundle\Redis; /** - * Redis client configured by chill main + * Chill is a software for social workers * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ -class ChillRedis extends \Redis + +declare(strict_types=1); + +namespace Chill\MainBundle\Redis; + +use Redis; + +/** + * Redis client configured by chill main. + */ +class ChillRedis extends Redis { } diff --git a/src/Bundle/ChillMainBundle/Redis/RedisConnectionFactory.php b/src/Bundle/ChillMainBundle/Redis/RedisConnectionFactory.php index 51f2d2c6d..6d61c408c 100644 --- a/src/Bundle/ChillMainBundle/Redis/RedisConnectionFactory.php +++ b/src/Bundle/ChillMainBundle/Redis/RedisConnectionFactory.php @@ -1,78 +1,59 @@ - * - * 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\MainBundle\Redis; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ -class RedisConnectionFactory implements EventSubscriberInterface + +declare(strict_types=1); + +namespace Chill\MainBundle\Redis; + +use RuntimeException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +final class RedisConnectionFactory implements EventSubscriberInterface { - protected $host; - - protected $port; - - protected $timeout; - - /** - * - * @var Redis - */ - protected $redis; - + private string $host; + + private int $port; + + private ChillRedis $redis; + + private int $timeout; + public function __construct($parameters) { $this->host = $parameters['host']; - $this->port = $parameters['port']; - $this->timeout = $parameters['timeout']; + $this->port = (int) $parameters['port']; + $this->timeout = (int) $parameters['timeout']; + $this->redis = new ChillRedis(); } - - + + public function create() + { + $result = $this->redis->connect($this->host, $this->port, $this->timeout); + + if (false === $result) { + throw new RuntimeException('Could not connect to redis instance'); + } + + return $this->redis; + } + public static function getSubscribedEvents(): array { return [ 'kernel.finish_request' => [ - [ 'onKernelFinishRequest' ] - ] + ['onKernelFinishRequest'], + ], ]; } - - public function create() - { - $redis = $this->redis = new ChillRedis(); - - $result = $redis->connect($this->host, $this->port, $this->timeout); - - if (FALSE === $result) { - throw new \RuntimeException("Could not connect to redis instance"); - } - - return $redis; - } - + public function onKernelFinishRequest() { - if ($this->redis !== null) { - $this->redis->close(); - } + $this->redis->close(); } - } diff --git a/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php b/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php index cf488f411..6be52ec85 100644 --- a/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php @@ -1,39 +1,40 @@ repository = $entityManager->getRepository(AddressReference::class); - } - - public function find($id, $lockMode = null, $lockVersion = null): ?AddressReference - { - return $this->repository->find($id, $lockMode, $lockVersion); - } - - public function findOneBy(array $criteria, array $orderBy = null): ?AddressReference - { - return $this->repository->findOneBy($criteria, $orderBy); - } - - /** - * @return AddressReference[] - */ - public function findAll(): array - { - return $this->repository->findAll(); + $this->entityManager = $entityManager; } public function countAll(): int @@ -44,15 +45,116 @@ final class AddressReferenceRepository implements ObjectRepository return $qb->getQuery()->getSingleScalarResult(); } + public function countByPostalCodePattern(PostalCode $postalCode, string $pattern): int + { + $query = $this->buildQueryByPostalCodePattern($postalCode, $pattern); + $sql = $query->buildQuery(true); + $rsm = new ResultSetMapping(); + $rsm->addScalarResult('c', 'c'); + + $nq = $this->entityManager->createNativeQuery($sql, $rsm)->setParameters($query->buildParameters(true)); + + return (int) $nq->getSingleResult()['c']; + } + + public function find($id, $lockMode = null, $lockVersion = null): ?AddressReference + { + return $this->repository->find($id, $lockMode, $lockVersion); + } + /** * @return AddressReference[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return AddressReference[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + /** + * @return AddressReference[]|array + */ + public function findByPostalCodePattern(PostalCode $postalCode, string $pattern, bool $simplify = false, int $start = 0, int $limit = 50): array + { + $query = $this->buildQueryByPostalCodePattern($postalCode, $pattern); + + if (!$simplify) { + $rsm = new ResultSetMappingBuilder($this->entityManager); + $rsm->addRootEntityFromClassMetadata(AddressReference::class, 'cma'); + $query->addSelectClause($rsm->generateSelectClause()); + } else { + throw new RuntimeException('not implemented'); + } + + $sql = strtr( + $query->buildQuery() . 'ORDER BY pertinence DESC, lpad(streetnumber, 10, \'0\') ASC OFFSET ? LIMIT ? ', + // little hack for adding sql method to point + ['cma.point AS point' => 'ST_AsGeojson(cma.point) AS point'] + ); + $parameters = [...$query->buildParameters(), $start, $limit]; + + return $this->entityManager->createNativeQuery($sql, $rsm) + ->setParameters($parameters) + ->getResult(); + } + + public function findOneBy(array $criteria, ?array $orderBy = null): ?AddressReference + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return AddressReference::class; } + + private function buildQueryByPostalCodePattern(PostalCode $postalCode, string $pattern): SearchApiQuery + { + $pattern = trim($pattern); + + if ('' === $pattern) { + throw new RuntimeException('the search pattern must not be empty'); + } + $query = new SearchApiQuery(); + + $query + ->setFromClause('chill_main_address_reference cma') + ->andWhereClause('postcode_id = ?', [$postalCode->getId()]); + + $pertinenceClause = ['STRICT_WORD_SIMILARITY(addresscanonical, UNACCENT(?))']; + $pertinenceArgs = [$pattern]; + $orWhere = ['addresscanonical %>> UNACCENT(?)']; + $orWhereArgs = [$pattern]; + + foreach (explode(' ', $pattern) as $part) { + $part = trim($part); + + if ('' === $part) { + continue; + } + + $orWhere[] = "addresscanonical LIKE '%' || UNACCENT(LOWER(?)) || '%'"; + $orWhereArgs[] = $part; + $pertinenceClause[] = + "(EXISTS (SELECT 1 FROM unnest(string_to_array(addresscanonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int"; + $pertinenceClause[] = + '(addresscanonical LIKE UNACCENT(LOWER(?)))::int'; + array_push($pertinenceArgs, $part, $part); + } + $query + ->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs) + ->andWhereClause(implode(' OR ', $orWhere), $orWhereArgs); + + return $query; + } } diff --git a/src/Bundle/ChillMainBundle/Repository/AddressRepository.php b/src/Bundle/ChillMainBundle/Repository/AddressRepository.php index 08e2da708..a6d739de4 100644 --- a/src/Bundle/ChillMainBundle/Repository/AddressRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/AddressRepository.php @@ -1,5 +1,12 @@ repository = $entityManager->getRepository(Address::class); } + public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder + { + return $this->repository->createQueryBuilder($alias, $indexBy); + } + public function find($id, $lockMode = null, $lockVersion = null): ?Address { return $this->repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?Address - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return Address[] */ @@ -38,19 +45,23 @@ final class AddressRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return Address[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { - return Address::class; + public function findOneBy(array $criteria, ?array $orderBy = null): ?Address + { + return $this->repository->findOneBy($criteria, $orderBy); } - public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder + public function getClassName() { - return $this->repository->createQueryBuilder($alias, $indexBy); + return Address::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/CenterRepository.php b/src/Bundle/ChillMainBundle/Repository/CenterRepository.php index 3cf8cf38b..554f39880 100644 --- a/src/Bundle/ChillMainBundle/Repository/CenterRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/CenterRepository.php @@ -1,5 +1,12 @@ repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?Center - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return Center[] */ @@ -37,14 +39,23 @@ final class CenterRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return Center[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + public function findOneBy(array $criteria, ?array $orderBy = null): ?Center + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return Center::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/CivilityRepository.php b/src/Bundle/ChillMainBundle/Repository/CivilityRepository.php index 2702784f0..3604f25d0 100644 --- a/src/Bundle/ChillMainBundle/Repository/CivilityRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/CivilityRepository.php @@ -1,25 +1,14 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\Civility; @@ -38,5 +27,4 @@ class CivilityRepository extends ServiceEntityRepository { parent::__construct($registry, Civility::class); } - } diff --git a/src/Bundle/ChillMainBundle/Repository/CountryRepository.php b/src/Bundle/ChillMainBundle/Repository/CountryRepository.php index 336768948..81bea648d 100644 --- a/src/Bundle/ChillMainBundle/Repository/CountryRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/CountryRepository.php @@ -1,5 +1,12 @@ repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?Country - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return Country[] */ @@ -37,14 +39,23 @@ final class CountryRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return Country[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + public function findOneBy(array $criteria, ?array $orderBy = null): ?Country + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return Country::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/GroupCenterRepository.php b/src/Bundle/ChillMainBundle/Repository/GroupCenterRepository.php index 6faed8e47..f8b11ceac 100644 --- a/src/Bundle/ChillMainBundle/Repository/GroupCenterRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/GroupCenterRepository.php @@ -1,5 +1,12 @@ repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?GroupCenter - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return GroupCenter[] */ @@ -37,14 +39,23 @@ final class GroupCenterRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return GroupCenter[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + public function findOneBy(array $criteria, ?array $orderBy = null): ?GroupCenter + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return GroupCenter::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/LanguageRepository.php b/src/Bundle/ChillMainBundle/Repository/LanguageRepository.php index f6a7ec284..88e00a6c4 100644 --- a/src/Bundle/ChillMainBundle/Repository/LanguageRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/LanguageRepository.php @@ -1,5 +1,12 @@ repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?Language - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return Language[] */ @@ -37,14 +39,23 @@ final class LanguageRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return Language[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + public function findOneBy(array $criteria, ?array $orderBy = null): ?Language + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return Language::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/LocationRepository.php b/src/Bundle/ChillMainBundle/Repository/LocationRepository.php index 8e67fecea..253298d49 100644 --- a/src/Bundle/ChillMainBundle/Repository/LocationRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/LocationRepository.php @@ -1,5 +1,14 @@ findBy(['active' => true, 'availableForUsers' => true]); + } } diff --git a/src/Bundle/ChillMainBundle/Repository/LocationTypeRepository.php b/src/Bundle/ChillMainBundle/Repository/LocationTypeRepository.php index 80e2d020d..e8d5579d0 100644 --- a/src/Bundle/ChillMainBundle/Repository/LocationTypeRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/LocationTypeRepository.php @@ -1,5 +1,14 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\Notification; +use Chill\MainBundle\Entity\User; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; -use Doctrine\Persistence\ObjectRepository; -use Chill\MainBundle\Entity\User; use Doctrine\ORM\Query; +use Doctrine\Persistence\ObjectRepository; final class NotificationRepository implements ObjectRepository { @@ -35,16 +27,18 @@ final class NotificationRepository implements ObjectRepository $this->repository = $entityManager->getRepository(Notification::class); } + public function countAllForAttendee(User $addressee): int // TODO passer à attendees avec S + { + $query = $this->queryAllForAttendee($addressee, $countQuery = true); + + return $query->getSingleScalarResult(); + } + public function find($id, $lockMode = null, $lockVersion = null): ?Notification { return $this->repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?Notification - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return Notification[] */ @@ -54,19 +48,54 @@ final class NotificationRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return Notification[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findAllForAttendee(User $addressee, $limit = null, $offset = null): array // TODO passer à attendees avec S + { + $query = $this->queryAllForAttendee($addressee); + + if ($limit) { + $query = $query->setMaxResults($limit); + } + + if ($offset) { + $query = $query->setFirstResult($offset); + } + + return $query->getResult(); + } + + /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return Notification[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - private function queryAllForAttendee(User $addressee, bool $countQuery=False): Query + public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { + return Notification::class; + } + + private function queryAllForAttendee(User $addressee, bool $countQuery = false): Query { $qb = $this->repository->createQueryBuilder('n'); $select = 'n'; - if($countQuery) { + + if ($countQuery) { $select = 'count(n)'; } @@ -78,37 +107,4 @@ final class NotificationRepository implements ObjectRepository return $qb->getQuery(); } - - /** - * @return int - */ - public function countAllForAttendee(User $addressee): int // TODO passer à attendees avec S - { - $query = $this->queryAllForAttendee($addressee, $countQuery=True); - - return $query->getSingleScalarResult(); - } - - - /** - * @return Notification[] - */ - public function findAllForAttendee(User $addressee, $limit = null, $offset = null): array // TODO passer à attendees avec S - { - $query = $this->queryAllForAttendee($addressee); - - if($limit) { - $query = $query->setMaxResults($limit); - } - - if($offset) { - $query = $query->setFirstResult($offset); - } - - return $query->getResult(); - } - - public function getClassName() { - return Notification::class; - } } diff --git a/src/Bundle/ChillMainBundle/Repository/PermissionsGroupRepository.php b/src/Bundle/ChillMainBundle/Repository/PermissionsGroupRepository.php index 0bb4c7bfd..18f8ff777 100644 --- a/src/Bundle/ChillMainBundle/Repository/PermissionsGroupRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/PermissionsGroupRepository.php @@ -1,5 +1,12 @@ repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?PermissionsGroup - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return PermissionsGroup[] */ @@ -37,14 +39,23 @@ final class PermissionsGroupRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return PermissionsGroup[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + public function findOneBy(array $criteria, ?array $orderBy = null): ?PermissionsGroup + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return PermissionsGroup::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php index 0087c9aa9..02e63771b 100644 --- a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php @@ -1,21 +1,49 @@ repository = $entityManager->getRepository(PostalCode::class); + $this->entityManager = $entityManager; + } + + public function countByPattern(string $pattern, ?Country $country): int + { + $query = $this->buildQueryByPattern($pattern, $country); + $sql = $query->buildQuery(true); + $rsm = new ResultSetMapping(); + $rsm->addScalarResult('c', 'c'); + + $nq = $this->entityManager->createNativeQuery($sql, $rsm) + ->setParameters($query->buildParameters(true)); + + return (int) $nq->getSingleResult()['c']; } public function find($id, $lockMode = null, $lockVersion = null): ?PostalCode @@ -23,11 +51,6 @@ final class PostalCodeRepository implements ObjectRepository return $this->repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?PostalCode - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return PostalCode[] */ @@ -37,14 +60,87 @@ final class PostalCodeRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return PostalCode[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + public function findByPattern(string $pattern, ?Country $country, ?int $start = 0, ?int $limit = 50): array + { + $query = $this->buildQueryByPattern($pattern, $country); + + $rsm = new ResultSetMappingBuilder($this->entityManager); + $rsm->addRootEntityFromClassMetadata(PostalCode::class, 'cmpc'); + $query->addSelectClause($rsm->generateSelectClause()); + + $sql = strtr( + $query->buildQuery() . 'ORDER BY pertinence DESC, canonical ASC OFFSET ? LIMIT ? ', + // little hack for adding sql method to point + ['cmpc.center AS center' => 'ST_AsGeojson(cmpc.center) AS center'] + ); + $parameters = [...$query->buildParameters(), $start, $limit]; + + return $this->entityManager->createNativeQuery($sql, $rsm) + ->setParameters($parameters) + ->getResult(); + } + + public function findOneBy(array $criteria, ?array $orderBy = null): ?PostalCode + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return PostalCode::class; } + + private function buildQueryByPattern(string $pattern, ?Country $country): SearchApiQuery + { + $pattern = trim($pattern); + + if ('' === $pattern) { + throw new RuntimeException('the search pattern must not be empty'); + } + $query = new SearchApiQuery(); + + $query + ->setFromClause('chill_main_postal_code cmpc') + ->andWhereClause('cmpc.origin = 0'); + + if (null !== $country) { + $query->andWhereClause('cmpc.country_id = ?', [$country->getId()]); + } + + $pertinenceClause = ['STRICT_WORD_SIMILARITY(canonical, UNACCENT(?))']; + $pertinenceArgs = [$pattern]; + $orWhere = ['canonical %>> UNACCENT(?)']; + $orWhereArgs = [$pattern]; + + foreach (explode(' ', $pattern) as $part) { + $part = trim($part); + + if ('' === $part) { + continue; + } + + $orWhere[] = "canonical LIKE '%' || UNACCENT(LOWER(?)) || '%'"; + $orWhereArgs[] = $part; + $pertinenceClause[] = + "(EXISTS (SELECT 1 FROM unnest(string_to_array(canonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int"; + $pertinenceClause[] = + '(canonical LIKE UNACCENT(LOWER(?)))::int'; + array_push($pertinenceArgs, $part, $part); + } + $query + ->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs) + ->andWhereClause(implode(' OR ', $orWhere), $orWhereArgs); + + return $query; + } } diff --git a/src/Bundle/ChillMainBundle/Repository/RoleScopeRepository.php b/src/Bundle/ChillMainBundle/Repository/RoleScopeRepository.php index 6996f384c..2475f9497 100644 --- a/src/Bundle/ChillMainBundle/Repository/RoleScopeRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/RoleScopeRepository.php @@ -1,5 +1,12 @@ repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?RoleScope - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return RoleScope[] */ @@ -37,14 +39,23 @@ final class RoleScopeRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return RoleScope[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + public function findOneBy(array $criteria, ?array $orderBy = null): ?RoleScope + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return RoleScope::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/ScopeRepository.php b/src/Bundle/ChillMainBundle/Repository/ScopeRepository.php index e100000c4..5b3efe658 100644 --- a/src/Bundle/ChillMainBundle/Repository/ScopeRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/ScopeRepository.php @@ -1,5 +1,12 @@ repository = $entityManager->getRepository(Scope::class); } + public function createQueryBuilder($alias, $indexBy = null) + { + return $this->repository->createQueryBuilder($alias, $indexBy); + } + public function find($id, $lockMode = null, $lockVersion = null): ?Scope { return $this->repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?Scope - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return Scope[] */ @@ -37,19 +44,23 @@ final class ScopeRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return Scope[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - - public function createQueryBuilder($alias, $indexBy = null) + + public function findOneBy(array $criteria, ?array $orderBy = null): ?Scope { - return $this->repository->createQueryBuilder($alias, $indexBy); + return $this->repository->findOneBy($criteria, $orderBy); } - public function getClassName() { + public function getClassName() + { return Scope::class; } } diff --git a/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepository.php b/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepository.php new file mode 100644 index 000000000..6dc446111 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepository.php @@ -0,0 +1,67 @@ +parentRoleHelper = $parentRoleHelper; + $this->em = $em; + } + + public function findUsersByReachedACL(string $role, $center, $scope = null, bool $onlyEnabled = true): array + { + $parents = $this->parentRoleHelper->getParentRoles($role); + $parents[] = $role; + + $qb = $this->em->createQueryBuilder(); + + $qb + ->select('u') + ->from(User::class, 'u') + ->join('u.groupCenters', 'gc') + ->join('gc.permissionsGroup', 'pg') + ->join('pg.roleScopes', 'rs') + ->where($qb->expr()->in('rs.role', $parents)); + + if ($onlyEnabled) { + $qb->andWhere($qb->expr()->eq('u.enabled', "'TRUE'")); + } + + if (null !== $center) { + $centers = $center instanceof Center ? [$center] : $center; + $qb + ->andWhere($qb->expr()->in('gc.center', ':centers')) + ->setParameter('centers', $centers); + } + + if (null !== $scope) { + $scopes = $scope instanceof Scope ? [$scope] : $scope; + $qb + ->andWhere($qb->expr()->in('rs.scope', ':scopes')) + ->setParameter('scopes', $scopes); + } + + return $qb->getQuery()->getResult(); + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepositoryInterface.php new file mode 100644 index 000000000..206096b96 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepositoryInterface.php @@ -0,0 +1,30 @@ +entityManager = $entityManager; $this->repository = $entityManager->getRepository(User::class); } - public function find($id, $lockMode = null, $lockVersion = null): ?User - { - return $this->repository->find($id, $lockMode, $lockVersion); - } - - public function findOneBy(array $criteria, array $orderBy = null): ?User - { - return $this->repository->findOneBy($criteria, $orderBy); - } - - /** - * @return User[] - */ - public function findAll(): array - { - return $this->repository->findAll(); - } - - /** - * @return User[] - */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array - { - return $this->repository->findBy($criteria, $orderBy, $limit, $offset); - } - public function countBy(array $criteria): int { return $this->repository->count($criteria); @@ -58,18 +41,6 @@ final class UserRepository implements ObjectRepository return $this->countBy(['enabled' => true]); } - /** - * @return User[]|array - */ - public function findByActive(array $orderBy = null, int $limit = null, int $offset = null): array - { - return $this->findBy(['enabled' => true], $orderBy, $limit, $offset); - } - - public function getClassName() { - return User::class; - } - public function countByUsernameOrEmail(string $pattern): int { $qb = $this->queryByUsernameOrEmail($pattern); @@ -79,6 +50,38 @@ final class UserRepository implements ObjectRepository return (int) $qb->getQuery()->getSingleScalarResult(); } + public function find($id, $lockMode = null, $lockVersion = null): ?User + { + return $this->repository->find($id, $lockMode, $lockVersion); + } + + /** + * @return User[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return User[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + /** + * @return array|User[] + */ + public function findByActive(?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->findBy(['enabled' => true], $orderBy, $limit, $offset); + } + public function findByUsernameOrEmail(string $pattern) { $qb = $this->queryByUsernameOrEmail($pattern); @@ -86,6 +89,11 @@ final class UserRepository implements ObjectRepository return $qb->getQuery()->getResult(); } + public function findOneBy(array $criteria, ?array $orderBy = null): ?User + { + return $this->repository->findOneBy($criteria, $orderBy); + } + public function findOneByUsernameOrEmail(string $pattern) { $qb = $this->queryByUsernameOrEmail($pattern); @@ -94,28 +102,29 @@ final class UserRepository implements ObjectRepository } /** - * Get the users having a specific flags + * Get the users having a specific flags. * * If provided, only the users amongst "filtered users" are searched. This * allows to make a first search amongst users based on role and center * and, then filter those users having some flags. * * @param \Chill\MainBundle\Entity\User[] $amongstUsers + * @param mixed $flag */ public function findUsersHavingFlags($flag, array $amongstUsers = []): array { $gcs = $this ->entityManager ->createQuery( - "SELECT DISTINCT gc " . - "FROM ".GroupCenter::class." gc " . - "JOIN gc.permissionsGroup pg " . - "WHERE " . - "JSONB_EXISTS_IN_ARRAY(pg.flags, :flag) = :true " + 'SELECT DISTINCT gc ' . + 'FROM ' . GroupCenter::class . ' gc ' . + 'JOIN gc.permissionsGroup pg ' . + 'WHERE ' . + 'JSONB_EXISTS_IN_ARRAY(pg.flags, :flag) = :true ' ) ->setParameters([ 'true' => true, - 'flag' => $flag + 'flag' => $flag, ]) ->getResult(); @@ -131,14 +140,14 @@ final class UserRepository implements ObjectRepository $orx = $qb->expr()->orX(); - foreach($gcs as $i => $gc) { + foreach ($gcs as $i => $gc) { $orx->add(':gc_' . $i . ' MEMBER OF u.groupCenters'); $qb->setParameter('gc_' . $i, $gc); } $qb->andWhere($orx); - if ($amongstUsers !== []) { + if ([] !== $amongstUsers) { $qb ->andWhere($qb->expr()->in('u', ':amongstUsers')) ->setParameter('amongstUsers', $amongstUsers); @@ -147,7 +156,12 @@ final class UserRepository implements ObjectRepository return $qb->getQuery()->getResult(); } - protected function queryByUsernameOrEmail(string $pattern): QueryBuilder + public function getClassName() + { + return User::class; + } + + private function queryByUsernameOrEmail(string $pattern): QueryBuilder { $qb = $this->entityManager->createQueryBuilder('u'); diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss index 593cd49b4..e7f613bc8 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss @@ -10,6 +10,9 @@ // Chill buttons @import './scss/buttons'; +// Chill badges +@import './scss/badge'; + // Chill forms @import './scss/forms'; @@ -202,9 +205,9 @@ footer.footer { } } - /* - * SPECIFIC RULES + * GENERIC MAIN STYLES + * miscellaneous */ /// titles @@ -280,7 +283,6 @@ div.metadata { } /// chill help tooltip - .chill-help-tooltip { &::before { content: '\f05a'; @@ -295,7 +297,6 @@ div.metadata { /// display definition list // with dt and dd on same line - dl.definition-inline { dd { display: inline; @@ -317,6 +318,19 @@ dl.definition-inline { font-style: italic; } +/// flash +div#flashMessages { + margin-top: 20px; + .flash-notice { + margin-top: 10px; + margin-bottom: 10px; + } +} + +/* + * SPECIFIC RULES + */ + //// still used ? // move from chillmain.css, converted to sass @@ -369,14 +383,6 @@ div#usefulbar { } } -div#flashMessages { - margin-top: 20px; - .flash-notice { - margin-top: 10px; - margin-bottom: 10px; - } -} - .personName { font-variant: small-caps; text-transform: capitalize; @@ -402,3 +408,11 @@ input.belgian_national_number { &.daily_counter {} &.control_digit {} } + +// replace abbr +span.item-key { + font-variant: all-small-caps; + font-size: 90%; + background-color: #0000000a; + //text-decoration: dotted underline; +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/badge.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/badge.scss new file mode 100644 index 000000000..4018e656b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/badge.scss @@ -0,0 +1,66 @@ + +/// mixin used in this sheet +@mixin remove_link { + cursor: pointer; + &:before { + font: normal normal normal 14px/1 ForkAwesome; + margin-left: 0.5em; + content: "\f00d"; // fa-times + color: var(--bs-danger); + text-decoration: none; + } +} + +/* +* SUGGESTIONS LIST - A specific list of badges used to manage suggestions (with add/remove link) +* /!\ use bootstrap badge styles, +* cfr. src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_custom.scss +*/ + +ul.list-suggest { + list-style-type: none; + padding-left: 0; + li > span { + white-space: normal; + text-align: start; + margin-bottom: 3px; + } + &.cols { + columns: 3; + } + &.inline { + li { + display: inline-block; + margin-right: 0.2em; + } + } + &.add-items { + li { + cursor: pointer; + & > span:before { + font: normal normal normal 14px/1 ForkAwesome; + margin-right: 0.5em; + content: "\f067"; // fa-plus + color: var(--bs-success); + } + & span:hover { + color: $chill-l-gray; + } + } + } + &.remove-items { + li { + a { + @include remove_link; + } + } + } +} + +/// manage remove link if it isn't a list but a title +/// (cfr. in Vue AccompanyingCourseWorkEdit) +div.item-title { + a { + @include remove_link; + } +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss index 8e3a9121b..20fe927a6 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss @@ -101,6 +101,10 @@ div.flex-table { div.item-row { flex-direction: row; + &.column { // exception + flex-direction: column + } + div.item-col { &:first-child { flex-grow: 0; flex-shrink: 0; flex-basis: auto; @@ -160,6 +164,12 @@ div.wrap-list { & > * { padding-right: 1em; } + + h3, h4 { + font-weight: 700; + font-size: 100%; + font-family: 'Open Sans'; + } } div.wl-col.list { @@ -171,6 +181,10 @@ div.wrap-list { padding: 0em; display: inline-block; } + + blockquote.chill-user-quote { + margin: 0.7em 0; + } } } @@ -183,7 +197,7 @@ div.wrap-list { div.wl-col.list { background-color: cyan; - p.wl-item { + .wl-item { background-color: orange; } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.js b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.js new file mode 100644 index 000000000..96a95ad93 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.js @@ -0,0 +1,105 @@ +/** + * Generic api method that can be adapted to any fetch request + */ + const makeFetch = (method, url, body) => { + return fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: (body !== null) ? JSON.stringify(body) : null + }) + .then(response => { + if (response.ok) { + return response.json(); + } + + if (response.status === 422) { + return response.json().then(response => { + throw ValidationException(response) + }); + } + + if (response.status === 403) { + return response.json().then(() => { + throw AccessException(); + }); + } + + throw { + name: 'Exception', + sta: response.status, + txt: response.statusText, + err: new Error(), + violations: response.body + }; + }); +} + +/** + * Fetch results with certain parameters + */ +const _fetchAction = (page, uri, params) => { + const item_per_page = 50; + if (params === undefined) { + params = {}; + } + let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params }); + + return fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + }).then(response => { + if (response.ok) { return response.json(); } + throw Error({ m: response.statusText }); + }); +}; + +const fetchResults = async (uri, params) => { + let promises = [], + page = 1; + let firstData = await _fetchAction(page, uri, params); + + promises.push(Promise.resolve(firstData.results)); + + if (firstData.pagination.more) { + do { + page = ++page; + promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results))); + } while (page * firstData.pagination.items_per_page < firstData.count) + } + + return Promise.all(promises).then(values => values.flat()); +}; + +const fetchScopes = () => { + return fetchResults('/api/1.0/main/scope.json'); +}; + + +/** + * Error objects to be thrown + */ +const ValidationException = (response) => { + const error = {}; + error.name = 'ValidationException'; + error.violations = response.violations.map((violation) => `${violation.title}`); + + return error; +} + +const AccessException = () => { + const error = {}; + error.name = 'AccessException'; + error.violations = ['You are no longer permitted to perform this action']; + + return error; +} + +export { + makeFetch, + fetchResults, + fetchScopes +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/download.js b/src/Bundle/ChillMainBundle/Resources/public/lib/api/download.js index 625103ebe..dec8ba727 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/download.js +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/download.js @@ -1,39 +1,39 @@ -const _fetchAction = (page, uri, params) => { - const item_per_page = 50; - if (params === undefined) { - params = {}; - } - let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params }); +// const _fetchAction = (page, uri, params) => { +// const item_per_page = 50; +// if (params === undefined) { +// params = {}; +// } +// let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params }); - return fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - }).then(response => { - if (response.ok) { return response.json(); } - throw Error({ m: response.statusText }); - }); -}; +// return fetch(url, { +// method: 'GET', +// headers: { +// 'Content-Type': 'application/json;charset=utf-8' +// }, +// }).then(response => { +// if (response.ok) { return response.json(); } +// throw Error({ m: response.statusText }); +// }); +// }; -const fetchResults = async (uri, params) => { - let promises = [], - page = 1; - let firstData = await _fetchAction(page, uri, params); +// const fetchResults = async (uri, params) => { +// let promises = [], +// page = 1; +// let firstData = await _fetchAction(page, uri, params); - promises.push(Promise.resolve(firstData.results)); +// promises.push(Promise.resolve(firstData.results)); - if (firstData.pagination.more) { - do { - page = ++page; - promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results))); - } while (page * firstData.pagination.items_per_page < firstData.count) - } +// if (firstData.pagination.more) { +// do { +// page = ++page; +// promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results))); +// } while (page * firstData.pagination.items_per_page < firstData.count) +// } - return Promise.all(promises).then(values => values.flat()); -}; +// return Promise.all(promises).then(values => values.flat()); +// }; -export { - fetchResults -}; +// export { +// fetchResults +// }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js b/src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js index 4c4223d77..a155c84a2 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js @@ -1,9 +1,9 @@ -import { fetchResults } from 'ChillMainAssets/lib/api/download.js'; +// import { fetchResults } from 'ChillMainAssets/lib/api/download.js'; -const fetchScopes = () => { - return fetchResults('/api/1.0/main/scope.json'); -}; +// const fetchScopes = () => { +// return fetchResults('/api/1.0/main/scope.json'); +// }; -export { - fetchScopes -}; +// export { +// fetchScopes +// }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/show_hide/index.js b/src/Bundle/ChillMainBundle/Resources/public/lib/show_hide/index.js index 34f3b80b5..88890ab40 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/show_hide/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/show_hide/index.js @@ -1 +1,5 @@ -require("./show_hide.js"); \ No newline at end of file +//require("./show_hide.js"); + +import { ShowHide } from './show_hide.js' + +export { ShowHide } \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/show_hide/show_hide.js b/src/Bundle/ChillMainBundle/Resources/public/lib/show_hide/show_hide.js index dd4dfd6db..ec6b796ec 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/show_hide/show_hide.js +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/show_hide/show_hide.js @@ -134,4 +134,4 @@ var ShowHide = function(options) { }; }; -export {ShowHide}; +export { ShowHide }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_custom.scss b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_custom.scss index 9d632026f..38998bfd5 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_custom.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/_custom.scss @@ -4,3 +4,12 @@ */ +/// A specific list of badges, use bootstrap badge styles +/// cfr. src/Bundle/ChillMainBundle/Resources/public/chill/scss/badge.scss +ul.list-suggest { + li > span { + @extend .badge; + @extend .bg-primary; + } +} + diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/index.js index daff13a55..936587dec 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/bootstrap/index.js @@ -8,3 +8,43 @@ require('./bootstrap.scss'); import Dropdown from 'bootstrap/js/src/dropdown'; import Modal from 'bootstrap/js/dist/modal'; import Collapse from 'bootstrap/js/src/collapse'; +import Carousel from 'bootstrap/js/src/carousel'; + +// +// ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section +// Initialize options, and show/hide controls in first/last slides +// +let ACHeaderSlider = document.querySelector('#ACHeaderSlider'); +if (ACHeaderSlider) { + let controlPrev = ACHeaderSlider.querySelector('button[data-bs-slide="prev"]'), + controlNext = ACHeaderSlider.querySelector('button[data-bs-slide="next"]'), + length = ACHeaderSlider.querySelectorAll('.carousel-item').length, + last = length-1, + carousel = new Carousel(ACHeaderSlider, { + interval: false, + wrap: false, + ride: false, + keyboard: false, + touch: true + }) + ; + document.addEventListener('DOMContentLoaded', (e) => { + controlNext.classList.remove('visually-hidden'); + }); + ACHeaderSlider.addEventListener('slid.bs.carousel', (e) => { + //console.log('from slide', e.direction, e.relatedTarget, e.from, e.to ); + switch (e.to) { + case 0: + controlPrev.classList.add('visually-hidden'); + controlNext.classList.remove('visually-hidden'); + break; + case last: + controlPrev.classList.remove('visually-hidden'); + controlNext.classList.add('visually-hidden'); + break; + default: + controlPrev.classList.remove('visually-hidden'); + controlNext.classList.remove('visually-hidden'); + } + }) +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/api.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/api.js index 62065b3ad..1dbc85dee 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/api.js +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/api.js @@ -16,7 +16,8 @@ const fetchCountries = () => { /** * Endpoint chill_api_single_postal_code__index -* method GET, get Country Object +* method GET, get Cities Object +* @params {object} a country object * @returns {Promise} a promise containing all Postal Code objects filtered with country */ const fetchCities = (country) => { @@ -29,6 +30,40 @@ const fetchCities = (country) => { }); }; + +/** +* Endpoint chill_main_postalcodeapi_search +* method GET, get Cities Object +* @params {string} search a search string +* @params {object} country a country object +* @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string +*/ +const searchCities = (search, country) => { + const url = `/api/1.0/main/postal-code/search.json?q=${search}&country=${country.id}`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/** +* Endpoint chill_main_addressreferenceapi_search +* method GET, get AddressReference Object +* @params {string} search a search string +* @params {object} postalCode a postalCode object +* @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string +*/ +const searchReferenceAddresses = (search, postalCode) => { + const url = `/api/1.0/main/address-reference/by-postal-code/${postalCode.id}/search.json?q=${search}`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + + /** * Endpoint chill_api_single_address_reference__index * method GET, get AddressReference Object @@ -170,5 +205,7 @@ export { postAddress, patchAddress, postPostalCode, - getAddress + getAddress, + searchCities, + searchReferenceAddresses }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue index 7bf088fb6..ed1d14633 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue @@ -556,8 +556,8 @@ export default { this.entity.selected.address.distribution = this.context.edit ? this.entity.address.distribution: null; this.entity.selected.address.extra = this.context.edit ? this.entity.address.extra: null; - this.entity.selected.writeNew.address = this.context.edit; - this.entity.selected.writeNew.postcode = this.context.edit; + this.entity.selected.writeNew.address = this.context.edit && this.entity.address.addressReference === null && this.entity.address.street.length > 0 + this.entity.selected.writeNew.postcode = false // NB: this used to be this.context.edit, but think it was erroneous; console.log('!! just set writeNew.postcode to', this.entity.selected.writeNew.postcode); }, @@ -569,7 +569,6 @@ export default { applyChanges() { console.log('apply changes'); - let newAddress = { 'isNoAddress': this.entity.selected.isNoAddress, 'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street, @@ -588,6 +587,12 @@ export default { newAddress = Object.assign(newAddress, { 'point': this.entity.selected.address.point.coordinates }); + } else { + if (this.entity.selected.postcode.coordinates) { + newAddress = Object.assign(newAddress, { + 'point': this.entity.selected.postcode.coordinates + }); + } } // add the address reference, if any @@ -627,7 +632,6 @@ export default { if (!this.context.edit) { this.addNewAddress(newAddress) .then(payload => this.addressChangedCallback(payload)); - } else { this.updateAddress({ addressId: this.context.addressId, @@ -691,8 +695,7 @@ export default { * Async PATCH transactions, * then update existing address with backend datas when promise is resolved */ - updateAddress(payload) - { + updateAddress(payload) { this.flag.loading = true; // TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue index ca2f5d634..c333961db 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue @@ -4,6 +4,10 @@ @@ -46,14 +49,17 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue index 5fe040e14..efdf62926 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue @@ -13,7 +13,6 @@ export default { methods : { toggleBlur: function(e){ if(e.target.matches('.toggle')){ - console.log(e); e.target.previousElementSibling.classList.toggle("blur"); e.target.classList.toggle("fa-eye"); e.target.classList.toggle("fa-eye-slash"); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js index c16b4c65d..4b998f98a 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js @@ -45,7 +45,8 @@ const messages = { redirect: { person: "Quitter la page et ouvrir la fiche de l'usager", thirdparty: "Quitter la page et voir le tiers", - } + }, + refresh: 'Rafraîchir' }, nav: { next: "Suivant", diff --git a/src/Bundle/ChillMainBundle/Resources/test/Fixtures/App/app/AppKernel.php b/src/Bundle/ChillMainBundle/Resources/test/Fixtures/App/app/AppKernel.php index 10886518a..4f0542aec 100644 --- a/src/Bundle/ChillMainBundle/Resources/test/Fixtures/App/app/AppKernel.php +++ b/src/Bundle/ChillMainBundle/Resources/test/Fixtures/App/app/AppKernel.php @@ -1,13 +1,38 @@ -load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); - } - - /** - * @return string - */ - public function getCacheDir() - { - return sys_get_temp_dir().'/ChillMainBundle/cache'; - } - - /** - * @return string - */ - public function getLogDir() - { - return sys_get_temp_dir().'/ChillMainBundle/logs'; - } + { + $loader->load(__DIR__ . '/config/config_' . $this->getEnvironment() . '.yml'); + } } diff --git a/src/Bundle/ChillMainBundle/Resources/test/Fixtures/App/app/autoload.php b/src/Bundle/ChillMainBundle/Resources/test/Fixtures/App/app/autoload.php index 39dc0e2ba..ea351fb8a 100644 --- a/src/Bundle/ChillMainBundle/Resources/test/Fixtures/App/app/autoload.php +++ b/src/Bundle/ChillMainBundle/Resources/test/Fixtures/App/app/autoload.php @@ -1,11 +1,20 @@ {{ address.street }} - {% if address.streetNumber is not empty %} - {{ address.streetNumber }} - {% endif %} -

+

{{ streetLine }}

{% endif %} {% if options['extended_infos'] %} {{ _self.extended(address, options) }} @@ -56,14 +54,21 @@ {% endif %} {% endmacro %} -{% macro inline(address, options) %} +{% macro inline(address, options, streetLine) %} {% if options['has_no_address'] == true and address.isNoAddress == true %} + {% if address.postCode is not empty %} +

+ {{ address.postCode.code }} + {{ address.postCode.name }} +

+

{{ address.postCode.country.name|localize_translatable_string }}

+ {% endif %} {{ 'address.consider homeless'|trans }} {% else %} - {{ _self.raw(address, options) }} + {{ _self.raw(address, options, streetLine) }} {% endif %} {{ _self.validity(address, options) }} @@ -83,12 +88,16 @@ {% endmacro %} +{# + this enclose the rendering inside a "li", which ease the placement operation when the address + must be shown in such list +#} {%- if render == 'list' -%}
  • {% if options['with_picto'] %} {% endif %} - {{ _self.inline(address, options) }} + {{ _self.inline(address, options, streetLine) }}
  • {%- endif -%} @@ -97,22 +106,32 @@ {% if options['with_picto'] %} {% endif %} - {{ _self.inline(address, options) }} + {{ _self.inline(address, options, streetLine) }} {%- endif -%} {%- if render == 'bloc' -%}
    {% if options['has_no_address'] == true and address.isNoAddress == true %} + {% if address.postCode is not empty %} +
    +

    + {{ address.postCode.code }} + {{ address.postCode.name }} +

    +

    {{ address.postCode.country.name|localize_translatable_string }}

    +
    + {% endif %}
    {{ 'address.consider homeless'|trans }}
    + {% else %}
    {% if options['with_picto'] %} {% endif %} - {{ _self.raw(address, options) }} + {{ _self.raw(address, options, streetLine) }}
    {% endif %} {{ _self.validity(address, options) }} diff --git a/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig b/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig index 074f3bb94..515685b9e 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig @@ -1,12 +1,41 @@ {{ form_start(form) }} -
    +
    -
    -
    - {{ form_widget(form.q)}} - + {% if form.vars.has_search_box %} +
    +
    + {{ form_widget(form.q)}} + +
    -
    + {% endif %}
    + {% if form.checkboxes is defined %} + {% if form.checkboxes|length > 0 %} + {% for checkbox_name, options in form.checkboxes %} +
    +
    + {% for c in form['checkboxes'][checkbox_name].children %} +
    + {{ form_widget(c) }} + {{ form_label(c) }} +
    + {% endfor %} +
    +
    + {% if loop.last %} +
    +
    +
      +
    • + +
    • +
    +
    +
    + {% endif %} + {% endfor %} + {% endif %} + {% endif %}
    {{ form_end(form) }} diff --git a/src/Bundle/ChillMainBundle/Resources/views/LocationType/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/LocationType/index.html.twig index ec617c6c0..5402d5e89 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/LocationType/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/LocationType/index.html.twig @@ -11,6 +11,7 @@ {{ 'Address required'|trans }} {{ 'Contact data'|trans }} {{ 'Active'|trans }} + {{ 'Default for'|trans }} @@ -33,6 +34,7 @@ {%- endif -%} + {{ entity.defaultFor|trans }}
    diff --git a/src/Bundle/ChillMainBundle/Routing/Loader/ChillRoutesLoader.php b/src/Bundle/ChillMainBundle/Routing/Loader/ChillRoutesLoader.php index b7a719073..bc16929b7 100644 --- a/src/Bundle/ChillMainBundle/Routing/Loader/ChillRoutesLoader.php +++ b/src/Bundle/ChillMainBundle/Routing/Loader/ChillRoutesLoader.php @@ -1,79 +1,51 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Routing\Loader; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Routing\RouteCollection; /** - * Import routes from bundles - * - * Routes must be defined in configuration, add an entry - * under `chill_main.routing.resources` - * - * + * Import routes from bundles. * - * @author Julien Fastré + * Routes must be defined in configuration, add an entry + * under `chill_main.routing.resources` */ class ChillRoutesLoader extends Loader { - private $routes; - - - + private array $routes; + public function __construct(array $routes) { $this->routes = $routes; + + parent::__construct(); } - - /** - * {@inheritDoc} - * - * @param type $resource - * @param type $type - * @return RouteCollection - */ + public function load($resource, $type = null) { $collection = new RouteCollection(); - - foreach ($this->routes as $resource) { + + foreach ($this->routes as $routeResource) { $collection->addCollection( - $this->import($resource, NULL) - ); + $this->import($routeResource, null) + ); } - + return $collection; } - /** - * {@inheritDoc} - * - * @param type $resource - * @param type $type - * @return boolean - */ public function supports($resource, $type = null) { return 'chill_routes' === $type; } - } diff --git a/src/Bundle/ChillMainBundle/Routing/LocalMenuBuilderInterface.php b/src/Bundle/ChillMainBundle/Routing/LocalMenuBuilderInterface.php index 37f7ead40..23d4a5724 100644 --- a/src/Bundle/ChillMainBundle/Routing/LocalMenuBuilderInterface.php +++ b/src/Bundle/ChillMainBundle/Routing/LocalMenuBuilderInterface.php @@ -1,38 +1,28 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Routing; use Knp\Menu\MenuItem; -/** - * - * @author Julien Fastré - */ interface LocalMenuBuilderInterface { + public function buildMenu($menuId, MenuItem $menu, array $parameters); + /** - * return an array of menu ids - * - * @internal this method is static to 1. keep all config in the class, + * return an array of menu ids. + * + * @internal this method is static to 1. keep all config in the class, * instead of tags arguments; 2. avoid a "supports" method, which could lead * to parsing all instances to get only one or two working instance. */ public static function getMenuIds(): array; - - public function buildMenu($menuId, MenuItem $menu, array $parameters); } diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/AdminSectionMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/AdminSectionMenuBuilder.php index 7f41dbf67..9bcc80ad2 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/AdminSectionMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/AdminSectionMenuBuilder.php @@ -1,35 +1,23 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Routing\MenuBuilder; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Knp\Menu\MenuItem; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -use Chill\MainBundle\Security\Authorization\ChillExportVoter; -/** - * - * - */ class AdminSectionMenuBuilder implements LocalMenuBuilderInterface { /** - * * @var AuthorizationCheckerInterface */ protected $authorizationChecker; @@ -47,26 +35,26 @@ class AdminSectionMenuBuilder implements LocalMenuBuilderInterface } $menu->addChild('Users and permissions', [ - 'route' => 'chill_main_admin_permissions' - ]) + 'route' => 'chill_main_admin_permissions', + ]) ->setExtras([ 'icons' => ['key'], 'order' => 200, - 'explain' => "Configure permissions for users" + 'explain' => 'Configure permissions for users', ]); $menu->addChild('Location and location type', [ - 'route' => 'chill_main_admin_locations' - ]) + 'route' => 'chill_main_admin_locations', + ]) ->setExtras([ 'icons' => ['key'], 'order' => 205, - 'explain' => "Configure location and location type" + 'explain' => 'Configure location and location type', ]); } public static function getMenuIds(): array { - return [ 'admin_section', 'admin_index' ]; + return ['admin_section', 'admin_index']; } } diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/LocationMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/LocationMenuBuilder.php index 890c1b2ef..da1ce479b 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/LocationMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/LocationMenuBuilder.php @@ -1,28 +1,33 @@ addChild('Location type list', [ - 'route' => 'chill_crud_main_location_type_index' + 'route' => 'chill_crud_main_location_type_index', ])->setExtras(['order' => 205]); $menu->addChild('Location list', [ - 'route' => 'chill_crud_main_location_index' + 'route' => 'chill_crud_main_location_index', ])->setExtras(['order' => 206]); } + + public static function getMenuIds(): array + { + return ['admin_location']; + } } diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/PermissionMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/PermissionMenuBuilder.php index 470930820..6fab6b635 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/PermissionMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/PermissionMenuBuilder.php @@ -1,43 +1,47 @@ addChild('Permissions group list', [ - 'route' => 'admin_permissionsgroup' + 'route' => 'admin_permissionsgroup', ])->setExtras([ - 'order' => 300 - ]); + 'order' => 300, + ]); $menu->addChild('crud.admin_user.index.title', [ - 'route' => 'chill_crud_admin_user_index' + 'route' => 'chill_crud_admin_user_index', ])->setExtras(['order' => 400]); $menu->addChild('List circles', [ - 'route' => 'admin_scope' + 'route' => 'admin_scope', ])->setExtras(['order' => 200]); $menu->addChild('Center list', [ - 'route' => 'admin_center' + 'route' => 'admin_center', ])->setExtras(['order' => 100]); $menu->addChild('User jobs', [ - 'route' => 'chill_crud_admin_user_job_index' + 'route' => 'chill_crud_admin_user_job_index', ])->setExtras(['order' => 150]); + } + public static function getMenuIds(): array + { + return ['admin_permissions']; } } diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php index b8619750c..9aae86e48 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php @@ -1,35 +1,24 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Routing\MenuBuilder; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Knp\Menu\MenuItem; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Symfony\Component\Translation\TranslatorInterface; /** - * Class SectionMenuBuilder - * - * @package Chill\MainBundle\Routing\MenuBuilder - * @author Julien Fastré + * Class SectionMenuBuilder. */ class SectionMenuBuilder implements LocalMenuBuilderInterface { @@ -37,62 +26,56 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface * @var AuthorizationCheckerInterface */ protected $authorizationChecker; - + /** * @var TranslatorInterface */ protected $translator; - + /** * SectionMenuBuilder constructor. - * - * @param AuthorizationCheckerInterface $authorizationChecker */ public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator) { $this->authorizationChecker = $authorizationChecker; $this->translator = $translator; } - + /** * @param $menuId - * @param MenuItem $menu - * @param array $parameters */ public function buildMenu($menuId, MenuItem $menu, array $parameters) { $menu->addChild($this->translator->trans('Homepage'), [ - 'route' => 'chill_main_homepage' - ]) + 'route' => 'chill_main_homepage', + ]) ->setExtras([ 'icons' => ['home'], - 'order' => 0 + 'order' => 0, ]); $menu->addChild($this->translator->trans('Global timeline'), [ 'route' => 'chill_center_timeline', ]) - ->setExtras([ - 'order' => 10 - ] - ); - + ->setExtras( + [ + 'order' => 10, + ] + ); + if ($this->authorizationChecker->isGranted(ChillExportVoter::EXPORT)) { $menu->addChild($this->translator->trans('Export Menu'), [ - 'route' => 'chill_main_export_index' + 'route' => 'chill_main_export_index', ]) - ->setExtras([ - 'icons' => ['upload'], - 'order' => 20 - ]); + ->setExtras([ + 'icons' => ['upload'], + 'order' => 20, + ]); } } - - /** - * @return array - */ + public static function getMenuIds(): array { - return [ 'section' ]; + return ['section']; } } diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php index 010f5609d..51758d979 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php @@ -1,71 +1,74 @@ - * - * 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\MainBundle\Routing\MenuBuilder; - -use Chill\MainBundle\Routing\LocalMenuBuilderInterface; -use Chill\MainBundle\Entity\User; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Routing\MenuBuilder; + +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Symfony\Component\Security\Core\Security; + class UserMenuBuilder implements LocalMenuBuilderInterface { - /** - * - * @var TokenStorageInterface - */ - protected $tokenStorage; - - public function __construct(TokenStorageInterface $tokenStorage) + private Security $security; + + public function __construct(Security $security) { - $this->tokenStorage = $tokenStorage; + $this->security = $security; } - + public function buildMenu($menuId, \Knp\Menu\MenuItem $menu, array $parameters) { - if ($this->tokenStorage->getToken()->getUser() instanceof User) { + $user = $this->security->getUser(); + + if ($user instanceof User) { + if (null !== $user->getCurrentLocation()) { + $locationTextMenu = $user->getCurrentLocation()->getName(); + } else { + $locationTextMenu = 'Set a location'; + } + + $menu + ->addChild( + $locationTextMenu, + ['route' => 'chill_main_user_currentlocation_edit'] + ) + ->setExtras([ + 'order' => -9999999, + 'icon' => 'map-marker', + ]); $menu ->addChild( 'Change password', - [ 'route' => 'change_my_password'] + ['route' => 'change_my_password'] ) ->setExtras([ - 'order' => 99999999998 + 'order' => 99999999998, ]); } - + $menu ->addChild( - 'Logout', + 'Logout', [ - 'route' => 'logout' - ]) + 'route' => 'logout', + ] + ) ->setExtras([ - 'order'=> 99999999999, - 'icon' => 'power-off' + 'order' => 99999999999, + 'icon' => 'power-off', ]); } public static function getMenuIds(): array { - return [ 'user' ]; + return ['user']; } } diff --git a/src/Bundle/ChillMainBundle/Routing/MenuComposer.php b/src/Bundle/ChillMainBundle/Routing/MenuComposer.php index 0bcee4c81..a94cf41f3 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuComposer.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuComposer.php @@ -1,51 +1,44 @@ translator = $translator; } - /** - * Set the route Collection - * This function is needed for testing purpose: routeCollection is not - * available as a service (RouterInterface is provided as a service and - * added to this class as paramater in __construct) - * - * @param RouteCollection $routeCollection - */ - public function setRouteCollection(RouteCollection $routeCollection) + public function addLocalMenuBuilder(LocalMenuBuilderInterface $menuBuilder, $menuId) { - $this->routeCollection = $routeCollection; + $this->localMenuBuilders[$menuId][] = $menuBuilder; + } + + public function getMenuFor($menuId, array $parameters = []) + { + $routes = $this->getRoutesFor($menuId, $parameters); + $menu = $this->menuFactory->createItem($menuId); + + // build menu from routes + foreach ($routes as $order => $route) { + $menu->addChild($this->translator->trans($route['label']), [ + 'route' => $route['key'], + 'routeParameters' => $parameters['args'], + 'order' => $order, + ]) + ->setExtras([ + //'icon' => $route['icon'], + // sf4 check: commented to avoid error: `An exception has been thrown during the rendering of a template ("Notice: Undefined index: icon").` + 'order' => $order, + ]); + } + + if ($this->hasLocalMenuBuilder($menuId)) { + foreach ($this->localMenuBuilders[$menuId] as $builder) { + /* @var $builder LocalMenuBuilderInterface */ + $builder->buildMenu($menuId, $menu, $parameters['args']); + } + } + + $this->reorderMenu($menu); + + return $menu; } /** * Return an array of routes added to $menuId, - * The array is aimed to build route with MenuTwig - * + * The array is aimed to build route with MenuTwig. + * * @param string $menuId * @param array $parameters see https://redmine.champs-libres.coop/issues/179 + * * @return array */ - public function getRoutesFor($menuId, array $parameters = array()) + public function getRoutesFor($menuId, array $parameters = []) { - $routes = array(); + $routes = []; $routeCollection = $this->router->getRouteCollection(); foreach ($routeCollection->all() as $routeKey => $route) { if ($route->hasOption('menus')) { - if (array_key_exists($menuId, $route->getOption('menus'))) { $route = $route->getOption('menus')[$menuId]; @@ -101,97 +117,69 @@ class MenuComposer return $routes; } - - public function getMenuFor($menuId, array $parameters = array()) + + /** + * Return true if the menu has at least one builder. + * + * This function is a helper to determine if the method `getMenuFor` + * should be used, or `getRouteFor`. The method `getMenuFor` should be used + * if the result is true (it **does** exists at least one menu builder. + * + * @param string $menuId + */ + public function hasLocalMenuBuilder($menuId): bool { - $routes = $this->getRoutesFor($menuId, $parameters); - $menu = $this->menuFactory->createItem($menuId); - - // build menu from routes - foreach ($routes as $order => $route) { - $menu->addChild($this->translator->trans($route['label']), [ - 'route' => $route['key'], - 'routeParameters' => $parameters['args'], - 'order' => $order - ]) - ->setExtras([ - //'icon' => $route['icon'], - // sf4 check: commented to avoid error: `An exception has been thrown during the rendering of a template ("Notice: Undefined index: icon").` - 'order' => $order - ]) - ; - } - - if ($this->hasLocalMenuBuilder($menuId)) { - foreach ($this->localMenuBuilders[$menuId] as $builder) { - /* @var $builder LocalMenuBuilderInterface */ - $builder->buildMenu($menuId, $menu, $parameters['args']); + return array_key_exists($menuId, $this->localMenuBuilders); + } + + /** + * Set the route Collection + * This function is needed for testing purpose: routeCollection is not + * available as a service (RouterInterface is provided as a service and + * added to this class as paramater in __construct). + */ + public function setRouteCollection(RouteCollection $routeCollection) + { + $this->routeCollection = $routeCollection; + } + + private function reorderMenu(ItemInterface $menu) + { + $ordered = []; + $unordered = []; + + foreach ($menu->getChildren() as $name => $item) { + $order = $item->getExtra('order'); + + if (null !== $order) { + $ordered[$this->resolveOrder($ordered, $order)] = $name; + } else { + $unordered = $name; } } - - $this->reorderMenu($menu); - - return $menu; + + ksort($ordered); + + $menus = array_merge(array_values($ordered), $unordered); + $menu->reorderChildren($menus); } - + /** * recursive function to resolve the order of a array of routes. - * If the order chosen in routing.yml is already in used, find the + * If the order chosen in routing.yml is already in used, find the * first next order available. - * + * * @param array $routes the routes previously added * @param int $order + * * @return int */ private function resolveOrder($routes, $order) { if (isset($routes[$order])) { return $this->resolveOrder($routes, $order + 1); - } else { - return $order; } - } - - private function reorderMenu(ItemInterface $menu) - { - $ordered = []; - $unordered = []; - - foreach ($menu->getChildren() as $name => $item) { - $order = $item->getExtra('order'); - - if ($order !== null) { - $ordered[$this->resolveOrder($ordered, $order)] = $name; - } else { - $unordered = $name; - } - } - - ksort($ordered); - - $menus = \array_merge(\array_values($ordered), $unordered); - $menu->reorderChildren($menus); - } - - - public function addLocalMenuBuilder(LocalMenuBuilderInterface $menuBuilder, $menuId) - { - $this->localMenuBuilders[$menuId][] = $menuBuilder; - } - - /** - * Return true if the menu has at least one builder. - * - * This function is a helper to determine if the method `getMenuFor` - * should be used, or `getRouteFor`. The method `getMenuFor` should be used - * if the result is true (it **does** exists at least one menu builder. - * - * @param string $menuId - * @return bool - */ - public function hasLocalMenuBuilder($menuId): bool - { - return \array_key_exists($menuId, $this->localMenuBuilders); - } + return $order; + } } diff --git a/src/Bundle/ChillMainBundle/Routing/MenuTwig.php b/src/Bundle/ChillMainBundle/Routing/MenuTwig.php index fe233e205..bf59bfcb8 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuTwig.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuTwig.php @@ -1,27 +1,16 @@ - * - * 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 . + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Routing; -use Chill\MainBundle\Routing\MenuComposer; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Twig\Environment; @@ -29,90 +18,86 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; /** - * Add the filter 'chill_menu' - * - * @author Julien Fastré + * Add the filter 'chill_menu'. */ class MenuTwig extends AbstractExtension implements ContainerAwareInterface { - /** - * - * @var MenuComposer - */ - private $menuComposer; - - /** - * * @var \Symfony\Component\DependencyInjection\ContainerInterface */ private $container; - + /** - * the default parameters for chillMenu - * - * @var mixed[] + * the default parameters for chillMenu. + * + * @var mixed[] */ - private $defaultParams = array( + private $defaultParams = [ 'layout' => '@ChillMain/Menu/defaultMenu.html.twig', - 'args' => array(), - 'activeRouteKey' => null - ); - + 'args' => [], + 'activeRouteKey' => null, + ]; + + /** + * @var MenuComposer + */ + private $menuComposer; + public function __construct(MenuComposer $menuComposer) { $this->menuComposer = $menuComposer; } - - public function getFunctions() - { - return [new TwigFunction('chill_menu', - array($this, 'chillMenu'), array( - 'is_safe' => array('html'), - 'needs_environment' => true - )) - ]; - } - + /** - * Render a Menu corresponding to $menuId - * - * Expected params : + * Render a Menu corresponding to $menuId. + * + * Expected params : * - args: the arguments to build the path (i.e: if pattern is /something/{bar}, args must contain {'bar': 'foo'} * - layout: the layout. Absolute path needed (i.e.: ChillXyzBundle:section:foo.html.twig) * - activeRouteKey : the key active, will render the menu differently. - * + * * @deprecated link: see https://redmine.champs-libres.coop/issues/179 for more informations - * + * * @param string $menuId * @param mixed[] $params */ - public function chillMenu(Environment $env, $menuId, array $params = array()) + public function chillMenu(Environment $env, $menuId, array $params = []) { $resolvedParams = array_merge($this->defaultParams, $params); $layout = $resolvedParams['layout']; unset($resolvedParams['layout']); - + if ($this->menuComposer->hasLocalMenuBuilder($menuId) === false) { $resolvedParams['routes'] = $this->menuComposer->getRoutesFor($menuId, $resolvedParams); - - return $env->render($layout, $resolvedParams); - } else { - $resolvedParams['menus'] = $this->menuComposer->getMenuFor($menuId, $resolvedParams); - + return $env->render($layout, $resolvedParams); } + $resolvedParams['menus'] = $this->menuComposer->getMenuFor($menuId, $resolvedParams); + + return $env->render($layout, $resolvedParams); } - + + public function getFunctions() + { + return [new TwigFunction( + 'chill_menu', + [$this, 'chillMenu'], + [ + 'is_safe' => ['html'], + 'needs_environment' => true, + ] + ), + ]; + } + public function getName() { return 'chill_menu'; } - public function setContainer(ContainerInterface $container = null) + public function setContainer(?ContainerInterface $container = null) { $this->container = $container; } - } diff --git a/src/Bundle/ChillMainBundle/Search/AbstractSearch.php b/src/Bundle/ChillMainBundle/Search/AbstractSearch.php index 942819a39..adadc3422 100644 --- a/src/Bundle/ChillMainBundle/Search/AbstractSearch.php +++ b/src/Bundle/ChillMainBundle/Search/AbstractSearch.php @@ -1,101 +1,91 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Search; -use Chill\MainBundle\Search\SearchInterface; -use Chill\MainBundle\Search\ParsingException; +use DateTime; +use function array_key_exists; +use function strpos; /** * This class implements abstract search with most common responses. - * + * * you should use this abstract class instead of SearchInterface : if the signature of * search interface change, the generic method will be implemented here. - * - * @author Julien Fastré - * */ abstract class AbstractSearch implements SearchInterface { /** - * parse string expected to be a date and transform to a DateTime object - * + * parse string expected to be a date and transform to a DateTime object. + * * @param type $string - * @return \DateTime + * * @throws ParsingException if the date is not parseable + * + * @return DateTime */ public function parseDate($string) { try { - return new \DateTime($string); + return new DateTime($string); } catch (ParsingException $ex) { $exception = new ParsingException('The date is ' . 'not parsable', 0, $ex); + throw $exception; } - } - + /** - * recompose a pattern, retaining only supported terms - * + * recompose a pattern, retaining only supported terms. + * * the outputted string should be used to show users their search - * - * @param array $terms - * @param array $supportedTerms + * * @param string $domain if your domain is NULL, you should set NULL. You should set used domain instead + * * @return string */ - protected function recomposePattern(array $terms, array $supportedTerms, $domain = NULL) + protected function recomposePattern(array $terms, array $supportedTerms, $domain = null) { $recomposed = ''; - - if ($domain !== NULL) - { - $recomposed .= '@'.$domain.' '; + + if (null !== $domain) { + $recomposed .= '@' . $domain . ' '; } - + foreach ($supportedTerms as $term) { - if (array_key_exists($term, $terms) && $term !== '_default') { - $recomposed .= ' '.$term.':'; - $containsSpace = \strpos($terms[$term], " ") !== false; + if (array_key_exists($term, $terms) && '_default' !== $term) { + $recomposed .= ' ' . $term . ':'; + $containsSpace = strpos($terms[$term], ' ') !== false; + if ($containsSpace) { - $recomposed .= "("; + $recomposed .= '"'; } - $recomposed .= (mb_stristr(' ', $terms[$term]) === FALSE) ? $terms[$term] : '('.$terms[$term].')'; + $recomposed .= (mb_stristr(' ', $terms[$term]) === false) ? $terms[$term] : '(' . $terms[$term] . ')'; + if ($containsSpace) { - $recomposed .= ")"; + $recomposed .= '"'; } } } - - if ($terms['_default'] !== '') { - $recomposed .= ' '.$terms['_default']; + + if ('' !== $terms['_default']) { + $recomposed .= ' ' . $terms['_default']; } - + //strip first character if empty - if (mb_strcut($recomposed, 0, 1) === ' '){ + if (mb_strcut($recomposed, 0, 1) === ' ') { $recomposed = mb_strcut($recomposed, 1); } - + return $recomposed; } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php b/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php index 6b8139419..5a68fab37 100644 --- a/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php +++ b/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php @@ -1,59 +1,67 @@ userRepository = $userRepository; } - public function provideQuery(string $pattern, array $parameters): SearchApiQuery - { - $query = new SearchApiQuery(); - $query - ->setSelectKey("user") - ->setSelectJsonbMetadata("jsonb_build_object('id', u.id)") - ->setSelectPertinence("GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical), - SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical))", [ $pattern, $pattern ]) - ->setFromClause("users AS u") - ->setWhereClauses("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 - OR - SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical) > 0.15 - ", [ $pattern, $pattern ]); - - return $query; - } - - public function supportsTypes(string $pattern, array $types, array $parameters): bool - { - return \in_array('user', $types); - } - - public function prepare(array $metadatas): void - { - $ids = \array_map(fn($m) => $m['id'], $metadatas); - - $this->userRepository->findBy([ 'id' => $ids ]); - } - - public function supportsResult(string $key, array $metadatas): bool - { - return $key === 'user'; - } - public function getResult(string $key, array $metadata, float $pertinence) { return $this->userRepository->find($metadata['id']); } + + public function prepare(array $metadatas): void + { + $ids = array_map(static fn ($m) => $m['id'], $metadatas); + + $this->userRepository->findBy(['id' => $ids]); + } + + public function provideQuery(string $pattern, array $parameters): SearchApiQuery + { + $query = new SearchApiQuery(); + $query + ->setSelectKey('user') + ->setSelectJsonbMetadata("jsonb_build_object('id', u.id)") + ->setSelectPertinence('GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical), + SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical))', [$pattern, $pattern]) + ->setFromClause('users AS u') + ->setWhereClauses('SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 + OR + SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical) > 0.15 + ', [$pattern, $pattern]); + + return $query; + } + + public function supportsResult(string $key, array $metadatas): bool + { + return 'user' === $key; + } + + public function supportsTypes(string $pattern, array $types, array $parameters): bool + { + return in_array('user', $types, true); + } } diff --git a/src/Bundle/ChillMainBundle/Search/HasAdvancedSearchFormInterface.php b/src/Bundle/ChillMainBundle/Search/HasAdvancedSearchFormInterface.php index f258a51f8..dbe34c00b 100644 --- a/src/Bundle/ChillMainBundle/Search/HasAdvancedSearchFormInterface.php +++ b/src/Bundle/ChillMainBundle/Search/HasAdvancedSearchFormInterface.php @@ -1,34 +1,23 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Search; use Symfony\Component\Form\FormBuilderInterface; -/** - * - * @author Julien Fastré - */ interface HasAdvancedSearchFormInterface extends SearchInterface { public function buildForm(FormBuilderInterface $builder); - + public function convertFormDataToQuery(array $data); - - public function convertTermsToFormData(array $terms) ; - + + public function convertTermsToFormData(array $terms); } diff --git a/src/Bundle/ChillMainBundle/Search/Model/Result.php b/src/Bundle/ChillMainBundle/Search/Model/Result.php index a88c2f55f..96d021187 100644 --- a/src/Bundle/ChillMainBundle/Search/Model/Result.php +++ b/src/Bundle/ChillMainBundle/Search/Model/Result.php @@ -1,5 +1,14 @@ result; } - - - } diff --git a/src/Bundle/ChillMainBundle/Search/ParsingException.php b/src/Bundle/ChillMainBundle/Search/ParsingException.php index 36d02ab8a..1c53463a5 100644 --- a/src/Bundle/ChillMainBundle/Search/ParsingException.php +++ b/src/Bundle/ChillMainBundle/Search/ParsingException.php @@ -1,6 +1,18 @@ em = $em; - $this->providers[] = $searchPerson; - $this->providers[] = $thirdPartyApiSearch; - $this->providers[] = $searchUser; + $this->providers = $providers; $this->paginator = $paginator; } @@ -59,105 +62,29 @@ class SearchApi return new Collection($results, $paginator); } - private function findQueries($pattern, array $types, array $parameters): array - { - return \array_map( - fn($p) => $p->provideQuery($pattern, $parameters), - $this->findProviders($pattern, $types, $parameters), - ); - } - - private function findProviders(string $pattern, array $types, array $parameters): array - { - return \array_filter( - $this->providers, - fn($p) => $p->supportsTypes($pattern, $types, $parameters) - ); - } - - private function countItems($providers, $types, $parameters): int - { - list($countQuery, $parameters) = $this->buildCountQuery($providers, $types, $parameters); - $rsmCount = new ResultSetMappingBuilder($this->em); - $rsmCount->addScalarResult('count', 'count'); - $countNq = $this->em->createNativeQuery($countQuery, $rsmCount); - $countNq->setParameters($parameters); - - return $countNq->getSingleScalarResult(); - } - private function buildCountQuery(array $queries, $types, $parameters) { - $query = "SELECT COUNT(sq.key) AS count FROM ({union_unordered}) AS sq"; + $query = 'SELECT SUM(c) AS count FROM ({union_unordered}) AS sq'; $unions = []; $parameters = []; foreach ($queries as $q) { - $unions[] = $q->buildQuery(); - $parameters = \array_merge($parameters, $q->buildParameters()); + $unions[] = $q->buildQuery(true); + $parameters = array_merge($parameters, $q->buildParameters(true)); } - $unionUnordered = \implode(" UNION ", $unions); + $unionUnordered = implode(' UNION ', $unions); return [ - \strtr($query, [ '{union_unordered}' => $unionUnordered ]), - $parameters + strtr($query, ['{union_unordered}' => $unionUnordered]), + $parameters, ]; } - private function buildUnionQuery(array $queries, $types, $parameters) + private function buildResults(array $rawResults): array { - $query = "{unions} ORDER BY pertinence DESC"; - $unions = []; - $parameters = []; + $items = []; - foreach ($queries as $q) { - $unions[] = $q->buildQuery(); - $parameters = \array_merge($parameters, $q->buildParameters()); - } - - $union = \implode(" UNION ", $unions); - - return [ - \strtr($query, [ '{unions}' => $union]), - $parameters - ]; - } - - private function fetchRawResult($queries, $types, $parameters, $paginator): array - { - list($union, $parameters) = $this->buildUnionQuery($queries, $types, $parameters, $paginator); - $rsm = new ResultSetMappingBuilder($this->em); - $rsm->addScalarResult('key', 'key', Types::STRING) - ->addScalarResult('metadata', 'metadata', Types::JSON) - ->addScalarResult('pertinence', 'pertinence', Types::FLOAT) - ; - - $nq = $this->em->createNativeQuery($union, $rsm); - $nq->setParameters($parameters); - - return $nq->getResult(); - } - - private function prepareProviders($rawResults) - { - $metadatas = []; - foreach ($rawResults as $r) { - foreach ($this->providers as $k => $p) { - if ($p->supportsResult($r['key'], $r['metadata'])) { - $metadatas[$k][] = $r['metadata']; - break; - } - } - } - - foreach ($metadatas as $k => $m) { - $this->providers[$k]->prepare($m); - } - } - - private function buildResults($rawResults) - { foreach ($rawResults as $r) { foreach ($this->providers as $k => $p) { if ($p->supportsResult($r['key'], $r['metadata'])) { @@ -165,11 +92,98 @@ class SearchApi ->setResult( $p->getResult($r['key'], $r['metadata'], $r['pertinence']) ); + break; } } } - return $items ?? []; + return $items; + } + + private function buildUnionQuery(array $queries, $types, $parameters) + { + $query = '{unions} ORDER BY pertinence DESC'; + $unions = []; + $parameters = []; + + foreach ($queries as $q) { + $unions[] = $q->buildQuery(); + $parameters = array_merge($parameters, $q->buildParameters()); + } + + $union = implode(' UNION ', $unions); + + return [ + strtr($query, ['{unions}' => $union]), + $parameters, + ]; + } + + private function countItems($providers, $types, $parameters): int + { + [$countQuery, $parameters] = $this->buildCountQuery($providers, $types, $parameters); + $rsmCount = new ResultSetMappingBuilder($this->em); + $rsmCount->addScalarResult('count', 'count'); + $countNq = $this->em->createNativeQuery($countQuery, $rsmCount); + $countNq->setParameters($parameters); + + return (int) $countNq->getSingleScalarResult(); + } + + private function fetchRawResult($queries, $types, $parameters, $paginator): array + { + [$union, $parameters] = $this->buildUnionQuery($queries, $types, $parameters); + $rsm = new ResultSetMappingBuilder($this->em); + $rsm->addScalarResult('key', 'key', Types::STRING) + ->addScalarResult('metadata', 'metadata', Types::JSON) + ->addScalarResult('pertinence', 'pertinence', Types::FLOAT); + + $nq = $this->em->createNativeQuery($union, $rsm); + $nq->setParameters($parameters); + + return $nq->getResult(); + } + + private function findProviders(string $pattern, array $types, array $parameters): array + { + $providers = []; + + foreach ($this->providers as $provider) { + if ($provider->supportsTypes($pattern, $types, $parameters)) { + $providers[] = $provider; + } + } + + return $providers; + } + + private function findQueries($pattern, array $types, array $parameters): array + { + return array_map( + static fn ($p) => $p->provideQuery($pattern, $parameters), + $this->findProviders($pattern, $types, $parameters), + ); + } + + private function prepareProviders(array $rawResults) + { + $metadatas = []; + $providers = []; + + foreach ($rawResults as $r) { + foreach ($this->providers as $k => $p) { + if ($p->supportsResult($r['key'], $r['metadata'])) { + $metadatas[$k][] = $r['metadata']; + $providers[$k] = $p; + + break; + } + } + } + + foreach ($metadatas as $k => $m) { + $providers[$k]->prepare($m); + } } } diff --git a/src/Bundle/ChillMainBundle/Search/SearchApiInterface.php b/src/Bundle/ChillMainBundle/Search/SearchApiInterface.php index 54269c946..111bd5e5e 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchApiInterface.php +++ b/src/Bundle/ChillMainBundle/Search/SearchApiInterface.php @@ -1,18 +1,25 @@ pattern = $pattern; $this->types = $types; diff --git a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php index 9188c0b5c..2fcea304d 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php +++ b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php @@ -1,40 +1,140 @@ selectKey = $selectKey; - $this->selectKeyParams = $params; + $this->select[] = $select; + $this->selectParams = [...$this->selectParams, ...$params]; return $this; } - public function setSelectJsonbMetadata(string $jsonbMetadata, array $params = []): self + /** + * Add a where clause. + * + * This will add to previous where clauses with and `AND` join + * + * @return $this + */ + public function andWhereClause(string $whereClause, array $params = []): self { - $this->jsonbMetadata = $jsonbMetadata; - $this->jsonbMetadataParams = $params; + $this->whereClauses[] = $whereClause; + array_push($this->whereClausesParams, ...$params); return $this; } - public function setSelectPertinence(string $pertinence, array $params = []): self + public function buildParameters(bool $countOnly = false): array { - $this->pertinence = $pertinence; - $this->pertinenceParams = $params; + if (!$countOnly) { + return [ + ...$this->buildSelectParams($countOnly), + ...$this->fromClauseParams, + ...$this->whereClausesParams, + ]; + } + + return [ + ...$this->fromClauseParams, + ...$this->whereClausesParams, + ]; + } + + public function buildQuery(bool $countOnly = false): string + { + $isMultiple = count($this->whereClauses); + $where = + ($isMultiple ? '(' : '') . + implode( + ($isMultiple ? ')' : '') . ' AND ' . ($isMultiple ? '(' : ''), + $this->whereClauses + ) . + ($isMultiple ? ')' : ''); + + $select = $this->buildSelectClause($countOnly); + + return strtr('SELECT + {select} + FROM {from} + WHERE {where} + ', [ + '{select}' => $select, + '{from}' => $this->fromClause, + '{where}' => $where, + ]); + } + + public function getFromClause(): string + { + return $this->fromClause; + } + + public function getFromParams(): array + { + return $this->fromClauseParams; + } + + public function getSelectClauses(): array + { + return $this->select; + } + + public function getSelectParams(): array + { + return $this->selectParams; + } + + public function resetSelectClause(): self + { + $this->select = []; + $this->selectParams = []; + $this->selectKey = null; + $this->selectKeyParams = []; + $this->jsonbMetadata = null; + $this->jsonbMetadataParams = []; + $this->pertinence = null; + $this->pertinenceParams = []; return $this; } @@ -47,62 +147,84 @@ class SearchApiQuery return $this; } + public function setSelectJsonbMetadata(string $jsonbMetadata, array $params = []): self + { + $this->jsonbMetadata = $jsonbMetadata; + $this->jsonbMetadataParams = $params; + + return $this; + } + + public function setSelectKey(string $selectKey, array $params = []): self + { + $this->selectKey = $selectKey; + $this->selectKeyParams = $params; + + return $this; + } + + public function setSelectPertinence(string $pertinence, array $params = []): self + { + $this->pertinence = $pertinence; + $this->pertinenceParams = $params; + + return $this; + } + /** * Set the where clause and replace all existing ones. - * */ public function setWhereClauses(string $whereClause, array $params = []): self { $this->whereClauses = [$whereClause]; - $this->whereClausesParams = [$params]; + $this->whereClausesParams = $params; return $this; } - /** - * Add a where clause. - * - * This will add to previous where clauses with and `AND` join - * - * @param string $whereClause - * @param array $params - * @return $this - */ - public function andWhereClause(string $whereClause, array $params = []): self + private function buildSelectClause(bool $countOnly = false): string { - $this->whereClauses[] = $whereClause; - $this->whereClausesParams[] = $params; + if ($countOnly) { + return 'count(*) AS c'; + } - return $this; + $selects = $this->getSelectClauses(); + + if (null !== $this->selectKey) { + $selects[] = strtr("'{key}' AS key", ['{key}' => $this->selectKey]); + } + + if (null !== $this->jsonbMetadata) { + $selects[] = strtr('{metadata} AS metadata', ['{metadata}' => $this->jsonbMetadata]); + } + + if (null !== $this->pertinence) { + $selects[] = strtr('{pertinence} AS pertinence', ['{pertinence}' => $this->pertinence]); + } + + return implode(', ', $selects); } - public function buildQuery(): string + private function buildSelectParams(bool $count = false): array { - $where = \implode(' AND ', $this->whereClauses); + if ($count) { + return []; + } - return \strtr("SELECT - '{key}' AS key, - {metadata} AS metadata, - {pertinence} AS pertinence - FROM {from} - WHERE {where} - ", [ - '{key}' => $this->selectKey, - '{metadata}' => $this->jsonbMetadata, - '{pertinence}' => $this->pertinence, - '{from}' => $this->fromClause, - '{where}' => $where, - ]); - } + $args = $this->getSelectParams(); - public function buildParameters(): array - { - return \array_merge( - $this->selectKeyParams, - $this->jsonbMetadataParams, - $this->pertinenceParams, - $this->fromClauseParams, - \array_merge([], ...$this->whereClausesParams), - ); + if (null !== $this->selectKey) { + $args = [...$args, ...$this->selectKeyParams]; + } + + if (null !== $this->jsonbMetadata) { + $args = [...$args, ...$this->jsonbMetadataParams]; + } + + if (null !== $this->pertinence) { + $args = [...$args, ...$this->pertinenceParams]; + } + + return $args; } } diff --git a/src/Bundle/ChillMainBundle/Search/SearchApiResult.php b/src/Bundle/ChillMainBundle/Search/SearchApiResult.php index bbc66877f..ac47de087 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchApiResult.php +++ b/src/Bundle/ChillMainBundle/Search/SearchApiResult.php @@ -1,5 +1,14 @@ relevance = $relevance; } - public function setResult($result): self - { - $this->result = $result; - - return $this; - } - - /** - * @Serializer\Groups({"read"}) - */ - public function getResult() - { - return $this->result; - } - /** * @Serializer\Groups({"read"}) */ @@ -37,4 +33,19 @@ class SearchApiResult { return $this->relevance; } + + /** + * @Serializer\Groups({"read"}) + */ + public function getResult() + { + return $this->result; + } + + public function setResult($result): self + { + $this->result = $result; + + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Search/SearchInterface.php b/src/Bundle/ChillMainBundle/Search/SearchInterface.php index 958ce9ddd..0c9369808 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchInterface.php +++ b/src/Bundle/ChillMainBundle/Search/SearchInterface.php @@ -1,100 +1,89 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Search; /** * This interface must be implemented on services which provide search results. * * @todo : write doc and add a link to documentation - * - * @author Julien Fastré - * */ interface SearchInterface { - const SEARCH_PREVIEW_OPTION = '_search_preview'; + /** + * Supplementary parameters to the query string. + */ + public const REQUEST_QUERY_KEY_ADD_PARAMETERS = 'add_q'; /** - * Request parameters contained inside the `add_q` parameters + * Request parameters contained inside the `add_q` parameters. */ - const REQUEST_QUERY_PARAMETERS = '_search_parameters'; + public const REQUEST_QUERY_PARAMETERS = '_search_parameters'; + + public const SEARCH_PREVIEW_OPTION = '_search_preview'; /** - * Supplementary parameters to the query string + * the order in which the results will appears in the global view. + * + * (this may be eventually defined in config.yml) + * + * @return int */ - const REQUEST_QUERY_KEY_ADD_PARAMETERS = 'add_q'; + public function getOrder(); - /** - * return the result in a html string. The string will be inclued (as raw) - * into a global view. - * - * The global view may be : - * {% for result as resultsFromDifferentSearchInterface %} - * {{ result|raw }} - * {% endfor %} - * - * **available options** : - * - SEARCH_PREVIEW_OPTION (boolean) : if renderResult should return a "preview" of - * the results. In this case, a subset of results should be returned, and, - * if the query return more results, a button "see all results" should be - * displayed at the end of the list. - * - * **Interaction between `start` and `limit` and pagination : you should - * take only the given parameters into account; the results from pagination - * should be ignored. (Most of the time, it should be the same). - * - * @param array $terms the string to search - * @param int $start the first result (for pagination) - * @param int $limit the number of result (for pagination) - * @param array $option the options, specific for each search - * @param string $format The format for result - * @return string, an HTML string - */ - public function renderResult(array $terms, $start=0, $limit=50, array $options = array(), $format = 'html'); + /** + * we may desactive the search interface by default. in this case, + * the search will be launch and rendered only with "advanced search". + * + * this may be activated/desactived from bundle definition in config.yml + * + * @return bool + */ + public function isActiveByDefault(); - /** - * we may desactive the search interface by default. in this case, - * the search will be launch and rendered only with "advanced search" - * - * this may be activated/desactived from bundle definition in config.yml - * - * @return bool - */ - public function isActiveByDefault(); - - /** - * the order in which the results will appears in the global view - * - * (this may be eventually defined in config.yml) - * - * @return int - */ - public function getOrder(); - - /** - * indicate if the implementation supports the given domain - * - * @return boolean - */ - public function supports($domain, $format); + /** + * return the result in a html string. The string will be inclued (as raw) + * into a global view. + * + * The global view may be : + * {% for result as resultsFromDifferentSearchInterface %} + * {{ result|raw }} + * {% endfor %} + * + * **available options** : + * - SEARCH_PREVIEW_OPTION (boolean) : if renderResult should return a "preview" of + * the results. In this case, a subset of results should be returned, and, + * if the query return more results, a button "see all results" should be + * displayed at the end of the list. + * + * **Interaction between `start` and `limit` and pagination : you should + * take only the given parameters into account; the results from pagination + * should be ignored. (Most of the time, it should be the same). + * + * @param array $terms the string to search + * @param int $start the first result (for pagination) + * @param int $limit the number of result (for pagination) + * @param string $format The format for result + * + * @return string, an HTML string + */ + public function renderResult(array $terms, $start = 0, $limit = 50, array $options = [], $format = 'html'); + /** + * indicate if the implementation supports the given domain. + * + * @param mixed $domain + * @param mixed $format + * + * @return bool + */ + public function supports($domain, $format); } diff --git a/src/Bundle/ChillMainBundle/Search/SearchProvider.php b/src/Bundle/ChillMainBundle/Search/SearchProvider.php index ca5d169fa..61eb3fcd8 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchProvider.php +++ b/src/Bundle/ChillMainBundle/Search/SearchProvider.php @@ -1,42 +1,83 @@ get('chill_main.search_provider') - * - * the syntax for search string is : - * - domain, which begin with `@`. Example: `@person`. Restrict the search to some - * entities. It may exists multiple search provider for the same domain (example: + * $container->get('chill_main.search_provider'). + * + * the syntax for search string is : + * - domain, which begin with `@`. Example: `@person`. Restrict the search to some + * entities. It may exists multiple search provider for the same domain (example: * a search provider for people which performs regular search, and suggestion search * with phonetical algorithms * - terms, which are the terms of the search. There are terms with argument (example : * `birthdate:2016-04-01` and the default term, which is the search without argument * and without domain. - * */ class SearchProvider { - /** - * - * @var SearchInterface[] - */ - private $searchServices = array(); - - /** - * * @var HasAdvancedSearchForm[] */ - private $hasAdvancedFormSearchServices = array(); - - /* + private $hasAdvancedFormSearchServices = []; + + /** + * store string which must be extracted to find default arguments. + * + * @var string[] + */ + private $mustBeExtracted = []; + + /** + * @var SearchInterface[] + */ + private $searchServices = []; + + public function addSearchService(SearchInterface $service, $name) + { + $this->searchServices[$name] = $service; + + if ($service instanceof HasAdvancedSearchFormInterface) { + $this->hasAdvancedFormSearchServices[$name] = $service; + } + } + + /** + * return search services with a specific name, defined in service + * definition. + * + * @param mixed $name + * + * @throws UnknowSearchNameException if not exists + * + * @return SearchInterface + */ + public function getByName($name) + { + if (isset($this->searchServices[$name])) { + return $this->searchServices[$name]; + } + + throw new UnknowSearchNameException($name); + } + + /** * return search services in an array, ordered by * the order key (defined in service definition) * the conflicts in keys (twice the same order) are resolved @@ -46,43 +87,136 @@ class SearchProvider * * @return SearchInterface[], with an int as array key */ - public function getByOrder() { //sort the array - uasort($this->searchServices, function(SearchInterface $a, SearchInterface $b) { - if ($a->getOrder() == $b->getOrder()) { + uasort($this->searchServices, static function (SearchInterface $a, SearchInterface $b) { + if ($a->getOrder() === $b->getOrder()) { return 0; } + return ($a->getOrder() < $b->getOrder()) ? -1 : 1; }); return $this->searchServices; } - + + /** + * return searchservice with an advanced form, defined in service + * definition. + * + * @param string $name + * + * @throws UnknowSearchNameException + * + * @return HasAdvancedSearchForm + */ + public function getHasAdvancedFormByName($name) + { + if (array_key_exists($name, $this->hasAdvancedFormSearchServices)) { + return $this->hasAdvancedFormSearchServices[$name]; + } + + throw new UnknowSearchNameException($name); + } + public function getHasAdvancedFormSearchServices() { //sort the array - uasort($this->hasAdvancedFormSearchServices, function(SearchInterface $a, SearchInterface $b) { - if ($a->getOrder() == $b->getOrder()) { + uasort($this->hasAdvancedFormSearchServices, static function (SearchInterface $a, SearchInterface $b) { + if ($a->getOrder() === $b->getOrder()) { return 0; } + return ($a->getOrder() < $b->getOrder()) ? -1 : 1; }); return $this->hasAdvancedFormSearchServices; } + public function getResultByName( + $pattern, + $name, + $start = 0, + $limit = 50, + array $options = [], + $format = 'html' + ) { + $terms = $this->parse($pattern); + $search = $this->getByName($name); + + if (null !== $terms['_domain'] && !$search->supports($terms['_domain'], $format)) { + throw new ParsingException('The domain is not supported for the search name'); + } + + return $search->renderResult($terms, $start, $limit, $options, $format); + } + /** - * parse the search string to extract domain and terms - * + * search through services which supports domain and give + * results as an array of resultsfrom different SearchInterface. + * * @param string $pattern + * @param number $start + * @param number $limit + * @param string $format + * + * @throws UnknowSearchDomainException if the domain is unknow + * + * @return array of results from different SearchInterface + */ + public function getSearchResults( + $pattern, + $start = 0, + $limit = 50, + array $options = [], + $format = 'html' + ) { + $terms = $this->parse($pattern); + $results = []; + + //sort searchServices by order + $sortedSearchServices = []; + + foreach ($this->searchServices as $service) { + $sortedSearchServices[$service->getOrder()] = $service; + } + + if (null !== $terms['_domain']) { + foreach ($sortedSearchServices as $service) { + if ($service->supports($terms['_domain'], $format)) { + $results[] = $service->renderResult($terms, $start, $limit, $options); + } + } + + if (count($results) === 0) { + throw new UnknowSearchDomainException($terms['_domain']); + } + } else { // no domain provided, we use default search + foreach ($sortedSearchServices as $service) { + if ($service->isActiveByDefault()) { + $results[] = $service->renderResult($terms, $start, $limit, $options); + } + } + } + + //sort array + ksort($results); + + return $results; + } + + /** + * parse the search string to extract domain and terms. + * + * @param string $pattern + * * @return string[] an array where the keys are _domain, _default (residual terms) or term */ public function parse($pattern) { //reset must be extracted - $this->mustBeExtracted = array(); + $this->mustBeExtracted = []; //filter to lower and remove accentued $filteredPattern = mb_strtolower($this->remove_accents($pattern)); @@ -94,13 +228,27 @@ class SearchProvider } /** - * Extract the domain of the subject - * - * The domain begins with `@`. Example: `@person`, `@report`, .... - * - * @param type $subject + * extract default (residual) arguments. + * + * @param string $subject + * * @return string + */ + private function extractDefault($subject) + { + return trim(str_replace($this->mustBeExtracted, '', $subject)); + } + + /** + * Extract the domain of the subject. + * + * The domain begins with `@`. Example: `@person`, `@report`, .... + * + * @param type $subject + * * @throws ParsingException + * + * @return string */ private function extractDomain(&$subject) { @@ -115,20 +263,21 @@ class SearchProvider $this->mustBeExtracted[] = $terms[0][0]; } - return isset($terms[1][0]) ? $terms[1][0] : NULL; + return $terms[1][0] ?? null; } private function extractTerms(&$subject) { - $terms = array(); - preg_match_all('/([a-z\-]+):([\w\-]+|\([^\(\r\n]+\))/', $subject, $matches); + $terms = []; + $matches = []; + preg_match_all('/([a-z\-]+):(([^"][\S\-]+)|"[^"]*")/', $subject, $matches); foreach ($matches[2] as $key => $match) { //remove from search pattern $this->mustBeExtracted[] = $matches[0][$key]; //strip parenthesis - if (mb_substr($match, 0, 1) === '(' && - mb_substr($match, mb_strlen($match) - 1) === ')') { + if (mb_substr($match, 0, 1) === '"' + && mb_substr($match, mb_strlen($match) - 1) === '"') { $match = trim(mb_substr($match, 1, mb_strlen($match) - 2)); } $terms[$matches[1][$key]] = $match; @@ -137,128 +286,6 @@ class SearchProvider return $terms; } - /** - * store string which must be extracted to find default arguments - * - * @var string[] - */ - private $mustBeExtracted = array(); - - /** - * extract default (residual) arguments - * - * @param string $subject - * @return string - */ - private function extractDefault($subject) - { - return trim(str_replace($this->mustBeExtracted, '', $subject)); - } - - /** - * search through services which supports domain and give - * results as an array of resultsfrom different SearchInterface - * - * @param string $pattern - * @param number $start - * @param number $limit - * @param array $options - * @param string $format - * @return array of results from different SearchInterface - * @throws UnknowSearchDomainException if the domain is unknow - */ - public function getSearchResults($pattern, $start = 0, $limit = 50, - array $options = array(), $format = 'html') - { - $terms = $this->parse($pattern); - $results = array(); - - //sort searchServices by order - $sortedSearchServices = array(); - foreach($this->searchServices as $service) { - $sortedSearchServices[$service->getOrder()] = $service; - } - - if ($terms['_domain'] !== NULL) { - foreach ($sortedSearchServices as $service) { - if ($service->supports($terms['_domain'], $format)) { - $results[] = $service->renderResult($terms, $start, $limit, $options); - } - } - - if (count($results) === 0) { - throw new UnknowSearchDomainException($terms['_domain']); - } - } else { // no domain provided, we use default search - foreach($sortedSearchServices as $service) { - if ($service->isActiveByDefault()) { - $results[] = $service->renderResult($terms, $start, $limit, $options); - } - } - } - - //sort array - ksort($results); - - return $results; - } - - public function getResultByName($pattern, $name, $start = 0, $limit = 50, - array $options = array(), $format = 'html') - { - $terms = $this->parse($pattern); - $search = $this->getByName($name); - - if ($terms['_domain'] !== NULL && !$search->supports($terms['_domain'], $format)) - { - throw new ParsingException("The domain is not supported for the search name"); - } - - return $search->renderResult($terms, $start, $limit, $options, $format); - } - - /** - * return search services with a specific name, defined in service - * definition. - * - * @return SearchInterface - * @throws UnknowSearchNameException if not exists - */ - public function getByName($name) - { - if (isset($this->searchServices[$name])) { - return $this->searchServices[$name]; - } else { - throw new UnknowSearchNameException($name); - } - } - - /** - * return searchservice with an advanced form, defined in service - * definition. - * - * @param string $name - * @return HasAdvancedSearchForm - * @throws UnknowSearchNameException - */ - public function getHasAdvancedFormByName($name) - { - if (\array_key_exists($name, $this->hasAdvancedFormSearchServices)) { - return $this->hasAdvancedFormSearchServices[$name]; - } else { - throw new UnknowSearchNameException($name); - } - } - - public function addSearchService(SearchInterface $service, $name) - { - $this->searchServices[$name] = $service; - - if ($service instanceof HasAdvancedSearchFormInterface) { - $this->hasAdvancedFormSearchServices[$name] = $service; - } - } - /** * Converts all accent characters to ASCII characters. * @@ -267,191 +294,194 @@ class SearchProvider * Imported from wordpress : https://core.trac.wordpress.org/browser/tags/4.1/src/wp-includes/formatting.php?order=name * * @param string $string Text that might have accent characters + * * @return string Filtered string with replaced "nice" characters. + * * @license GNU GPLv2 : https://core.trac.wordpress.org/browser/tags/4.1/src/license.txt */ private function remove_accents($string) { - if (!preg_match('/[\x80-\xff]/', $string)) + if (!preg_match('/[\x80-\xff]/', $string)) { return $string; + } //if ($this->seems_utf8($string)) { // remove from wordpress: we use only UTF-8 if (true) { - $chars = array( - // Decompositions for Latin-1 Supplement - chr(194) . chr(170) => 'a', chr(194) . chr(186) => 'o', - chr(195) . chr(128) => 'A', chr(195) . chr(129) => 'A', - chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A', - chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A', - chr(195) . chr(134) => 'AE', chr(195) . chr(135) => 'C', - chr(195) . chr(136) => 'E', chr(195) . chr(137) => 'E', - chr(195) . chr(138) => 'E', chr(195) . chr(139) => 'E', - chr(195) . chr(140) => 'I', chr(195) . chr(141) => 'I', - chr(195) . chr(142) => 'I', chr(195) . chr(143) => 'I', - chr(195) . chr(144) => 'D', chr(195) . chr(145) => 'N', - chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O', - chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O', - chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U', - chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U', - chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y', - chr(195) . chr(158) => 'TH', chr(195) . chr(159) => 's', - chr(195) . chr(160) => 'a', chr(195) . chr(161) => 'a', - chr(195) . chr(162) => 'a', chr(195) . chr(163) => 'a', - chr(195) . chr(164) => 'a', chr(195) . chr(165) => 'a', - chr(195) . chr(166) => 'ae', chr(195) . chr(167) => 'c', - chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e', - chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e', - chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i', - chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i', - chr(195) . chr(176) => 'd', chr(195) . chr(177) => 'n', - chr(195) . chr(178) => 'o', chr(195) . chr(179) => 'o', - chr(195) . chr(180) => 'o', chr(195) . chr(181) => 'o', - chr(195) . chr(182) => 'o', chr(195) . chr(184) => 'o', - chr(195) . chr(185) => 'u', chr(195) . chr(186) => 'u', - chr(195) . chr(187) => 'u', chr(195) . chr(188) => 'u', - chr(195) . chr(189) => 'y', chr(195) . chr(190) => 'th', - chr(195) . chr(191) => 'y', chr(195) . chr(152) => 'O', - // Decompositions for Latin Extended-A - chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a', - chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a', - chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a', - chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c', - chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c', - chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c', - chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c', - chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd', - chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd', - chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e', - chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e', - chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e', - chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e', - chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e', - chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g', - chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g', - chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g', - chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g', - chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h', - chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h', - chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i', - chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i', - chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i', - chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i', - chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i', - chr(196) . chr(178) => 'IJ', chr(196) . chr(179) => 'ij', - chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j', - chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k', - chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L', - chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L', - chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L', - chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L', - chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L', - chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N', - chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N', - chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N', - chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N', - chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N', - chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o', - chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o', - chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o', - chr(197) . chr(146) => 'OE', chr(197) . chr(147) => 'oe', - chr(197) . chr(148) => 'R', chr(197) . chr(149) => 'r', - chr(197) . chr(150) => 'R', chr(197) . chr(151) => 'r', - chr(197) . chr(152) => 'R', chr(197) . chr(153) => 'r', - chr(197) . chr(154) => 'S', chr(197) . chr(155) => 's', - chr(197) . chr(156) => 'S', chr(197) . chr(157) => 's', - chr(197) . chr(158) => 'S', chr(197) . chr(159) => 's', - chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's', - chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't', - chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't', - chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't', - chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u', - chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u', - chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u', - chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u', - chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u', - chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u', - chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w', - chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y', - chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z', - chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z', - chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z', - chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's', - // Decompositions for Latin Extended-B - chr(200) . chr(152) => 'S', chr(200) . chr(153) => 's', - chr(200) . chr(154) => 'T', chr(200) . chr(155) => 't', - // Euro Sign - chr(226) . chr(130) . chr(172) => 'E', - // GBP (Pound) Sign - chr(194) . chr(163) => '', - // Vowels with diacritic (Vietnamese) - // unmarked - chr(198) . chr(160) => 'O', chr(198) . chr(161) => 'o', - chr(198) . chr(175) => 'U', chr(198) . chr(176) => 'u', - // grave accent - chr(225) . chr(186) . chr(166) => 'A', chr(225) . chr(186) . chr(167) => 'a', - chr(225) . chr(186) . chr(176) => 'A', chr(225) . chr(186) . chr(177) => 'a', - chr(225) . chr(187) . chr(128) => 'E', chr(225) . chr(187) . chr(129) => 'e', - chr(225) . chr(187) . chr(146) => 'O', chr(225) . chr(187) . chr(147) => 'o', - chr(225) . chr(187) . chr(156) => 'O', chr(225) . chr(187) . chr(157) => 'o', - chr(225) . chr(187) . chr(170) => 'U', chr(225) . chr(187) . chr(171) => 'u', - chr(225) . chr(187) . chr(178) => 'Y', chr(225) . chr(187) . chr(179) => 'y', - // hook - chr(225) . chr(186) . chr(162) => 'A', chr(225) . chr(186) . chr(163) => 'a', - chr(225) . chr(186) . chr(168) => 'A', chr(225) . chr(186) . chr(169) => 'a', - chr(225) . chr(186) . chr(178) => 'A', chr(225) . chr(186) . chr(179) => 'a', - chr(225) . chr(186) . chr(186) => 'E', chr(225) . chr(186) . chr(187) => 'e', - chr(225) . chr(187) . chr(130) => 'E', chr(225) . chr(187) . chr(131) => 'e', - chr(225) . chr(187) . chr(136) => 'I', chr(225) . chr(187) . chr(137) => 'i', - chr(225) . chr(187) . chr(142) => 'O', chr(225) . chr(187) . chr(143) => 'o', - chr(225) . chr(187) . chr(148) => 'O', chr(225) . chr(187) . chr(149) => 'o', - chr(225) . chr(187) . chr(158) => 'O', chr(225) . chr(187) . chr(159) => 'o', - chr(225) . chr(187) . chr(166) => 'U', chr(225) . chr(187) . chr(167) => 'u', - chr(225) . chr(187) . chr(172) => 'U', chr(225) . chr(187) . chr(173) => 'u', - chr(225) . chr(187) . chr(182) => 'Y', chr(225) . chr(187) . chr(183) => 'y', - // tilde - chr(225) . chr(186) . chr(170) => 'A', chr(225) . chr(186) . chr(171) => 'a', - chr(225) . chr(186) . chr(180) => 'A', chr(225) . chr(186) . chr(181) => 'a', - chr(225) . chr(186) . chr(188) => 'E', chr(225) . chr(186) . chr(189) => 'e', - chr(225) . chr(187) . chr(132) => 'E', chr(225) . chr(187) . chr(133) => 'e', - chr(225) . chr(187) . chr(150) => 'O', chr(225) . chr(187) . chr(151) => 'o', - chr(225) . chr(187) . chr(160) => 'O', chr(225) . chr(187) . chr(161) => 'o', - chr(225) . chr(187) . chr(174) => 'U', chr(225) . chr(187) . chr(175) => 'u', - chr(225) . chr(187) . chr(184) => 'Y', chr(225) . chr(187) . chr(185) => 'y', - // acute accent - chr(225) . chr(186) . chr(164) => 'A', chr(225) . chr(186) . chr(165) => 'a', - chr(225) . chr(186) . chr(174) => 'A', chr(225) . chr(186) . chr(175) => 'a', - chr(225) . chr(186) . chr(190) => 'E', chr(225) . chr(186) . chr(191) => 'e', - chr(225) . chr(187) . chr(144) => 'O', chr(225) . chr(187) . chr(145) => 'o', - chr(225) . chr(187) . chr(154) => 'O', chr(225) . chr(187) . chr(155) => 'o', - chr(225) . chr(187) . chr(168) => 'U', chr(225) . chr(187) . chr(169) => 'u', - // dot below - chr(225) . chr(186) . chr(160) => 'A', chr(225) . chr(186) . chr(161) => 'a', - chr(225) . chr(186) . chr(172) => 'A', chr(225) . chr(186) . chr(173) => 'a', - chr(225) . chr(186) . chr(182) => 'A', chr(225) . chr(186) . chr(183) => 'a', - chr(225) . chr(186) . chr(184) => 'E', chr(225) . chr(186) . chr(185) => 'e', - chr(225) . chr(187) . chr(134) => 'E', chr(225) . chr(187) . chr(135) => 'e', - chr(225) . chr(187) . chr(138) => 'I', chr(225) . chr(187) . chr(139) => 'i', - chr(225) . chr(187) . chr(140) => 'O', chr(225) . chr(187) . chr(141) => 'o', - chr(225) . chr(187) . chr(152) => 'O', chr(225) . chr(187) . chr(153) => 'o', - chr(225) . chr(187) . chr(162) => 'O', chr(225) . chr(187) . chr(163) => 'o', - chr(225) . chr(187) . chr(164) => 'U', chr(225) . chr(187) . chr(165) => 'u', - chr(225) . chr(187) . chr(176) => 'U', chr(225) . chr(187) . chr(177) => 'u', - chr(225) . chr(187) . chr(180) => 'Y', chr(225) . chr(187) . chr(181) => 'y', - // Vowels with diacritic (Chinese, Hanyu Pinyin) - chr(201) . chr(145) => 'a', - // macron - chr(199) . chr(149) => 'U', chr(199) . chr(150) => 'u', - // acute accent - chr(199) . chr(151) => 'U', chr(199) . chr(152) => 'u', - // caron - chr(199) . chr(141) => 'A', chr(199) . chr(142) => 'a', - chr(199) . chr(143) => 'I', chr(199) . chr(144) => 'i', - chr(199) . chr(145) => 'O', chr(199) . chr(146) => 'o', - chr(199) . chr(147) => 'U', chr(199) . chr(148) => 'u', - chr(199) . chr(153) => 'U', chr(199) . chr(154) => 'u', - // grave accent - chr(199) . chr(155) => 'U', chr(199) . chr(156) => 'u', - ); + $chars = [ + // Decompositions for Latin-1 Supplement + chr(194) . chr(170) => 'a', chr(194) . chr(186) => 'o', + chr(195) . chr(128) => 'A', chr(195) . chr(129) => 'A', + chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A', + chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A', + chr(195) . chr(134) => 'AE', chr(195) . chr(135) => 'C', + chr(195) . chr(136) => 'E', chr(195) . chr(137) => 'E', + chr(195) . chr(138) => 'E', chr(195) . chr(139) => 'E', + chr(195) . chr(140) => 'I', chr(195) . chr(141) => 'I', + chr(195) . chr(142) => 'I', chr(195) . chr(143) => 'I', + chr(195) . chr(144) => 'D', chr(195) . chr(145) => 'N', + chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O', + chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O', + chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U', + chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U', + chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y', + chr(195) . chr(158) => 'TH', chr(195) . chr(159) => 's', + chr(195) . chr(160) => 'a', chr(195) . chr(161) => 'a', + chr(195) . chr(162) => 'a', chr(195) . chr(163) => 'a', + chr(195) . chr(164) => 'a', chr(195) . chr(165) => 'a', + chr(195) . chr(166) => 'ae', chr(195) . chr(167) => 'c', + chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e', + chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e', + chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i', + chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i', + chr(195) . chr(176) => 'd', chr(195) . chr(177) => 'n', + chr(195) . chr(178) => 'o', chr(195) . chr(179) => 'o', + chr(195) . chr(180) => 'o', chr(195) . chr(181) => 'o', + chr(195) . chr(182) => 'o', chr(195) . chr(184) => 'o', + chr(195) . chr(185) => 'u', chr(195) . chr(186) => 'u', + chr(195) . chr(187) => 'u', chr(195) . chr(188) => 'u', + chr(195) . chr(189) => 'y', chr(195) . chr(190) => 'th', + chr(195) . chr(191) => 'y', chr(195) . chr(152) => 'O', + // Decompositions for Latin Extended-A + chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a', + chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a', + chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a', + chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c', + chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c', + chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c', + chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c', + chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd', + chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd', + chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e', + chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e', + chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e', + chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e', + chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e', + chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g', + chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g', + chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g', + chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g', + chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h', + chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h', + chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i', + chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i', + chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i', + chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i', + chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i', + chr(196) . chr(178) => 'IJ', chr(196) . chr(179) => 'ij', + chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j', + chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k', + chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L', + chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L', + chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L', + chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L', + chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L', + chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N', + chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N', + chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N', + chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N', + chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N', + chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o', + chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o', + chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o', + chr(197) . chr(146) => 'OE', chr(197) . chr(147) => 'oe', + chr(197) . chr(148) => 'R', chr(197) . chr(149) => 'r', + chr(197) . chr(150) => 'R', chr(197) . chr(151) => 'r', + chr(197) . chr(152) => 'R', chr(197) . chr(153) => 'r', + chr(197) . chr(154) => 'S', chr(197) . chr(155) => 's', + chr(197) . chr(156) => 'S', chr(197) . chr(157) => 's', + chr(197) . chr(158) => 'S', chr(197) . chr(159) => 's', + chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's', + chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't', + chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't', + chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't', + chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u', + chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u', + chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u', + chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u', + chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u', + chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u', + chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w', + chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y', + chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z', + chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z', + chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z', + chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's', + // Decompositions for Latin Extended-B + chr(200) . chr(152) => 'S', chr(200) . chr(153) => 's', + chr(200) . chr(154) => 'T', chr(200) . chr(155) => 't', + // Euro Sign + chr(226) . chr(130) . chr(172) => 'E', + // GBP (Pound) Sign + chr(194) . chr(163) => '', + // Vowels with diacritic (Vietnamese) + // unmarked + chr(198) . chr(160) => 'O', chr(198) . chr(161) => 'o', + chr(198) . chr(175) => 'U', chr(198) . chr(176) => 'u', + // grave accent + chr(225) . chr(186) . chr(166) => 'A', chr(225) . chr(186) . chr(167) => 'a', + chr(225) . chr(186) . chr(176) => 'A', chr(225) . chr(186) . chr(177) => 'a', + chr(225) . chr(187) . chr(128) => 'E', chr(225) . chr(187) . chr(129) => 'e', + chr(225) . chr(187) . chr(146) => 'O', chr(225) . chr(187) . chr(147) => 'o', + chr(225) . chr(187) . chr(156) => 'O', chr(225) . chr(187) . chr(157) => 'o', + chr(225) . chr(187) . chr(170) => 'U', chr(225) . chr(187) . chr(171) => 'u', + chr(225) . chr(187) . chr(178) => 'Y', chr(225) . chr(187) . chr(179) => 'y', + // hook + chr(225) . chr(186) . chr(162) => 'A', chr(225) . chr(186) . chr(163) => 'a', + chr(225) . chr(186) . chr(168) => 'A', chr(225) . chr(186) . chr(169) => 'a', + chr(225) . chr(186) . chr(178) => 'A', chr(225) . chr(186) . chr(179) => 'a', + chr(225) . chr(186) . chr(186) => 'E', chr(225) . chr(186) . chr(187) => 'e', + chr(225) . chr(187) . chr(130) => 'E', chr(225) . chr(187) . chr(131) => 'e', + chr(225) . chr(187) . chr(136) => 'I', chr(225) . chr(187) . chr(137) => 'i', + chr(225) . chr(187) . chr(142) => 'O', chr(225) . chr(187) . chr(143) => 'o', + chr(225) . chr(187) . chr(148) => 'O', chr(225) . chr(187) . chr(149) => 'o', + chr(225) . chr(187) . chr(158) => 'O', chr(225) . chr(187) . chr(159) => 'o', + chr(225) . chr(187) . chr(166) => 'U', chr(225) . chr(187) . chr(167) => 'u', + chr(225) . chr(187) . chr(172) => 'U', chr(225) . chr(187) . chr(173) => 'u', + chr(225) . chr(187) . chr(182) => 'Y', chr(225) . chr(187) . chr(183) => 'y', + // tilde + chr(225) . chr(186) . chr(170) => 'A', chr(225) . chr(186) . chr(171) => 'a', + chr(225) . chr(186) . chr(180) => 'A', chr(225) . chr(186) . chr(181) => 'a', + chr(225) . chr(186) . chr(188) => 'E', chr(225) . chr(186) . chr(189) => 'e', + chr(225) . chr(187) . chr(132) => 'E', chr(225) . chr(187) . chr(133) => 'e', + chr(225) . chr(187) . chr(150) => 'O', chr(225) . chr(187) . chr(151) => 'o', + chr(225) . chr(187) . chr(160) => 'O', chr(225) . chr(187) . chr(161) => 'o', + chr(225) . chr(187) . chr(174) => 'U', chr(225) . chr(187) . chr(175) => 'u', + chr(225) . chr(187) . chr(184) => 'Y', chr(225) . chr(187) . chr(185) => 'y', + // acute accent + chr(225) . chr(186) . chr(164) => 'A', chr(225) . chr(186) . chr(165) => 'a', + chr(225) . chr(186) . chr(174) => 'A', chr(225) . chr(186) . chr(175) => 'a', + chr(225) . chr(186) . chr(190) => 'E', chr(225) . chr(186) . chr(191) => 'e', + chr(225) . chr(187) . chr(144) => 'O', chr(225) . chr(187) . chr(145) => 'o', + chr(225) . chr(187) . chr(154) => 'O', chr(225) . chr(187) . chr(155) => 'o', + chr(225) . chr(187) . chr(168) => 'U', chr(225) . chr(187) . chr(169) => 'u', + // dot below + chr(225) . chr(186) . chr(160) => 'A', chr(225) . chr(186) . chr(161) => 'a', + chr(225) . chr(186) . chr(172) => 'A', chr(225) . chr(186) . chr(173) => 'a', + chr(225) . chr(186) . chr(182) => 'A', chr(225) . chr(186) . chr(183) => 'a', + chr(225) . chr(186) . chr(184) => 'E', chr(225) . chr(186) . chr(185) => 'e', + chr(225) . chr(187) . chr(134) => 'E', chr(225) . chr(187) . chr(135) => 'e', + chr(225) . chr(187) . chr(138) => 'I', chr(225) . chr(187) . chr(139) => 'i', + chr(225) . chr(187) . chr(140) => 'O', chr(225) . chr(187) . chr(141) => 'o', + chr(225) . chr(187) . chr(152) => 'O', chr(225) . chr(187) . chr(153) => 'o', + chr(225) . chr(187) . chr(162) => 'O', chr(225) . chr(187) . chr(163) => 'o', + chr(225) . chr(187) . chr(164) => 'U', chr(225) . chr(187) . chr(165) => 'u', + chr(225) . chr(187) . chr(176) => 'U', chr(225) . chr(187) . chr(177) => 'u', + chr(225) . chr(187) . chr(180) => 'Y', chr(225) . chr(187) . chr(181) => 'y', + // Vowels with diacritic (Chinese, Hanyu Pinyin) + chr(201) . chr(145) => 'a', + // macron + chr(199) . chr(149) => 'U', chr(199) . chr(150) => 'u', + // acute accent + chr(199) . chr(151) => 'U', chr(199) . chr(152) => 'u', + // caron + chr(199) . chr(141) => 'A', chr(199) . chr(142) => 'a', + chr(199) . chr(143) => 'I', chr(199) . chr(144) => 'i', + chr(199) . chr(145) => 'O', chr(199) . chr(146) => 'o', + chr(199) . chr(147) => 'U', chr(199) . chr(148) => 'u', + chr(199) . chr(153) => 'U', chr(199) . chr(154) => 'u', + // grave accent + chr(199) . chr(155) => 'U', chr(199) . chr(156) => 'u', + ]; // Used for locale-specific rules /* remove from wordpress @@ -477,7 +507,7 @@ class SearchProvider $string = strtr($string, $chars); } /* remove from wordpress: we use only utf 8 * else { - + // Assume ISO-8859-1 if not UTF-8 $chars['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158) . chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194) @@ -500,5 +530,4 @@ class SearchProvider return $string; } - } diff --git a/src/Bundle/ChillMainBundle/Search/UnknowSearchDomainException.php b/src/Bundle/ChillMainBundle/Search/UnknowSearchDomainException.php index 5c09ebe39..6868a53f6 100644 --- a/src/Bundle/ChillMainBundle/Search/UnknowSearchDomainException.php +++ b/src/Bundle/ChillMainBundle/Search/UnknowSearchDomainException.php @@ -1,42 +1,32 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Search; +use Exception; + /** - * Throw by search provider when the search name is not found - * - * @author Julien Fastré + * Throw by search provider when the search name is not found. */ -class UnknowSearchDomainException extends \Exception +class UnknowSearchDomainException extends Exception { - private $domain; - + public function __construct($domain) { - parent::__construct( "The domain $domain is not found"); + parent::__construct("The domain {$domain} is not found"); $this->domain = $domain; } - - public function getDomain() + + public function getDomain() { return $this->domain; } diff --git a/src/Bundle/ChillMainBundle/Search/UnknowSearchNameException.php b/src/Bundle/ChillMainBundle/Search/UnknowSearchNameException.php index 989fdbec5..36ee550e0 100644 --- a/src/Bundle/ChillMainBundle/Search/UnknowSearchNameException.php +++ b/src/Bundle/ChillMainBundle/Search/UnknowSearchNameException.php @@ -1,41 +1,31 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Search; +use Exception; + /** - * Throw by search provider when the search name is not found - * - * @author Julien Fastré + * Throw by search provider when the search name is not found. */ -class UnknowSearchNameException extends \Exception +class UnknowSearchNameException extends Exception { - private $name; - + public function __construct($name) { - parent::__construct( "No module search supports with the name $name"); + parent::__construct("No module search supports with the name {$name}"); $this->name = $name; } - + public function getName() { return $this->name; diff --git a/src/Bundle/ChillMainBundle/Search/Utils/ExtractDateFromPattern.php b/src/Bundle/ChillMainBundle/Search/Utils/ExtractDateFromPattern.php new file mode 100644 index 000000000..058bfe070 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Search/Utils/ExtractDateFromPattern.php @@ -0,0 +1,49 @@ + ''])); + } + } + } + + return new SearchExtractionResult($filteredSubject, $dates); + } +} diff --git a/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php b/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php new file mode 100644 index 000000000..caad6d093 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php @@ -0,0 +1,79 @@ + $char) { + switch ($char) { + case '0': + $length++; + + if (0 === $key) { + $phonenumber[] = '+32'; + } else { + $phonenumber[] = $char; + } + + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + $length++; + $phonenumber[] = $char; + + break; + + case ' ': + break; + + default: + throw new LogicException('should not match not alnum character'); + } + } + + if (5 < $length) { + $filtered = trim(strtr($subject, [$matches[0] => ''])); + + return new SearchExtractionResult($filtered, [implode('', $phonenumber)]); + } + } + + return new SearchExtractionResult($subject, []); + } +} diff --git a/src/Bundle/ChillMainBundle/Search/Utils/SearchExtractionResult.php b/src/Bundle/ChillMainBundle/Search/Utils/SearchExtractionResult.php new file mode 100644 index 000000000..23c1dae6b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Search/Utils/SearchExtractionResult.php @@ -0,0 +1,40 @@ +filteredSubject = $filteredSubject; + $this->found = $found; + } + + public function getFilteredSubject(): string + { + return $this->filteredSubject; + } + + public function getFound(): array + { + return $this->found; + } + + public function hasResult(): bool + { + return [] !== $this->found; + } +} diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php index 9131a6501..2cca61e9f 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php @@ -1,46 +1,42 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Security\Authorization; -use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use function get_class; +use function in_array; +use const E_USER_DEPRECATED; /** * Voter for Chill software. * * This abstract Voter provide generic methods to handle object specific to Chill - * - * - * @author Julien Fastré */ abstract class AbstractChillVoter extends Voter implements ChillVoterInterface { protected function supports($attribute, $subject) { - @trigger_error('This voter should implements the new `supports` ' + @trigger_error( + 'This voter should implements the new `supports` ' . 'methods introduced by Symfony 3.0, and do not rely on ' . 'getSupportedAttributes and getSupportedClasses methods.', - E_USER_DEPRECATED); + E_USER_DEPRECATED + ); - return \in_array($attribute, $this->getSupportedAttributes($attribute)) - && \in_array(\get_class($subject), $this->getSupportedClasses()); + // @TODO: getSupportedAttributes() should be created in here and made abstract or in ChillVoterInterface. + // @TODO: getSupportedClasses() should be created in here and made abstract or in ChillVoterInterface. + return in_array($attribute, $this->getSupportedAttributes($attribute), true) + && in_array(get_class($subject), $this->getSupportedClasses(), true); } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) @@ -49,7 +45,7 @@ abstract class AbstractChillVoter extends Voter implements ChillVoterInterface . 'methods introduced by Symfony 3.0, and do not rely on ' . 'isGranted method', E_USER_DEPRECATED); + // @TODO: isGranted() should be created in here and made abstract or in ChillVoterInterface. return $this->isGranted($attribute, $subject, $token->getUser()); } - } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php index afff931ed..de7bff9ab 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php @@ -1,102 +1,227 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Security\Authorization; -use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\Center; -use Chill\MainBundle\Entity\HasCenterInterface; -use Chill\MainBundle\Entity\HasScopeInterface; -use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; -use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; -use Chill\MainBundle\Security\Resolver\ScopeResolverInterface; -use Psr\Log\LoggerInterface; -use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; -use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; -use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\Entity\Scope; -use Chill\MainBundle\Security\RoleProvider; -use Doctrine\ORM\EntityManagerInterface; -use Chill\MainBundle\Entity\GroupCenter; -use Chill\MainBundle\Entity\RoleScope; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface; +use Chill\MainBundle\Security\ParentRoleHelper; +use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; +use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; +use Psr\Log\LoggerInterface; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Core\User\UserInterface; +use Traversable; +use UnexpectedValueException; +use function array_merge; +use function get_class; /** * Helper for authorizations. * * Provides methods for user and entities information. - * */ class AuthorizationHelper implements AuthorizationHelperInterface { - protected RoleHierarchyInterface $roleHierarchy; + private CenterResolverDispatcherInterface $centerResolverDispatcher; - /** - * The role in a hierarchy, given by the parameter - * `security.role_hierarchy.roles` from the container. - * - * @var string[] - */ - protected array $hierarchy; + private LoggerInterface $logger; - protected EntityManagerInterface $em; + private ParentRoleHelper $parentRoleHelper; - protected CenterResolverDispatcher $centerResolverDispatcher; + private ScopeResolverDispatcher $scopeResolverDispatcher; - protected ScopeResolverDispatcher $scopeResolverDispatcher; - - protected LoggerInterface $logger; + private UserACLAwareRepositoryInterface $userACLAwareRepository; public function __construct( - RoleHierarchyInterface $roleHierarchy, - ParameterBagInterface $parameterBag, - EntityManagerInterface $em, - CenterResolverDispatcher $centerResolverDispatcher, + CenterResolverDispatcherInterface $centerResolverDispatcher, LoggerInterface $logger, - ScopeResolverDispatcher $scopeResolverDispatcher + ScopeResolverDispatcher $scopeResolverDispatcher, + UserACLAwareRepositoryInterface $userACLAwareRepository, + ParentRoleHelper $parentRoleHelper ) { - $this->roleHierarchy = $roleHierarchy; - $this->hierarchy = $parameterBag->get('security.role_hierarchy.roles'); - $this->em = $em; $this->centerResolverDispatcher = $centerResolverDispatcher; $this->logger = $logger; $this->scopeResolverDispatcher = $scopeResolverDispatcher; + $this->userACLAwareRepository = $userACLAwareRepository; + $this->parentRoleHelper = $parentRoleHelper; } /** - * Determines if a user is active on this center + * Filter an array of centers, return only center which are reachable. * - * If - * - * @param User $user - * @param Center|Center[] $center May be an array of center - * @return bool + * @param User $user The user + * @param array $centers a list of centers which are going to be filtered + * @param Center|string $role */ - public function userCanReachCenter(User $user, $center) + public function filterReachableCenters(User $user, array $centers, $role): array { - if ($center instanceof \Traversable) { + $results = []; + + if ($role instanceof Role) { + $role = $role->getRole(); + } + + foreach ($centers as $center) { + if ($this->userCanReachCenter($user, $center)) { + $results[] = $center; + } + } + + return $results; + } + + /** + * @deprecated use UserACLAwareRepositoryInterface::findUsersByReachedACL instead + * + * @param array|Center|Center[] $center + * @param array|Scope|Scope[]|null $scope + * + * @return User[] + */ + public function findUsersReaching(string $role, $center, $scope = null, bool $onlyEnabled = true): array + { + return $this->userACLAwareRepository + ->findUsersByReachedACL($role, $center, $scope, $onlyEnabled); + } + + /** + * Return all the role which give access to the given role. Only the role + * which are registered into Chill are taken into account. + * + * @return string[] the role which give access to the given $role + */ + public function getParentRoles(string $role): array + { + trigger_deprecation('Chill\MainBundle', '2.0', 'use ParentRoleHelper::getParentRoles instead'); + + return $this->parentRoleHelper->getParentRoles($role); + } + + /** + * Get reachable Centers for the given user, role, + * and optionally Scope. + * + * @return array|Center[] + */ + public function getReachableCenters(UserInterface $user, string $role, ?Scope $scope = null): array + { + if ($role instanceof Role) { + $role = $role->getRole(); + } + $centers = []; + + foreach ($user->getGroupCenters() as $groupCenter) { + $permissionGroup = $groupCenter->getPermissionsGroup(); + //iterate on roleScopes + foreach ($permissionGroup->getRoleScopes() as $roleScope) { + //check that the role is in the reachable roles + if ($this->isRoleReached($role, $roleScope->getRole())) { + if (null === $scope) { + $centers[] = $groupCenter->getCenter(); + + break; + } + + if ($scope->getId() === $roleScope->getScope()->getId()) { + $centers[] = $groupCenter->getCenter(); + + break; + } + } + } + } + + return $centers; + } + + /** + * Return all reachable circle for a given user, center and role. + * + * @param Role|string $role + * @param Center|Center[] $center + * + * @return Scope[] + */ + public function getReachableCircles(UserInterface $user, $role, $center) + { + $scopes = []; + + if (is_iterable($center)) { + foreach ($center as $c) { + $scopes = array_merge($scopes, $this->getReachableCircles($user, $role, $c)); + } + + return $scopes; + } + + if ($role instanceof Role) { + $role = $role->getRole(); + } + + foreach ($user->getGroupCenters() as $groupCenter) { + if ($center->getId() === $groupCenter->getCenter()->getId()) { + //iterate on permissionGroup + $permissionGroup = $groupCenter->getPermissionsGroup(); + //iterate on roleScopes + foreach ($permissionGroup->getRoleScopes() as $roleScope) { + //check that the role is in the reachable roles + if ($this->isRoleReached($role, $roleScope->getRole())) { + $scopes[] = $roleScope->getScope(); + } + } + } + } + + return $scopes; + } + + /** + * Return all reachable scope for a given user, center and role. + * + * @deprecated Use getReachableCircles + * + * @param Center|Center[] $center + * + * @return array|Scope[] + */ + public function getReachableScopes(UserInterface $user, string $role, $center): array + { + if ($role instanceof Role) { + $role = $role->getRole(); + } + + return $this->getReachableCircles($user, $role, $center); + } + + /** + * Determines if a user is active on this center. + * + * @param Center|Center[] $center May be an array of center + */ + public function userCanReachCenter(User $user, $center): bool + { + if ($center instanceof Traversable) { foreach ($center as $c) { if ($c->userCanReachCenter($user, $c)) { return true; } } + return false; - } elseif ($center instanceof Center) { + } + + if ($center instanceof Center) { foreach ($user->getGroupCenters() as $groupCenter) { if ($center->getId() === $groupCenter->getCenter()->getId()) { return true; @@ -106,21 +231,25 @@ class AuthorizationHelper implements AuthorizationHelperInterface return false; } - throw new \UnexpectedValueException(sprintf("The entity given is not an ". - "instance of %s, %s given", Center::class, get_class($center))); + throw new UnexpectedValueException( + sprintf( + 'The entity given is not an instance of %s, %s given', + Center::class, + get_class($center) + ) + ); } /** - * * Determines if the user has access to the given entity. * * if the entity implements Chill\MainBundle\Entity\HasScopeInterface, * the scope is taken into account. * - * @param User $user * @param mixed $entity the entity may also implement HasScopeInterface - * @param string|Role $attribute - * @return boolean true if the user has access + * @param Role|string $attribute + * + * @return bool true if the user has access */ public function userHasAccess(User $user, $entity, $attribute) { @@ -134,51 +263,71 @@ class AuthorizationHelper implements AuthorizationHelperInterface } return false; - } elseif ($center instanceof Center) { - return $this->userHasAccessForCenter($user, $center, $entity, $attribute); - } elseif (NULL === $center) { - return false; - } else { - throw new \UnexpectedValueException("could not resolver a center"); } + + if ($center instanceof Center) { + return $this->userHasAccessForCenter($user, $center, $entity, $attribute); + } + + if (null === $center) { + return false; + } + + throw new UnexpectedValueException('could not resolver a center'); + } + + /** + * Test if a parent role may give access to a given child role. + * + * @param string $childRole The role we want to test if he is reachable + * @param string $parentRole The role which should give access to $childRole + * + * @return bool true if the child role is granted by parent role + */ + private function isRoleReached(string $childRole, string $parentRole) + { + return $this->parentRoleHelper->isRoleReached($childRole, $parentRole); } private function userHasAccessForCenter(User $user, Center $center, $entity, $attribute): bool { if (!$this->userCanReachCenter($user, $center)) { - $this->logger->debug("user cannot reach center of entity", [ + $this->logger->debug('user cannot reach center of entity', [ 'center_name' => $center->getName(), - 'user' => $user->getUsername() + 'user' => $user->getUsername(), ]); + return false; } - foreach ($user->getGroupCenters() as $groupCenter){ + foreach ($user->getGroupCenters() as $groupCenter) { //filter on center if ($groupCenter->getCenter() === $center) { $permissionGroup = $groupCenter->getPermissionsGroup(); //iterate on roleScopes - foreach($permissionGroup->getRoleScopes() as $roleScope) { + foreach ($permissionGroup->getRoleScopes() as $roleScope) { //check that the role allow to reach the required role if ($this->isRoleReached($attribute, $roleScope->getRole())) { //if yes, we have a right on something... // perform check on scope if necessary if ($this->scopeResolverDispatcher->isConcerned($entity)) { - $scope = $this->scopeResolverDispatcher->resolveScope($entity); + $scope = $this->scopeResolverDispatcher->resolveScope($entity); - if (NULL === $scope) { - return true; - } elseif (is_iterable($scope)) { - foreach ($scope as $s) { - if ($s === $roleScope->getScope()) { - return true; - } - } - } else { - if ($scope === $roleScope->getScope()) { - return true; - } - } + if (null === $scope) { + return true; + } + + if (is_iterable($scope)) { + foreach ($scope as $s) { + if ($roleScope->getScope() === $s) { + return true; + } + } + } else { + if ($roleScope->getScope() === $scope) { + return true; + } + } } else { return true; } @@ -187,204 +336,11 @@ class AuthorizationHelper implements AuthorizationHelperInterface } } - $this->logger->debug("user can reach center entity, but not role", [ + $this->logger->debug('user can reach center entity, but not role', [ 'username' => $user->getUsername(), - 'center' => $center->getName() + 'center' => $center->getName(), ]); return false; } - - /** - * Get reachable Centers for the given user, role, - * and optionnaly Scope - * - * @param User $user - * @param string|Role $role - * @param null|Scope $scope - * @return Center[]|array - */ - public function getReachableCenters(User $user, string $role, ?Scope $scope = null): array - { - if ($role instanceof Role) { - $role = $role->getRole(); - } - $centers = array(); - - foreach ($user->getGroupCenters() as $groupCenter){ - $permissionGroup = $groupCenter->getPermissionsGroup(); - //iterate on roleScopes - foreach($permissionGroup->getRoleScopes() as $roleScope) { - //check that the role is in the reachable roles - if ($this->isRoleReached($role, $roleScope->getRole())) { - if ($scope === null) { - $centers[] = $groupCenter->getCenter(); - break 1; - } else { - if ($scope->getId() == $roleScope->getScope()->getId()){ - $centers[] = $groupCenter->getCenter(); - break 1; - } - } - } - } - - } - - return $centers; - } - - /** - * Filter an array of centers, return only center which are reachable - * - * @param User $user The user - * @param array $centers a list of centers which are going to be filtered - * @param string|Center $role - */ - public function filterReachableCenters(User $user, array $centers, $role): array - { - $results = []; - - if ($role instanceof Role) { - $role = $role->getRole(); - } - - foreach ($centers as $center) { - if ($this->userCanReachCenter($user, $center, $role)) { - $results[] = $center; - } - } - - return $results; - } - - /** - * Return all reachable scope for a given user, center and role - * - * @deprecated Use getReachableCircles - * - * @param User $user - * @param string role - * @param Center|Center[] $center - * @return Scope[]|array - */ - public function getReachableScopes(User $user, string $role, $center): array - { - if ($role instanceof Role) { - $role = $role->getRole(); - } - - return $this->getReachableCircles($user, $role, $center); - } - - /** - * Return all reachable circle for a given user, center and role - * - * @param User $user - * @param string|Role $role - * @param Center|Center[] $center - * @return Scope[] - */ - public function getReachableCircles(User $user, $role, $center) - { - $scopes = []; - - if (is_iterable($center)) { - foreach ($center as $c) { - $scopes = \array_merge($scopes, $this->getReachableCircles($user, $role, $c)); - } - - return $scopes; - } - - if ($role instanceof Role) { - $role = $role->getRole(); - } - - foreach ($user->getGroupCenters() as $groupCenter){ - if ($center->getId() === $groupCenter->getCenter()->getId()) { - //iterate on permissionGroup - $permissionGroup = $groupCenter->getPermissionsGroup(); - //iterate on roleScopes - foreach($permissionGroup->getRoleScopes() as $roleScope) { - //check that the role is in the reachable roles - if ($this->isRoleReached($role, $roleScope->getRole())) { - $scopes[] = $roleScope->getScope(); - } - } - } - } - - return $scopes; - } - - /** - * - * @return User[] - */ - public function findUsersReaching(string $role, Center $center, Scope $circle = null): array - { - $parents = $this->getParentRoles($role); - $parents[] = $role; - - $qb = $this->em->createQueryBuilder(); - $qb - ->select('u') - ->from(User::class, 'u') - ->join('u.groupCenters', 'gc') - ->join('gc.permissionsGroup', 'pg') - ->join('pg.roleScopes', 'rs') - ->where('gc.center = :center') - ->andWhere($qb->expr()->in('rs.role', $parents)) - ; - - $qb->setParameter('center', $center); - - if ($circle !== null) { - $qb->andWhere('rs.scope = :circle') - ->setParameter('circle', $circle) - ; - } - - return $qb->getQuery()->getResult(); - } - - /** - * Test if a parent role may give access to a given child role - * - * @param Role $childRole The role we want to test if he is reachable - * @param Role $parentRole The role which should give access to $childRole - * @return boolean true if the child role is granted by parent role - */ - protected function isRoleReached($childRole, $parentRole) - { - $reachableRoles = $this->roleHierarchy - ->getReachableRoleNames([$parentRole]); - - return in_array($childRole, $reachableRoles); - } - - /** - * Return all the role which give access to the given role. Only the role - * which are registered into Chill are taken into account. - * - * @param Role $role - * @return string[] the role which give access to the given $role - */ - public function getParentRoles($role): array - { - $parentRoles = []; - // transform the roles from role hierarchy from string to Role - $roles = \array_keys($this->hierarchy); - - foreach ($roles as $r) { - $childRoles = $this->roleHierarchy->getReachableRoleNames([$r]); - - if (\in_array($role, $childRoles)) { - $parentRoles[] = $r; - } - } - - return $parentRoles; - } } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelperInterface.php b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelperInterface.php index 31709bcd5..0e4d739f6 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelperInterface.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelperInterface.php @@ -1,32 +1,32 @@ - * - * 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\MainBundle\Security\Authorization; - -use Symfony\Component\Security\Core\Authorization\Voter\Voter; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Chill\MainBundle\Entity\User; -use Symfony\Component\Security\Core\Role\Role; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Security\Authorization; + +use Chill\MainBundle\Entity\User; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; + class ChillExportVoter extends Voter { - const EXPORT = 'chill_export'; - - /** - * - * @var AuthorizationHelper - */ - protected $authorizationHelper; - - public function __construct(AuthorizationHelper $authorizationHelper) + public const EXPORT = 'chill_export'; + + protected AuthorizationHelperInterface $authorizationHelper; + + public function __construct(AuthorizationHelperInterface $authorizationHelper) { $this->authorizationHelper = $authorizationHelper; } - + protected function supports($attribute, $subject): bool { - return $attribute === self::EXPORT; + return self::EXPORT === $attribute; } protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool @@ -53,10 +36,7 @@ class ChillExportVoter extends Voter if (!$token->getUser() instanceof User) { return false; } - - $centers = $this->authorizationHelper - ->getReachableCenters($token->getUser(), new Role($attribute)); - - return count($centers) > 0; + + return [] !== $this->authorizationHelper->getReachableCenters($token->getUser(), $attribute); } } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/ChillVoterInterface.php b/src/Bundle/ChillMainBundle/Security/Authorization/ChillVoterInterface.php index 3e89d2595..013cac51d 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/ChillVoterInterface.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/ChillVoterInterface.php @@ -1,30 +1,19 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Security\Authorization; /** * Provides methods for compiling voter and build admin role fields. - * - * @author Julien Fastré */ interface ChillVoterInterface { - } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelper.php b/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelper.php index ef1d319ac..95f070b3b 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelper.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelper.php @@ -1,26 +1,31 @@ authorizationHelper = $authorizationHelper; @@ -30,13 +35,13 @@ final class DefaultVoterHelper implements VoterHelperInterface public function supports($attribute, $subject): bool { - foreach ($this->configuration as list($attributes, $subj)) { - if ($subj === null) { - if ($subject === null && \in_array($attribute, $attributes)) { + foreach ($this->configuration as [$attributes, $subj]) { + if (null === $subj) { + if (null === $subject && in_array($attribute, $attributes, true)) { return true; } } elseif ($subject instanceof $subj) { - return \in_array($attribute, $attributes); + return in_array($attribute, $attributes, true); } } @@ -49,8 +54,8 @@ final class DefaultVoterHelper implements VoterHelperInterface return false; } - if (NULL === $subject) { - return 0 < count($this->authorizationHelper->getReachableCenters($token->getUser(), $attribute, null)); + if (null === $subject) { + return [] !== $this->authorizationHelper->getReachableCenters($token->getUser(), $attribute, null); } return $this->authorizationHelper->userHasAccess( diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperFactory.php b/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperFactory.php index 9349eaed2..f95e93c2b 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperFactory.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperFactory.php @@ -1,17 +1,27 @@ authorizationHelper = $authorizationHelper; $this->centerResolverDispatcher = $centerResolverDispatcher; diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperGenerator.php b/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperGenerator.php index c8300e45a..64d40ad15 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperGenerator.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/DefaultVoterHelperGenerator.php @@ -1,18 +1,29 @@ authorizationHelper = $authorizationHelper; $this->centerResolverDispatcher = $centerResolverDispatcher; @@ -20,9 +31,9 @@ final class DefaultVoterHelperGenerator implements VoterGeneratorInterface public function addCheckFor(?string $subject, array $attributes): self { - $this->configuration[] = [$attributes, $subject]; + $this->configuration[] = [$attributes, $subject]; - return $this; + return $this; } public function build(): VoterHelperInterface diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/VoterGeneratorInterface.php b/src/Bundle/ChillMainBundle/Security/Authorization/VoterGeneratorInterface.php index 70e517536..ebf35ad7e 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/VoterGeneratorInterface.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/VoterGeneratorInterface.php @@ -1,5 +1,14 @@ roleHierarchy = $roleHierarchy; + $this->hierarchy = $parameterBag->get('security.role_hierarchy.roles'); + } + + /** + * Return all the role which give access to the given role. Only the role + * which are registered into Chill are taken into account. + * + * The array contains always the current $role (which give access to himself) + * + * @return string[] the role which give access to the given $role + */ + public function getParentRoles(string $role): array + { + $parentRoles = [$role]; + // transform the roles from role hierarchy from string to Role + $roles = array_keys($this->hierarchy); + + foreach ($roles as $r) { + $childRoles = $this->roleHierarchy->getReachableRoleNames([$r]); + + if (in_array($role, $childRoles, true)) { + $parentRoles[] = $r; + } + } + + return $parentRoles; + } + + /** + * Test if a parent role may give access to a given child role. + * + * @param string $childRole The role we want to test if he is reachable + * @param string $parentRole The role which should give access to $childRole + * + * @return bool true if the child role is granted by parent role + */ + public function isRoleReached($childRole, $parentRole): bool + { + $reachableRoles = $this->roleHierarchy + ->getReachableRoleNames([$parentRole]); + + return in_array($childRole, $reachableRoles, true); + } +} diff --git a/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php b/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php index e9809d431..5e183e478 100644 --- a/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php +++ b/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php @@ -1,77 +1,75 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Security\PasswordRecover; use Chill\MainBundle\Entity\User; use Symfony\Component\EventDispatcher\Event; -/** - * - * - * @author Julien Fastré - */ class PasswordRecoverEvent extends Event { + public const ASK_TOKEN_INVALID_FORM = 'chill_main.ask_token_invalid_form'; + + public const ASK_TOKEN_SUCCESS = 'chill_main.ask_token_success'; + + public const INVALID_TOKEN = 'chill_main.password_recover_invalid_token'; + /** - * - * @var User - */ - protected $user; - - /** - * - * @var string - */ - protected $token; - - /** - * * @var string */ protected $ip; - + /** - * - * @var boolean + * @var bool */ protected $safelyGenerated; - - const ASK_TOKEN_INVALID_FORM = 'chill_main.ask_token_invalid_form'; - const INVALID_TOKEN = 'chill_main.password_recover_invalid_token'; - const ASK_TOKEN_SUCCESS = 'chill_main.ask_token_success'; - + + /** + * @var string + */ + protected $token; + + /** + * @var User + */ + protected $user; + /** - * * @param type $token * @param User $user * @param type $ip * @param bool $safelyGenerated true if generated safely (from console command, etc.) */ - public function __construct($token = null, User $user = null, $ip = null, bool $safelyGenerated = false) + public function __construct($token = null, ?User $user = null, $ip = null, bool $safelyGenerated = false) { $this->user = $user; $this->token = $token; $this->ip = $ip; $this->safelyGenerated = $safelyGenerated; } - + + public function getIp() + { + return $this->ip; + } + + /** + * @return string + */ + public function getToken() + { + return $this->token; + } + /** - * * @return User|null */ public function getUser() @@ -79,38 +77,16 @@ class PasswordRecoverEvent extends Event return $this->user; } - /** - * - * @return string - */ - public function getToken() - { - return $this->token; - } - - public function getIp() - { - return $this->ip; - } - - /** - * - * @return boolean - */ public function hasIp(): bool { return !empty($this->ip); } - - /** - * - * @return boolean - */ + public function hasUser(): bool { return $this->user instanceof User; } - + public function isSafelyGenerated(): bool { return $this->safelyGenerated; diff --git a/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEventSubscriber.php b/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEventSubscriber.php index 686a0dec5..3bd747428 100644 --- a/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEventSubscriber.php +++ b/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEventSubscriber.php @@ -1,83 +1,69 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Security\PasswordRecover; use Symfony\Component\EventDispatcher\EventSubscriberInterface; - -/** - * - * - * @author Julien Fastré - */ class PasswordRecoverEventSubscriber implements EventSubscriberInterface { /** - * * @var PasswordRecoverLocker */ protected $locker; - + public function __construct(PasswordRecoverLocker $locker) { $this->locker = $locker; } - public static function getSubscribedEvents(): array { return [ PasswordRecoverEvent::INVALID_TOKEN => [ - [ 'onInvalidToken' ] + ['onInvalidToken'], ], PasswordRecoverEvent::ASK_TOKEN_INVALID_FORM => [ - [ 'onAskTokenInvalidForm' ] + ['onAskTokenInvalidForm'], ], PasswordRecoverEvent::ASK_TOKEN_SUCCESS => [ - [ 'onAskTokenSuccess'] - ] + ['onAskTokenSuccess'], + ], ]; } - - public function onInvalidToken(PasswordRecoverEvent $event) - { - // set global lock - $this->locker->createLock('invalid_token_global', null); - - // set ip lock - if ($event->hasIp()) { - $this->locker->createLock('invalid_token_by_ip', $event->getIp()); - } - } - + public function onAskTokenInvalidForm(PasswordRecoverEvent $event) { // set global lock $this->locker->createLock('ask_token_invalid_form_global', null); - + // set ip lock if ($event->hasIp()) { $this->locker->createLock('ask_token_invalid_form_by_ip', $event->getIp()); } } - + public function onAskTokenSuccess(PasswordRecoverEvent $event) { $this->locker->createLock('ask_token_success_by_user', $event->getUser()); } + + public function onInvalidToken(PasswordRecoverEvent $event) + { + // set global lock + $this->locker->createLock('invalid_token_global', null); + + // set ip lock + if ($event->hasIp()) { + $this->locker->createLock('invalid_token_by_ip', $event->getIp()); + } + } } diff --git a/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverLocker.php b/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverLocker.php index 95c3890d0..223aa1494 100644 --- a/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverLocker.php +++ b/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverLocker.php @@ -1,170 +1,165 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Security\PasswordRecover; use Chill\MainBundle\Redis\ChillRedis; +use LogicException; use Psr\Log\LoggerInterface; +use UnexpectedValueException; -/** - * - * - * @author Julien Fastré - */ class PasswordRecoverLocker { - + public const ASK_TOKEN_INVALID_FORM_TTL = 3600; + /** - * The maximum of invalid token globally (across all ip) + * TTL to keep invalid token recorded. */ - const MAX_INVALID_TOKEN_GLOBAL = 50; - + public const INVALID_TOKEN_TTL = 3600; + + public const MAX_ASK_TOKEN_BY_USER = 10; + + public const MAX_ASK_TOKEN_INVALID_FORM_BY_IP = 10; + + public const MAX_ASK_TOKEN_INVALID_FORM_GLOBAL = 50; + /** - * The maximum of invalid token by ip + * The maximum of invalid token by ip. */ - const MAX_INVALID_TOKEN_BY_IP = 10; - + public const MAX_INVALID_TOKEN_BY_IP = 10; + /** - * TTL to keep invalid token recorded + * The maximum of invalid token globally (across all ip). */ - const INVALID_TOKEN_TTL = 3600; - - const MAX_ASK_TOKEN_INVALID_FORM_GLOBAL = 50; - - const MAX_ASK_TOKEN_INVALID_FORM_BY_IP = 10; - - const MAX_ASK_TOKEN_BY_USER = 10; - - const ASK_TOKEN_INVALID_FORM_TTL = 3600; - + public const MAX_INVALID_TOKEN_GLOBAL = 50; + /** - * * @var ChillRedis */ protected $chillRedis; - + /** - * * @var LoggerInterface */ protected $logger; - + public function __construct(ChillRedis $chillRedis, LoggerInterface $logger) { $this->chillRedis = $chillRedis; $this->logger = $logger; } - public function createLock($usage, $discriminator = null) { $max = $this->getMax($usage); $ttl = $this->getTtl($usage); - - for ($i = 0; $i < $max; $i++) { + + for ($i = 0; $i < $max; ++$i) { $key = self::generateLockKey($usage, $i, $discriminator); - + if (0 === $this->chillRedis->exists($key)) { $this->chillRedis->set($key, 1); $this->chillRedis->setTimeout($key, $ttl); - + break; } } } - - public function isLocked($usage, $discriminator = null) - { - $max = $this->getMax($usage); - - for ($i = 0; $i < $max; $i++) { - $key = self::generateLockKey($usage, $i, $discriminator); - if ($this->chillRedis->exists($key) === 0) { - return false; - } + /** + * @param string $usage 'invalid_token_global' or ... + * @param mixed|null $discriminator + */ + public static function generateLockKey($usage, int $number, $discriminator = null) + { + switch ($usage) { + case 'invalid_token_global': + return sprintf('invalid_token_global_%d', $number); + + case 'invalid_token_by_ip': + return sprintf('invalid_token_ip_%s_%d', $discriminator, $number); + + case 'ask_token_invalid_form_global': + return sprintf('ask_token_invalid_form_global_%d', $number); + + case 'ask_token_invalid_form_by_ip': + return sprintf('ask_token_invalid_form_by_ip_%s_%d', $discriminator, $number); + + case 'ask_token_success_by_user': + return sprintf('ask_token_success_by_user_%s_%d', $discriminator->getId(), $number); + + default: + throw new LogicException('this usage is not implemented'); } - - $this->logger->warning("locking reaching for password recovery", [ - 'usage' => $usage, - 'discriminator' => $discriminator - ]); - - return true; } - + public function getMax($usage) { switch ($usage) { case 'invalid_token_global': return self::MAX_INVALID_TOKEN_GLOBAL; + case 'invalid_token_by_ip': return self::MAX_INVALID_TOKEN_BY_IP; + case 'ask_token_invalid_form_global': return self::MAX_ASK_TOKEN_INVALID_FORM_GLOBAL; + case 'ask_token_invalid_form_by_ip': return self::MAX_ASK_TOKEN_INVALID_FORM_BY_IP; + case 'ask_token_success_by_user': return self::MAX_ASK_TOKEN_BY_USER; - + default: - throw new \UnexpectedValueException("this usage '$usage' is not yet implemented"); + throw new UnexpectedValueException("this usage '{$usage}' is not yet implemented"); } } - + public function getTtl($usage) { switch ($usage) { case 'invalid_token_global': case 'invalid_token_by_ip': return self::INVALID_TOKEN_TTL; - + case 'ask_token_invalid_form_global': case 'ask_token_invalid_form_by_ip': return self::ASK_TOKEN_INVALID_FORM_TTL; - + case 'ask_token_success_by_user': return self::ASK_TOKEN_INVALID_FORM_TTL * 24; - + default: - throw new \UnexpectedValueException("this usage '$usage' is not yet implemented"); + throw new UnexpectedValueException("this usage '{$usage}' is not yet implemented"); } } - - /** - * - * @param string $usage 'invalid_token_global' or ... - * @param int $number - */ - public static function generateLockKey($usage, int $number, $discriminator = null) + + public function isLocked($usage, $discriminator = null) { - switch($usage) { - case "invalid_token_global": - return sprintf('invalid_token_global_%d', $number); - case "invalid_token_by_ip": - return sprintf('invalid_token_ip_%s_%d', $discriminator, $number); - case "ask_token_invalid_form_global": - return sprintf('ask_token_invalid_form_global_%d', $number); - case "ask_token_invalid_form_by_ip": - return sprintf('ask_token_invalid_form_by_ip_%s_%d', $discriminator, $number); - case 'ask_token_success_by_user': - return sprintf('ask_token_success_by_user_%s_%d', $discriminator->getId(), $number); - default: - throw new \LogicException("this usage is not implemented"); + $max = $this->getMax($usage); + + for ($i = 0; $i < $max; ++$i) { + $key = self::generateLockKey($usage, $i, $discriminator); + + if ($this->chillRedis->exists($key) === 0) { + return false; + } } + + $this->logger->warning('locking reaching for password recovery', [ + 'usage' => $usage, + 'discriminator' => $discriminator, + ]); + + return true; } } diff --git a/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverVoter.php b/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverVoter.php index 324f76b02..40787fe74 100644 --- a/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverVoter.php +++ b/src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverVoter.php @@ -1,67 +1,55 @@ - * - * 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\MainBundle\Security\PasswordRecover; - -use Symfony\Component\Security\Core\Authorization\Voter\Voter; -use Chill\MainBundle\Entity\User; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\HttpFoundation\RequestStack; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Security\PasswordRecover; + +use Chill\MainBundle\Entity\User; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use function in_array; + class PasswordRecoverVoter extends Voter { - const TRY_TOKEN = 'CHILL_PASSWORD_TRY_TOKEN'; - const ASK_TOKEN = 'CHILL_PASSWORD_ASK_TOKEN'; - - protected $supported = [ - self::TRY_TOKEN, - self::ASK_TOKEN - ]; - + public const ASK_TOKEN = 'CHILL_PASSWORD_ASK_TOKEN'; + + public const TRY_TOKEN = 'CHILL_PASSWORD_TRY_TOKEN'; + /** - * * @var PasswordRecoverLocker */ protected $locker; - + /** - * * @var RequestStack */ protected $requestStack; - + + protected $supported = [ + self::TRY_TOKEN, + self::ASK_TOKEN, + ]; + public function __construct(PasswordRecoverLocker $locker, RequestStack $requestStack) { $this->locker = $locker; $this->requestStack = $requestStack; } - protected function supports($attribute, $subject): bool { - if (!in_array($attribute, $this->supported)) { + if (!in_array($attribute, $this->supported, true)) { return false; } - + return true; } @@ -69,34 +57,38 @@ class PasswordRecoverVoter extends Voter { switch ($attribute) { case self::TRY_TOKEN: - if (TRUE === $this->locker->isLocked('invalid_token_global')) { + if (true === $this->locker->isLocked('invalid_token_global')) { return false; } - + $ip = $this->requestStack->getCurrentRequest()->getClientIp(); - if (TRUE === $this->locker->isLocked('invalid_token_by_ip', $ip)) { + + if (true === $this->locker->isLocked('invalid_token_by_ip', $ip)) { return false; } - + return true; + case self::ASK_TOKEN: - if (TRUE === $this->locker->isLocked('ask_token_invalid_form_global')) { + if (true === $this->locker->isLocked('ask_token_invalid_form_global')) { return false; } - + $ip = $this->requestStack->getCurrentRequest()->getClientIp(); - if (TRUE === $this->locker->isLocked('ask_token_invalid_form_by_ip', $ip)) { + + if (true === $this->locker->isLocked('ask_token_invalid_form_by_ip', $ip)) { return false; } - + if ($subject instanceof User) { - if (TRUE === $this->locker->isLocked('ask_token_success_by_user', $subject)) { + if (true === $this->locker->isLocked('ask_token_success_by_user', $subject)) { return false; } } - + return true; - } + + return false; } } diff --git a/src/Bundle/ChillMainBundle/Security/PasswordRecover/RecoverPasswordHelper.php b/src/Bundle/ChillMainBundle/Security/PasswordRecover/RecoverPasswordHelper.php index 969f774a6..e6f3db0e7 100644 --- a/src/Bundle/ChillMainBundle/Security/PasswordRecover/RecoverPasswordHelper.php +++ b/src/Bundle/ChillMainBundle/Security/PasswordRecover/RecoverPasswordHelper.php @@ -1,59 +1,46 @@ - * - * 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\MainBundle\Security\PasswordRecover; - -use Chill\MainBundle\Security\PasswordRecover\TokenManager; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Chill\MainBundle\Notification\Mailer; -use Chill\MainBundle\Entity\User; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Security\PasswordRecover; + +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Notification\Mailer; +use DateTimeInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use function array_merge; + class RecoverPasswordHelper { + public const RECOVER_PASSWORD_ROUTE = 'password_recover'; + /** - * - * @var TokenManager - */ - protected $tokenManager; - - /** - * - * @var UrlGeneratorInterface - */ - protected $urlGenerator; - - /** - * * @var Mailer */ protected $mailer; - + protected $routeParameters; - - const RECOVER_PASSWORD_ROUTE = 'password_recover'; - + + /** + * @var TokenManager + */ + protected $tokenManager; + + /** + * @var UrlGeneratorInterface + */ + protected $urlGenerator; + public function __construct( - TokenManager $tokenManager, - UrlGeneratorInterface $urlGenerator, + TokenManager $tokenManager, + UrlGeneratorInterface $urlGenerator, Mailer $mailer, array $routeParameters ) { @@ -64,64 +51,65 @@ class RecoverPasswordHelper } /** - * - * @param User $user - * @param \DateTimeInterface $expiration * @param bool $absolute * @param array $parameters additional parameters to url + * * @return string */ - public function generateUrl(User $user, \DateTimeInterface $expiration, $absolute = true, array $parameters = []) + public function generateUrl(User $user, DateTimeInterface $expiration, $absolute = true, array $parameters = []) { - $context = $this->urlGenerator->getContext(); $previousHost = $context->getHost(); $previousScheme = $context->getScheme(); - + $context->setHost($this->routeParameters['host']); $context->setScheme($this->routeParameters['scheme']); - + $url = $this->urlGenerator->generate( - self::RECOVER_PASSWORD_ROUTE, - \array_merge( + self::RECOVER_PASSWORD_ROUTE, + array_merge( $this->tokenManager->generate($user, $expiration), - $parameters), + $parameters + ), $absolute ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH - ); - + ); + // reset the host $context->setHost($previousHost); $context->setScheme($previousScheme); - + return $url; } - + public function sendRecoverEmail( - User $user, - \DateTimeInterface $expiration, - $template = '@ChillMain/Password/recover_email.txt.twig', - array $templateParameters = [], + User $user, + DateTimeInterface $expiration, + $template = '@ChillMain/Password/recover_email.txt.twig', + array $templateParameters = [], $force = false, array $additionalUrlParameters = [], $emailSubject = 'Recover your password' ) { $content = $this->mailer->renderContentToUser( - $user, - $template, - \array_merge([ + $user, + $template, + array_merge( + [ 'user' => $user, - 'url' => $this->generateUrl($user, $expiration, true, $additionalUrlParameters) + 'url' => $this->generateUrl($user, $expiration, true, $additionalUrlParameters), ], $templateParameters - )); - + ) + ); + $this->mailer->sendNotification( - $user, - [ $emailSubject ], + $user, + [$emailSubject], [ 'text/plain' => $content, - ], - null, - $force); + ], + null, + $force + ); } } diff --git a/src/Bundle/ChillMainBundle/Security/PasswordRecover/TokenManager.php b/src/Bundle/ChillMainBundle/Security/PasswordRecover/TokenManager.php index 3dc0c1060..3d9eb29d2 100644 --- a/src/Bundle/ChillMainBundle/Security/PasswordRecover/TokenManager.php +++ b/src/Bundle/ChillMainBundle/Security/PasswordRecover/TokenManager.php @@ -1,103 +1,101 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Security\PasswordRecover; use Chill\MainBundle\Entity\User; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; use Psr\Log\LoggerInterface; +use UnexpectedValueException; +use function bin2hex; +use function hash; +use function hex2bin; +use function random_bytes; +use function strlen; +use function trim; -/** - * - * - * @author Julien Fastré - */ class TokenManager { + public const HASH = 'h'; + + public const TIMESTAMP = 'ts'; + + public const TOKEN = 't'; + + public const TOKEN_LENGTH = 24; + + public const USERNAME_CANONICAL = 'u'; + /** - * - * @var string - */ - protected $secret; - - /** - * * @var LoggerInterface */ protected $logger; - - const TOKEN = 't'; - const HASH = 'h'; - const TIMESTAMP = 'ts'; - const USERNAME_CANONICAL = 'u'; - - const TOKEN_LENGTH = 24; - + + /** + * @var string + */ + protected $secret; + public function __construct($secret, LoggerInterface $logger) { $this->secret = $secret; $this->logger = $logger; } - - public function generate(User $user, \DateTimeInterface $expiration) + + public function generate(User $user, DateTimeInterface $expiration) { - $token = \random_bytes(self::TOKEN_LENGTH); + $token = random_bytes(self::TOKEN_LENGTH); $username = $user->getUsernameCanonical(); - + if (empty($username)) { - throw new \UnexpectedValueException("username should not be empty to generate a token"); + throw new UnexpectedValueException('username should not be empty to generate a token'); } - - $timestamp = $expiration->getTimestamp(); - $hash = \hash('sha1', $token.$username.$timestamp.$this->secret); - - return [ - self::HASH => $hash, - self::TOKEN => \bin2hex($token), - self::TIMESTAMP => $timestamp, - self::USERNAME_CANONICAL => $username - ]; + + $timestamp = (string) $expiration->getTimestamp(); + $hash = hash('sha1', $token . $username . $timestamp . $this->secret); + + return [ + self::HASH => $hash, + self::TOKEN => bin2hex($token), + self::TIMESTAMP => $timestamp, + self::USERNAME_CANONICAL => $username, + ]; } - - public function verify($hash, $token, User $user, $timestamp) + + public function verify($hash, $token, User $user, string $timestamp) { - $token = \hex2bin(\trim($token)); - - if (\strlen($token) !== self::TOKEN_LENGTH) { + $token = hex2bin(trim($token)); + + if (strlen($token) !== self::TOKEN_LENGTH) { return false; } - + $username = $user->getUsernameCanonical(); - $date = \DateTimeImmutable::createFromFormat('U', $timestamp); - - if ($date < new \DateTime('now')) { - + $date = DateTimeImmutable::createFromFormat('U', $timestamp); + + if ($date < new DateTime('now')) { $this->logger->info('receiving a password recover token with expired ' . 'validity'); - + return false; } - - $expected = \hash('sha1', $token.$username.$timestamp.$this->secret); - + + $expected = hash('sha1', $token . $username . $timestamp . $this->secret); + if ($expected !== $hash) { return false; } - + return true; } - } diff --git a/src/Bundle/ChillMainBundle/Security/ProvideRoleHierarchyInterface.php b/src/Bundle/ChillMainBundle/Security/ProvideRoleHierarchyInterface.php index d5f49db5d..5a7d73eae 100644 --- a/src/Bundle/ChillMainBundle/Security/ProvideRoleHierarchyInterface.php +++ b/src/Bundle/ChillMainBundle/Security/ProvideRoleHierarchyInterface.php @@ -1,42 +1,34 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Security; /** - * Give a hierarchy for the role. - * - * This hierarchy allow to sort roles, which is useful in UI + * Give a hierarchy for the role. * - * @author Julien Fastré + * This hierarchy allow to sort roles, which is useful in UI */ interface ProvideRoleHierarchyInterface extends ProvideRoleInterface { /** * Return an array of roles, where keys are the hierarchy, and values * an array of roles. - * - * Example: - * + * + * Example: + * * ``` * [ 'Title' => [ 'CHILL_FOO_SEE', 'CHILL_FOO_UPDATE' ] ] * ``` - * - * @return array where keys are the hierarchy, and values an array of roles: `[ 'title' => [ 'CHILL_FOO_SEE', 'CHILL_FOO_UPDATE' ] ]` + * + * @return array> Where keys are the hierarchy, and values an array of roles: `[ 'title' => [ 'CHILL_FOO_SEE', 'CHILL_FOO_UPDATE' ] ]` */ - public function getRolesWithHierarchy(); + public function getRolesWithHierarchy(): array; } diff --git a/src/Bundle/ChillMainBundle/Security/ProvideRoleInterface.php b/src/Bundle/ChillMainBundle/Security/ProvideRoleInterface.php index 08a48181a..b6680946c 100644 --- a/src/Bundle/ChillMainBundle/Security/ProvideRoleInterface.php +++ b/src/Bundle/ChillMainBundle/Security/ProvideRoleInterface.php @@ -1,53 +1,43 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Security; /** - * Declare role - * + * Declare role. + * * The role are added to the configuration at compile time. - * - * The implemented object must be declared as a service and tagged as - * + * + * The implemented object must be declared as a service and tagged as + * *
      * my_role_declaration:
      *    # ...
      *    tags:
      *       - { name: chill.role }
      * 
    - * - * @author Julien Fastré */ interface ProvideRoleInterface { /** - * return an array of role provided by the object - * + * Return an array of role provided by the object. + * * @return string[] array of roles (as string) */ - public function getRoles(); - + public function getRoles(): array; + /** - * return roles which doesn't need - * + * Return roles which doesn't need. + * * @return string[] array of roles without scopes */ - public function getRolesWithoutScope(); + public function getRolesWithoutScope(): array; } diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverDispatcher.php b/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverDispatcher.php index 1e2f9c02b..e2fafbf7f 100644 --- a/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverDispatcher.php +++ b/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverDispatcher.php @@ -1,27 +1,41 @@ resolvers = $resolvers; } - /** - * @param mixed $entity - * @param array|null $options - * @return null|Center|Center[] - */ public function resolveCenter($entity, ?array $options = []) { - foreach($this->resolvers as $priority => $resolver) { + trigger_deprecation( + 'ChillMainBundle', + 'dev-master', + ' + Use the service CenterResolverManager through the interface CenterResolverManagerInterface. + The new method "CenterResolverManagerInterface::resolveCenters(): array" is available and the typing + has been improved in order to avoid mixing types. + ' + ); + + foreach ($this->resolvers as $resolver) { if ($resolver->supports($entity, $options)) { return $resolver->resolveCenter($entity, $options); } diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverDispatcherInterface.php b/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverDispatcherInterface.php new file mode 100644 index 000000000..95bd03e20 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverDispatcherInterface.php @@ -0,0 +1,27 @@ +resolvers = $resolvers; + } + + public function resolveCenters($entity, ?array $options = []): array + { + foreach ($this->resolvers as $resolver) { + if ($resolver->supports($entity, $options)) { + $resolved = $resolver->resolveCenter($entity, $options); + + if (null === $resolved) { + return []; + } + + if ($resolved instanceof Center) { + return [$resolved]; + } + + if (is_array($resolved)) { + return $resolved; + } + + throw new UnexpectedValueException(sprintf( + 'the return type of a %s should be an instance of %s, an array or null. Resolver is %s', + CenterResolverInterface::class, + Center::class, + get_class($resolver) + )); + } + } + + return []; + } +} diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverManagerInterface.php b/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverManagerInterface.php new file mode 100644 index 000000000..34de9ecb6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Resolver/CenterResolverManagerInterface.php @@ -0,0 +1,24 @@ +getCenter(); - } elseif ($entity instanceof HasCentersInterface) { - return $entity->getCenters(); - } else { - throw new \UnexpectedValueException("should be an instanceof"); } + + if ($entity instanceof HasCentersInterface) { + return $entity->getCenters(); + } + + throw new UnexpectedValueException('should be an instanceof'); } - public static function getDefaultPriority(): int + public function supports($entity, ?array $options = []): bool { - return -256; + return $entity instanceof HasCenterInterface || $entity instanceof HasCentersInterface; } } diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/DefaultScopeResolver.php b/src/Bundle/ChillMainBundle/Security/Resolver/DefaultScopeResolver.php index caf052764..b2b5e97e5 100644 --- a/src/Bundle/ChillMainBundle/Security/Resolver/DefaultScopeResolver.php +++ b/src/Bundle/ChillMainBundle/Security/Resolver/DefaultScopeResolver.php @@ -1,33 +1,25 @@ getScope(); - } elseif ($entity instanceof HasScopesInterface) { - return $entity->getScopes(); - } else { - throw new \UnexpectedValueException("should be an instanceof %s or %s", - HasScopesInterface::class, HasScopeInterface::class); - } + return -256; } public function isConcerned($entity, ?array $options = []): bool @@ -35,8 +27,28 @@ class DefaultScopeResolver implements ScopeResolverInterface return $entity instanceof HasScopeInterface || $entity instanceof HasScopesInterface; } - public static function getDefaultPriority(): int + /** + * @param HasScopeInterface|HasScopesInterface $entity + */ + public function resolveScope($entity, ?array $options = []) { - return -256; + if ($entity instanceof HasScopeInterface) { + return $entity->getScope(); + } + + if ($entity instanceof HasScopesInterface) { + return $entity->getScopes(); + } + + throw new UnexpectedValueException( + 'should be an instanceof %s or %s', + HasScopesInterface::class, + HasScopeInterface::class + ); + } + + public function supports($entity, ?array $options = []): bool + { + return $entity instanceof HasScopeInterface || $entity instanceof HasScopesInterface; } } diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/ResolverTwigExtension.php b/src/Bundle/ChillMainBundle/Security/Resolver/ResolverTwigExtension.php index 21ece21a3..66bfdb4ee 100644 --- a/src/Bundle/ChillMainBundle/Security/Resolver/ResolverTwigExtension.php +++ b/src/Bundle/ChillMainBundle/Security/Resolver/ResolverTwigExtension.php @@ -1,36 +1,66 @@ centerResolverDispatcher = $centerResolverDispatcher; + $this->scopeResolverDispatcher = $scopeResolverDispatcher; } public function getFilters() { return [ - new TwigFilter('chill_resolve_center', [$this, 'resolveCenter']) + new TwigFilter('chill_resolve_center', [$this, 'resolveCenter']), + new TwigFilter('chill_resolve_scope', [$this, 'resolveScope']), + new TwigFilter('chill_is_scope_concerned', [$this, 'isScopeConcerned']), ]; } + /** + * @param $entity + * + * @return bool + */ + public function isScopeConcerned($entity, ?array $options = []) + { + return $this->scopeResolverDispatcher->isConcerned($entity, $options); + } + /** * @param mixed $entity - * @param array|null $options + * * @return Center|Center[]|null */ public function resolveCenter($entity, ?array $options = []) { - return $this->centerResolverDispatcher->resolveCenter($entity, $options); + return $this->centerResolverDispatcher->resolveCenters($entity, $options); } + /** + * @param $entity + * + * @return array|\Chill\MainBundle\Entity\Scope|\Chill\MainBundle\Entity\Scope[] + */ + public function resolveScope($entity, ?array $options = []) + { + return $this->scopeResolverDispatcher->resolveScope(); + } } diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php b/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php index cc3f11560..0e4cfdc78 100644 --- a/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php +++ b/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php @@ -1,8 +1,18 @@ resolvers = $resolvers; } - /** - * @param $entity - * @return Scope|Scope[]|iterable - */ - public function resolveScope($entity, ?array $options = []) - { - foreach ($this->resolvers as $resolver) { - if ($resolver->supports($entity, $options)) { - return $resolver->resolveScope($entity, $options); - } - } - - return null; - } - public function isConcerned($entity, ?array $options = []): bool { foreach ($this->resolvers as $resolver) { @@ -41,4 +36,26 @@ final class ScopeResolverDispatcher return false; } + + /** + * @param $entity + * + * @return iterable|Scope|Scope[] + */ + public function resolveScope($entity, ?array $options = []) + { + foreach ($this->resolvers as $resolver) { + if ($resolver->supports($entity, $options)) { + $scopes = $resolver->resolveScope($entity, $options); + + if ($scopes instanceof Collection) { + return $scopes->toArray(); + } + + return $scopes; + } + } + + return null; + } } diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverInterface.php b/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverInterface.php index 04e949a59..2d5601ffa 100644 --- a/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverInterface.php +++ b/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverInterface.php @@ -1,5 +1,14 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Security; -/** - * - * - * @author Julien Fastré - */ +use function array_fill_keys; +use function array_key_exists; + class RoleProvider { /** - * * @var ProvideRoleInterface[] */ - private $providers = array(); - + private $providers = []; + /** * an array where keys are the role, and value is the title * for the given role. - * + * * Null when not initialized. * * @var array|null */ - private $rolesTitlesCache = null; - + private $rolesTitlesCache; + /** - * Add a role provider - * + * Add a role provider. + * * @internal This function is called by the dependency injector: it inject provider + * * @param \Chill\MainBundle\Security\ProvideRoleInterface $provider */ - public function addProvider(ProvideRoleInterface $provider) + public function addProvider(ProvideRoleInterface $provider) { $this->providers[] = $provider; } - - /** - * - * @return string[] the roles as string - */ - public function getRoles() + + public function getRoles(): array { - $roles = array(); + $roles = []; + foreach ($this->providers as $provider) { - if ($provider->getRoles() !== NULL) { + if ($provider->getRoles() !== null) { $roles = array_merge($roles, $provider->getRoles()); } } - + return $roles; } - - /** - * - * @return string[] the roles as string - */ - public function getRolesWithoutScopes() + + public function getRolesWithoutScopes(): array { - $roles = array(); + $roles = []; + foreach ($this->providers as $provider) { - if ($provider->getRolesWithoutScope() !== NULL) { + if ($provider->getRolesWithoutScope() !== null) { $roles = array_merge($roles, $provider->getRolesWithoutScope()); } } - + return $roles; } - - /** - * initialize the array for caching role and titles - * - */ - private function initializeRolesTitlesCache() - { - // break if already initialized - if ($this->rolesTitlesCache !== null) { - return; - } - - foreach ($this->providers as $provider) { - if ($provider instanceof ProvideRoleHierarchyInterface) { - foreach ($provider->getRolesWithHierarchy() as $title => $roles) { - foreach($roles as $role) { - $this->rolesTitlesCache[$role] = $title; - } - } - } else { - if ($provider->getRoles() !== null) { - $this->rolesTitlesCache = \array_merge( - $this->rolesTitlesCache, - \array_fill_keys($provider->getRoles(), null) - ); - } - } - } - } - + /** * Get the title for each role. - * + * * @param string $role + * * @return string the title of the role */ public function getRoleTitle($role) { $this->initializeRolesTitlesCache(); - - if (! \array_key_exists($role, $this->rolesTitlesCache)) { - // this case might happens when the role is not described in + + if (!array_key_exists($role, $this->rolesTitlesCache)) { + // this case might happens when the role is not described in // `getRolesWithHierarchy` return null; } - + return $this->rolesTitlesCache[$role]; } - + + /** + * initialize the array for caching role and titles. + */ + private function initializeRolesTitlesCache() + { + // break if already initialized + if (null !== $this->rolesTitlesCache) { + return; + } + + foreach ($this->providers as $provider) { + if ($provider instanceof ProvideRoleHierarchyInterface) { + foreach ($provider->getRolesWithHierarchy() as $title => $roles) { + foreach ($roles as $role) { + $this->rolesTitlesCache[$role] = $title; + } + } + } else { + if ($provider->getRoles() !== null) { + $this->rolesTitlesCache = array_merge( + $this->rolesTitlesCache, + array_fill_keys($provider->getRoles(), null) + ); + } + } + } + } } diff --git a/src/Bundle/ChillMainBundle/Security/UserProvider/UserProvider.php b/src/Bundle/ChillMainBundle/Security/UserProvider/UserProvider.php index b741fd2c2..b25c513fd 100644 --- a/src/Bundle/ChillMainBundle/Security/UserProvider/UserProvider.php +++ b/src/Bundle/ChillMainBundle/Security/UserProvider/UserProvider.php @@ -1,83 +1,73 @@ - * - * 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\MainBundle\Security\UserProvider; - -use Symfony\Component\Security\Core\User\UserProviderInterface; -use Symfony\Component\Security\Core\User\UserInterface; -use Doctrine\ORM\EntityManagerInterface; -use Chill\MainBundle\Entity\User; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; -use Symfony\Component\Security\Core\Exception\UnsupportedUserException; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Security\UserProvider; + +use Chill\MainBundle\Entity\User; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\NoResultException; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + class UserProvider implements UserProviderInterface { - /** - * - * @var EntityManagerInterface - */ - protected $em; - + protected EntityManagerInterface $em; + public function __construct(EntityManagerInterface $em) { $this->em = $em; } - public function loadUserByUsername($username): UserInterface { try { $user = $this->em->createQuery(sprintf( - "SELECT u FROM %s u " - . "WHERE u.usernameCanonical = UNACCENT(LOWER(:pattern)) " - . "OR " - . "u.emailCanonical = UNACCENT(LOWER(:pattern))", - User::class)) + 'SELECT u FROM %s u ' + . 'WHERE u.usernameCanonical = UNACCENT(LOWER(:pattern)) ' + . 'OR ' + . 'u.emailCanonical = UNACCENT(LOWER(:pattern))', + User::class + )) ->setParameter('pattern', $username) ->getSingleResult(); - } catch (\Doctrine\ORM\NoResultException $e) { - throw new UsernameNotFoundException(sprintf('Bad credentials.', $username)); + } catch (NoResultException $e) { + throw new UsernameNotFoundException( + 'Bad credentials.', + 0, + $e + ); } - + return $user; } public function refreshUser(UserInterface $user): UserInterface { if (!$user instanceof User) { - throw new UnsupportedUserException("Unsupported user class: cannot reload this user"); + throw new UnsupportedUserException('Unsupported user class: cannot reload this user'); } - + $reloadedUser = $this->em->getRepository(User::class)->find($user->getId()); - - if (NULL === $reloadedUser) { + + if (null === $reloadedUser) { throw new UsernameNotFoundException(sprintf('User with ID "%s" could not be reloaded.', $user->getId())); } - + return $reloadedUser; } public function supportsClass($class): bool { - return $class === User::class; + return User::class === $class; } } diff --git a/src/Bundle/ChillMainBundle/Serializer/Model/Collection.php b/src/Bundle/ChillMainBundle/Serializer/Model/Collection.php index 9983d3595..758ca55b4 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Model/Collection.php +++ b/src/Bundle/ChillMainBundle/Serializer/Model/Collection.php @@ -1,28 +1,37 @@ items = $items; $this->paginator = $paginator; } - public function getPaginator(): PaginatorInterface - { - return $this->paginator; - } - public function getItems() { return $this->items; } + + public function getPaginator(): PaginatorInterface + { + return $this->paginator; + } } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php index ee628e014..fd250e967 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/AddressNormalizer.php @@ -1,50 +1,135 @@ getId(); - $data['text'] = $address->isNoAddress() ? '' : $address->getStreetNumber().', '.$address->getStreet(); - $data['street'] = $address->getStreet(); - $data['streetNumber'] = $address->getStreetNumber(); - $data['postcode']['id'] = $address->getPostCode()->getId(); - $data['postcode']['name'] = $address->getPostCode()->getName(); - $data['postcode']['code'] = $address->getPostCode()->getCode(); - $data['country']['id'] = $address->getPostCode()->getCountry()->getId(); - $data['country']['name'] = $address->getPostCode()->getCountry()->getName(); - $data['country']['code'] = $address->getPostCode()->getCountry()->getCountryCode(); - $data['floor'] = $address->getFloor(); - $data['corridor'] = $address->getCorridor(); - $data['steps'] = $address->getSteps(); - $data['flat'] = $address->getFlat(); - $data['buildingName'] = $address->getBuildingName(); - $data['distribution'] = $address->getDistribution(); - $data['extra'] = $address->getExtra(); - $data['validFrom'] = $address->getValidFrom(); - $data['validTo'] = $address->getValidTo(); - $data['addressReference'] = $this->normalizer->normalize($address->getAddressReference(), $format, [ - AbstractNormalizer::GROUPS => ['read'] - ]); + private const NULL_POSTCODE_COUNTRY = [ + 'id', 'name', 'code', + ]; - return $data; + private const NULL_VALUE = [ + 'address_id', + 'text', + 'street', + 'streetNumber', + 'postcode', + 'country', + 'floor', + 'corridor', + 'steps', + 'flat', + 'buildingName', + 'distribution', + 'extra', + 'validFrom' => DateTimeInterface::class, + 'validTo' => DateTimeInterface::class, + ]; + + /** + * @param Address $address + */ + public function normalize($address, ?string $format = null, array $context = []) + { + if ($address instanceof Address) { + $text = $address->isNoAddress() ? '' : $address->getStreet() . ', ' . $address->getStreetNumber(); + + if (null !== $address->getPostCode()->getCountry()->getCountryCode()) { + if ($address->getPostCode()->getCountry()->getCountryCode() === 'FR') { + $text = $address->isNoAddress() ? '' : $address->getStreetNumber() . ', ' . $address->getStreet(); + } else { + $text = $address->isNoAddress() ? '' : $address->getStreetNumber() . ', ' . $address->getStreet(); + } + } + + $data = [ + 'address_id' => $address->getId(), + 'text' => $text, + 'street' => $address->getStreet(), + 'streetNumber' => $address->getStreetNumber(), + 'postcode' => [ + 'id' => $address->getPostCode()->getId(), + 'name' => $address->getPostCode()->getName(), + 'code' => $address->getPostCode()->getCode(), + ], + 'country' => [ + 'id' => $address->getPostCode()->getCountry()->getId(), + 'name' => $address->getPostCode()->getCountry()->getName(), + 'code' => $address->getPostCode()->getCountry()->getCountryCode(), + ], + 'floor' => $address->getFloor(), + 'corridor' => $address->getCorridor(), + 'steps' => $address->getSteps(), + 'flat' => $address->getFlat(), + 'buildingName' => $address->getBuildingName(), + 'distribution' => $address->getDistribution(), + 'extra' => $address->getExtra(), + ]; + + if ('json' === $format) { + $data['addressReference'] = $this->normalizer->normalize( + $address->getAddressReference(), + $format, + [AbstractNormalizer::GROUPS => ['read']] + ); + $data['validFrom'] = $address->getValidFrom(); + $data['validTo'] = $address->getValidTo(); + } elseif ('docgen' === $format) { + $dateContext = array_merge($context, ['docgen:expects' => DateTimeInterface::class]); + $data['validFrom'] = $this->normalizer->normalize($address->getValidFrom(), $format, $dateContext); + $data['validTo'] = $this->normalizer->normalize($address->getValidTo(), $format, $dateContext); + } + + return $data; + } + + if (null === $address) { + $helper = new NormalizeNullValueHelper($this->normalizer); + + return array_merge( + $helper->normalize(self::NULL_VALUE, $format, $context), + [ + 'postcode' => $helper->normalize(self::NULL_POSTCODE_COUNTRY, $format, $context), + 'country' => $helper->normalize(self::NULL_POSTCODE_COUNTRY, $format, $context), + ] + ); + } + + throw new UnexpectedValueException(); } - public function supportsNormalization($data, string $format = null) + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { - return $data instanceof Address; + if ('json' === $format) { + return $data instanceof Address; + } + + if ('docgen' === $format) { + return + $data instanceof Address + || (null === $data && Address::class === ($context['docgen:expects'] ?? null)); + } + + return false; } - - } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php index f1681c60a..cbe712ee8 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php @@ -1,69 +1,44 @@ - * - * 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 . + +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Serializer\Normalizer; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Repository\CenterRepository; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use function array_key_exists; -/** - * - * - */ -class CenterNormalizer implements NormalizerInterface, DenormalizerInterface +class CenterNormalizer implements DenormalizerInterface, NormalizerInterface { private CenterRepository $repository; - public function __construct(CenterRepository $repository) { $this->repository = $repository; } - public function normalize($center, string $format = null, array $context = array()) + public function denormalize($data, string $type, ?string $format = null, array $context = []) { - /** @var Center $center */ - return [ - 'id' => $center->getId(), - 'type' => 'center', - 'name' => $center->getName() - ]; - } - - public function supportsNormalization($data, string $format = null): bool - { - return $data instanceof Center; - } - - public function denormalize($data, string $type, string $format = null, array $context = []) - { - if (FALSE === \array_key_exists('type', $data)) { + if (false === array_key_exists('type', $data)) { throw new InvalidArgumentException('missing "type" key in data'); } + if ('center' !== $data['type']) { throw new InvalidArgumentException('type should be equal to "center"'); } - if (FALSE === \array_key_exists('id', $data)) { + + if (false === array_key_exists('id', $data)) { throw new InvalidArgumentException('missing "id" key in data'); } @@ -76,8 +51,23 @@ class CenterNormalizer implements NormalizerInterface, DenormalizerInterface return $center; } - public function supportsDenormalization($data, string $type, string $format = null) + public function normalize($center, ?string $format = null, array $context = []) { - return $type === Center::class; + /** @var Center $center */ + return [ + 'id' => $center->getId(), + 'type' => 'center', + 'name' => $center->getName(), + ]; + } + + public function supportsDenormalization($data, string $type, ?string $format = null) + { + return Center::class === $type; + } + + public function supportsNormalization($data, ?string $format = null): bool + { + return $data instanceof Center && 'json' === $format; } } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php index d3f513c54..0c80e0439 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php @@ -1,40 +1,47 @@ getPaginator(); + + return [ + 'count' => $paginator->getTotalItems(), + 'pagination' => [ + 'first' => $paginator->getCurrentPageFirstItemNumber(), + 'items_per_page' => $paginator->getItemsPerPage(), + 'next' => $paginator->hasNextPage() ? $paginator->getNextPage()->generateUrl() : null, + 'previous' => $paginator->hasPreviousPage() ? $paginator->getPreviousPage()->generateUrl() : null, + 'more' => $paginator->hasNextPage(), + ], + 'results' => $this->normalizer->normalize($collection->getItems(), $format, $context), + ]; + } + + public function supportsNormalization($data, ?string $format = null): bool { return $data instanceof Collection; } - - public function normalize($collection, string $format = null, array $context = []) - { - /** @var $collection Collection */ - $paginator = $collection->getPaginator(); - - $data['count'] = $paginator->getTotalItems(); - $pagination['first'] = $paginator->getCurrentPageFirstItemNumber(); - $pagination['items_per_page'] = $paginator->getItemsPerPage(); - $pagination['next'] = $paginator->hasNextPage() ? - $paginator->getNextPage()->generateUrl() : null; - $pagination['previous'] = $paginator->hasPreviousPage() ? - $paginator->getPreviousPage()->generateUrl() : null; - $pagination['more'] = $paginator->hasNextPage(); - $data['pagination'] = $pagination; - - // normalize results - $data['results'] = $this->normalizer->normalize($collection->getItems(), - $format, $context); - - return $data; - } } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php index 8e333a2a4..3050ad268 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php @@ -1,60 +1,115 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Serializer\Normalizer; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use IntlDateFormatter; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use function array_key_exists; +use function is_array; -class DateNormalizer implements NormalizerInterface, DenormalizerInterface +class DateNormalizer implements ContextAwareNormalizerInterface, DenormalizerInterface { - public function normalize($date, string $format = null, array $context = array()) + private ParameterBagInterface $parameterBag; + + private RequestStack $requestStack; + + public function __construct(RequestStack $requestStack, ParameterBagInterface $parameterBag) { - /** @var \DateTimeInterface $date */ - return [ - 'datetime' => $date->format(\DateTimeInterface::ISO8601) - ]; + $this->requestStack = $requestStack; + $this->parameterBag = $parameterBag; } - public function supportsNormalization($data, string $format = null): bool - { - return $data instanceof \DateTimeInterface; - } - - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize($data, string $type, ?string $format = null, array $context = []) { switch ($type) { - case \DateTime::class: - return \DateTime::createFromFormat(\DateTimeInterface::ISO8601, $data['datetime']); - case \DateTimeInterface::class: - case \DateTimeImmutable::class: + case DateTime::class: + return DateTime::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); + + case DateTimeInterface::class: + case DateTimeImmutable::class: default: - return \DateTimeImmutable::createFromFormat(\DateTimeInterface::ISO8601, $data['datetime']); + return DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); } } - public function supportsDenormalization($data, string $type, string $format = null): bool + public function normalize($date, ?string $format = null, array $context = []) { - return $type === \DateTimeInterface::class || - $type === \DateTime::class || - $type === \DateTimeImmutable::class || - (\is_array($data) && array_key_exists('datetime', $data)); + /** @var DateTimeInterface $date */ + switch ($format) { + case 'json': + return [ + 'datetime' => $date->format(DateTimeInterface::ISO8601), + ]; + + case 'docgen': + if (null === $date) { + return [ + 'long' => '', 'short' => '', + ]; + } + + $hasTime = $date->format('His') !== '000000'; + $request = $this->requestStack->getCurrentRequest(); + $locale = null !== $request ? $request->getLocale() : $this->parameterBag->get('kernel.default_locale'); + $formatterLong = IntlDateFormatter::create( + $locale, + IntlDateFormatter::LONG, + $hasTime ? IntlDateFormatter::SHORT : IntlDateFormatter::NONE + ); + $formatterShort = IntlDateFormatter::create( + $locale, + IntlDateFormatter::SHORT, + $hasTime ? IntlDateFormatter::SHORT : IntlDateFormatter::NONE + ); + + return [ + 'short' => $formatterShort->format($date), + 'long' => $formatterLong->format($date), + ]; + } + } + + public function supportsDenormalization($data, string $type, ?string $format = null): bool + { + return DateTimeInterface::class === $type + || DateTime::class === $type + || DateTimeImmutable::class === $type + || (is_array($data) && array_key_exists('datetime', $data)); + } + + public function supportsNormalization($data, ?string $format = null, array $context = []): bool + { + if ('json' === $format) { + return $data instanceof DateTimeInterface; + } + + if ('docgen' === $format) { + return $data instanceof DateTimeInterface || ( + null === $data + && array_key_exists('docgen:expects', $context) + && ( + DateTimeInterface::class === $context['docgen:expects'] + || DateTime::class === $context['docgen:expects'] + || DateTimeImmutable::class === $context['docgen:expects'] + ) + ); + } + + return false; } } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php index 25b2c5017..f31b5d60d 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DiscriminatedObjectDenormalizer.php @@ -1,62 +1,67 @@ denormalizer->supportsDenormalization($data, $localType, $format)) { try { - return $this->denormalizer->denormalize($data, $localType, $format, $context); } catch (RuntimeException $e) { + return $this->denormalizer->denormalize($data, $localType, $format, $context); + } catch (RuntimeException $e) { $lastException = $e; } } } - throw new RuntimeException(sprintf("Could not find any denormalizer for those ". - "ALLOWED_TYPES: %s", \implode(", ", $context[self::ALLOWED_TYPES]))); + throw new RuntimeException(sprintf('Could not find any denormalizer for those ' . + 'ALLOWED_TYPES: %s', implode(', ', $context[self::ALLOWED_TYPES]))); } - /** - * {@inheritDoc} - */ - public function supportsDenormalization($data, string $type, string $format = null, array $context = []) + public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []) { if (self::TYPE !== $type) { return false; } if (0 === count($context[self::ALLOWED_TYPES] ?? [])) { - throw new \LogicException("The context should contains a list of - allowed types"); + throw new LogicException('The context should contains a list of + allowed types'); } foreach ($context[self::ALLOWED_TYPES] as $localType) { @@ -67,5 +72,4 @@ class DiscriminatedObjectDenormalizer implements ContextAwareDenormalizerInterfa return false; } - } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php index cb2635802..aa9d12e24 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DoctrineExistingEntityNormalizer.php @@ -1,14 +1,24 @@ em = $em; $this->serializerMetadataFactory = $serializerMetadataFactory; } - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize($data, string $type, ?string $format = null, array $context = []) { - if (\array_key_exists(AbstractNormalizer::OBJECT_TO_POPULATE, $context)) { + if (array_key_exists(AbstractNormalizer::OBJECT_TO_POPULATE, $context)) { return $context[AbstractNormalizer::OBJECT_TO_POPULATE]; } @@ -33,23 +42,22 @@ class DoctrineExistingEntityNormalizer implements DenormalizerInterface ->find($data['id']); } - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization($data, string $type, ?string $format = null) { - if (FALSE === \is_array($data)) { + if (false === is_array($data)) { return false; } - if (FALSE === \array_key_exists('id', $data)) { + if (false === array_key_exists('id', $data)) { return false; - } + } - if (FALSE === $this->em->getClassMetadata($type) instanceof ClassMetadata) { - return false; + if (false === $this->em->getClassMetadata($type) instanceof ClassMetadata) { + return false; } // does have serializer metadata, and class discriminator ? if ($this->serializerMetadataFactory->hasMetadataFor($type)) { - $classDiscriminator = $this->serializerMetadataFactory ->getMetadataFor($type)->getClassDiscriminatorMapping(); @@ -58,14 +66,14 @@ class DoctrineExistingEntityNormalizer implements DenormalizerInterface // check that only 2 keys // that the second key is property - // and that the type match the class for given type property + // and that the type match the class for given type property return count($data) === 2 - && \array_key_exists($typeProperty, $data) - && $type === $classDiscriminator->getClassForType($data[$typeProperty]); + && array_key_exists($typeProperty, $data) + && $classDiscriminator->getClassForType($data[$typeProperty]) === $type; } } // we do not have any class discriminator. Check that the id is the only one key return count($data) === 1; } -} +} diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php index 210e85fb7..c8c532802 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php @@ -1,34 +1,40 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Serializer\Normalizer; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Templating\Entity\UserRender; +use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -class UserNormalizer implements NormalizerInterface, NormalizerAwareInterface +class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; + + public const NULL_USER = [ + 'type' => 'user', + 'id' => '', + 'username' => '', + 'text' => '', + 'label' => '', + 'email' => '', + ]; + private UserRender $userRender; public function __construct(UserRender $userRender) @@ -35,23 +40,53 @@ class UserNormalizer implements NormalizerInterface, NormalizerAwareInterface $this->userRender = $userRender; } - public function normalize($user, string $format = null, array $context = array()) + public function normalize($user, ?string $format = null, array $context = []) { /** @var User $user */ + $userJobContext = array_merge( + $context, + ['docgen:expects' => UserJob::class, 'groups' => 'docgen:read'] + ); + $scopeContext = array_merge( + $context, + ['docgen:expects' => Scope::class, 'groups' => 'docgen:read'] + ); + $centerContext = array_merge( + $context, + ['docgen:expects' => Center::class, 'groups' => 'docgen:read'] + ); + + if (null === $user && 'docgen' === $format) { + return array_merge(self::NULL_USER, [ + 'user_job' => $this->normalizer->normalize(null, $format, $userJobContext), + 'main_center' => $this->normalizer->normalize(null, $format, $centerContext), + 'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext), + ]); + } + return [ 'type' => 'user', 'id' => $user->getId(), 'username' => $user->getUsername(), 'text' => $this->userRender->renderString($user, []), 'label' => $user->getLabel(), - 'user_job' => $this->normalizer->normalize($user->getUserJob(), $format, $context), - 'main_center' => $this->normalizer->normalize($user->getMainCenter(), $format, $context), - 'main_scope' => $this->normalizer->normalize($user->getMainScope(), $format, $context), + 'email' => (string) $user->getEmail(), + 'user_job' => $this->normalizer->normalize($user->getUserJob(), $format, $userJobContext), + 'main_center' => $this->normalizer->normalize($user->getMainCenter(), $format, $centerContext), + 'main_scope' => $this->normalizer->normalize($user->getMainScope(), $format, $scopeContext), ]; } - public function supportsNormalization($data, string $format = null): bool + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { - return $data instanceof User; + if ($data instanceof User && ('json' === $format || 'docgen' === $format)) { + return true; + } + + if (null === $data && 'docgen' === $format && User::class === ($context['docgen:expects'] ?? null)) { + return true; + } + + return false; } } diff --git a/src/Bundle/ChillMainBundle/Templating/CSVCellTwig.php b/src/Bundle/ChillMainBundle/Templating/CSVCellTwig.php index f587b41fe..6d3075d6f 100644 --- a/src/Bundle/ChillMainBundle/Templating/CSVCellTwig.php +++ b/src/Bundle/ChillMainBundle/Templating/CSVCellTwig.php @@ -1,25 +1,14 @@ , - * - * 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 . + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Templating; use Twig\Extension\AbstractExtension; @@ -28,39 +17,42 @@ use Twig\TwigFilter; /** * Twig filter to transform a string in a safer way to be the content of a csv * cell. - * + * * This filter replace the char " by "" */ class CSVCellTwig extends AbstractExtension { - /* - * Returns a list of filters to add to the existing list. - * - * (non-PHPdoc) - * @see Twig_Extension::getFilters() - */ - public function getFilters() - { - return array( - new TwigFilter( - 'csv_cell', - array($this, 'csvCellFilter'), - array('is_safe' => array('html'))) - ); - } - - /* - * Replace into a string the char " by "" - * - * @param String $content The input string. - * @return String The safe string. + /** + * Replace into a string the char " by "". + * + * @param string $content The input string. + * + * @return string The safe string. */ public function csvCellFilter($content) { return str_replace('"', '""', $content); } - - /* + + /** + * Returns a list of filters to add to the existing list. + * + * (non-PHPdoc) + * + * @see Twig_Extension::getFilters() + */ + public function getFilters() + { + return [ + new TwigFilter( + 'csv_cell', + [$this, 'csvCellFilter'], + ['is_safe' => ['html']] + ), + ]; + } + + /** * Returns the name of the extension. * * @return The name of the extension. diff --git a/src/Bundle/ChillMainBundle/Templating/ChillMarkdownRenderExtension.php b/src/Bundle/ChillMainBundle/Templating/ChillMarkdownRenderExtension.php index 29d189155..55212c64f 100644 --- a/src/Bundle/ChillMainBundle/Templating/ChillMarkdownRenderExtension.php +++ b/src/Bundle/ChillMainBundle/Templating/ChillMarkdownRenderExtension.php @@ -1,29 +1,23 @@ - * - * 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\MainBundle\Templating; - -use Twig\Extension\AbstractExtension; -use Twig\TwigFilter; - -use Parsedown; /** - * Render markdown + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Templating; + +use Parsedown; +use Twig\Extension\AbstractExtension; + +use Twig\TwigFilter; + +/** + * Render markdown. */ final class ChillMarkdownRenderExtension extends AbstractExtension { @@ -42,8 +36,8 @@ final class ChillMarkdownRenderExtension extends AbstractExtension { return [ new TwigFilter('chill_markdown_to_html', [$this, 'renderMarkdownToHtml'], [ - 'is_safe' => [ 'html' ] - ]) + 'is_safe' => ['html'], + ]), ]; } diff --git a/src/Bundle/ChillMainBundle/Templating/ChillTwigHelper.php b/src/Bundle/ChillMainBundle/Templating/ChillTwigHelper.php index b6b267386..4bc0cffd4 100644 --- a/src/Bundle/ChillMainBundle/Templating/ChillTwigHelper.php +++ b/src/Bundle/ChillMainBundle/Templating/ChillTwigHelper.php @@ -1,32 +1,22 @@ - * - * 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\MainBundle\Templating; - -use Twig\Extension\AbstractExtension; -use Twig\TwigFilter; -use Twig\Environment; /** - * + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Templating; + +use DateTimeInterface; +use Twig\Environment; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; +use function array_merge; + class ChillTwigHelper extends AbstractExtension { public function getFilters() @@ -34,51 +24,53 @@ class ChillTwigHelper extends AbstractExtension return [ new TwigFilter('chill_print_or_message', [$this, 'printOrMessage'], [ 'needs_environment' => true, - 'is_safe' => ['html', 'html_attrs'] + 'is_safe' => ['html', 'html_attrs'], ]), ]; } - + /** - * Print `value` inside a template, or, if $value is empty, + * Print `value` inside a template, or, if $value is empty, * print $message. - * + * * The template can be customized. The template is a full path to another - * template, or one of the key belows: - * + * template, or one of the key belows: + * * - 'default' ; * - 'blockquote' ; - * + * * `DateTimeInterface are also rendered. The date and time format may be set * using those key in `$options´ parameter: - * + * * - `date_format` (default to `'medium'`) * - `time_format` (default to `'none'`) - * - * @param Environment $twig + * * @param string $value Default to 'No value'. Fallback to default if null * @param string $message * @param string $template - * @param array $options + * * @return string */ public function printOrMessage( Environment $twig, - $value, - $message = 'No value', + $value, + $message = 'No value', $template = 'default', array $options = [] ) { - if ($value instanceof \DateTimeInterface) { - $options = \array_merge([ + if ($value instanceof DateTimeInterface) { + $options = array_merge([ 'date_format' => 'medium', - 'time_format' => 'none' + 'time_format' => 'none', ], $options); + switch ($template) { case 'default': case 'blockquote': - $t = '@ChillMain/Extensions/PrintOrMessage/'.$template.'_date.html.twig'; + $t = '@ChillMain/Extensions/PrintOrMessage/' . $template . '_date.html.twig'; + break; + default: $t = $template; } @@ -86,16 +78,18 @@ class ChillTwigHelper extends AbstractExtension switch ($template) { case 'default': case 'blockquote': - $t = '@ChillMain/Extensions/PrintOrMessage/'.$template.'.html.twig'; + $t = '@ChillMain/Extensions/PrintOrMessage/' . $template . '.html.twig'; + break; + default: $t = $template; } } - - return $twig->render($t, \array_merge([ + + return $twig->render($t, array_merge([ 'value' => $value, - 'message' => $message ?? 'No value' + 'message' => $message ?? 'No value', ], $options)); } } diff --git a/src/Bundle/ChillMainBundle/Templating/ChillTwigRoutingHelper.php b/src/Bundle/ChillMainBundle/Templating/ChillTwigRoutingHelper.php index c3f0f0688..478920f05 100644 --- a/src/Bundle/ChillMainBundle/Templating/ChillTwigRoutingHelper.php +++ b/src/Bundle/ChillMainBundle/Templating/ChillTwigRoutingHelper.php @@ -1,152 +1,141 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Templating; +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Component\HttpFoundation\RequestStack; use Twig\Extension\AbstractExtension; use Twig\Node\Node; -use Twig\TwigFunction; use Twig\TwigFilter; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Twig\TwigFunction; /** * Provides function to build path with returnPath. - * - * The logic of the function is based on the original routing extension. * + * The logic of the function is based on the original routing extension. */ class ChillTwigRoutingHelper extends AbstractExtension { /** - * - * @var RequestStack - */ - protected $requestStack; - - /** - * * @var RoutingExtension */ protected $originalExtension; - + + /** + * @var RequestStack + */ + protected $requestStack; + public function __construct( - RequestStack $requestStack, + RequestStack $requestStack, RoutingExtension $originalExtension ) { $this->requestStack = $requestStack; $this->originalExtension = $originalExtension; } - - public function getFunctions() - { - return [ - new TwigFunction('chill_return_path_or', [$this, 'getReturnPathOr'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']] ), - new TwigFunction('chill_path_add_return_path', [$this, 'getPathAddReturnPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']] ), - new TwigFunction('chill_path_forward_return_path', [$this, 'getPathForwardReturnPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']] ), - - ]; - } - public function getFilters() { return [ new TwigFilter('chill_return_path_label', [$this, 'getLabelReturnPath']), ]; } - - public function isUrlGenerationSafe(Node $argsNode) + + public function getFunctions() { - return $this->originalExtension->isUrlGenerationSafe($argsNode); + return [ + new TwigFunction('chill_return_path_or', [$this, 'getReturnPathOr'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('chill_path_add_return_path', [$this, 'getPathAddReturnPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('chill_path_forward_return_path', [$this, 'getPathForwardReturnPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + ]; } - + public function getLabelReturnPath($default) { $request = $this->requestStack->getCurrentRequest(); - + return $request->query->get('returnPathLabel', null) ?? $default; } - + /** - * Return the return path if it exists, or generate the path if not. - * - * @param string $name - * @param array $parameters - * @param bool $relative - * @return string - */ - public function getReturnPathOr($name, $parameters = [], $relative = false) - { - $request = $this->requestStack->getCurrentRequest(); - - if ($request->query->has('returnPath')) { - return $request->query->get('returnPath'); - } - - return $this->originalExtension->getPath($name, $parameters, $relative); - } - - /** - * Build an url with a returnPath parameter to current page - * + * Build an url with a returnPath parameter to current page. + * * @param string $name * @param array $parameters * @param bool $relative + * @param mixed|null $label + * * @return string */ public function getPathAddReturnPath($name, $parameters = [], $relative = false, $label = null) { $request = $this->requestStack->getCurrentRequest(); - + $parameters['returnPath'] = $request->getRequestUri(); - + if ($label) { $parameters['returnPathLabel'] = $label; } - + return $this->originalExtension->getPath($name, $parameters, $relative); } - + /** - * Build an url with a returnPath parameter to current page - * + * Build an url with a returnPath parameter to current page. + * * @param string $name * @param array $parameters * @param bool $relative + * * @return string */ public function getPathForwardReturnPath($name, $parameters = [], $relative = false) { $request = $this->requestStack->getCurrentRequest(); - + if ($request->query->has('returnPath')) { $parameters['returnPath'] = $request->query->get('returnPath'); } - + return $this->originalExtension ->getPath( - $name, + $name, $parameters, $relative ); } + /** + * Return the return path if it exists, or generate the path if not. + * + * @param string $name + * @param array $parameters + * @param bool $relative + * + * @return string + */ + public function getReturnPathOr($name, $parameters = [], $relative = false) + { + $request = $this->requestStack->getCurrentRequest(); + + if ($request->query->has('returnPath')) { + return $request->query->get('returnPath'); + } + + return $this->originalExtension->getPath($name, $parameters, $relative); + } + + public function isUrlGenerationSafe(Node $argsNode) + { + return $this->originalExtension->isUrlGenerationSafe($argsNode); + } } diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/AbstractChillEntityRender.php b/src/Bundle/ChillMainBundle/Templating/Entity/AbstractChillEntityRender.php index c9b06a883..d82b2885a 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/AbstractChillEntityRender.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/AbstractChillEntityRender.php @@ -1,38 +1,25 @@ - * - * 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\MainBundle\Templating\Entity; /** - * + * Chill is a software for social workers * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Templating\Entity; + abstract class AbstractChillEntityRender implements ChillEntityRenderInterface { - protected function getDefaultOpeningBox($classSuffix): string - { - return '
    '; - } - protected function getDefaultClosingBox(): string { return '
    '; } + + protected function getDefaultOpeningBox($classSuffix): string + { + return '
    '; + } } diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/AddressRender.php b/src/Bundle/ChillMainBundle/Templating/Entity/AddressRender.php index 788fd43c1..264f11688 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/AddressRender.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/AddressRender.php @@ -1,14 +1,23 @@ false, 'with_valid_to' => false, @@ -16,57 +25,84 @@ class AddressRender implements ChillEntityRenderInterface 'with_delimiter' => false, 'has_no_address' => false, 'multiline' => true, - 'extended_infos' => false + 'extended_infos' => false, ]; + private EngineInterface $templating; + public function __construct(EngineInterface $templating) { $this->templating = $templating; } /** - * {@inheritDoc} + * @param Address addr + * @param mixed $addr */ + public function renderBox($addr, array $options): string + { + $options = array_merge(self::DEFAULT_OPTIONS, $options); + + return $this->templating + ->render('@ChillMain/Entity/address.html.twig', [ + 'address' => $addr, + 'streetLine' => $this->renderStreetLine($addr), + 'render' => $options['render'] ?? 'bloc', + 'options' => $options, + ]); + } + + /** + * @param Address addr + * @param mixed $addr + */ + public function renderString($addr, array $options): string + { + $lines = []; + + $lines[0] = $this->renderStreetLine($addr); + + if (!empty($addr->getPostcode())) { + $lines[1] = strtr('{postcode} {label}', [ + '{postcode}' => $addr->getPostcode()->getCode(), + '{label}' => $addr->getPostcode()->getName(), + ]); + } + + return implode(' - ', $lines); + } + public function supports($entity, array $options): bool { return $entity instanceof Address; } - /** - * @param Address addr - */ - public function renderString($addr, array $options): string + private function renderStreetLine($addr): string { - $lines = []; if (!empty($addr->getStreet())) { - $lines[0] = $addr->getStreet(); + $street = $addr->getStreet(); + } else { + $street = ''; } + if (!empty($addr->getStreetNumber())) { - $lines[0] .= ", ".$addr->getStreetNumber(); - } - if (!empty($addr->getPostcode())) { - $lines[1] = \strtr("{postcode} {label}", [ - '{postcode}' => $addr->getPostcode()->getCode(), - '{label}' => $addr->getPostcode()->getName() - ]); + $streetNumber = $addr->getStreetNumber(); + } else { + $streetNumber = ''; } - return implode(" - ", $lines); - } + $res = trim($street . ', ' . $streetNumber, ', '); - /** - * {@inheritDoc} - * @param Address addr - */ - public function renderBox($addr, array $options): string - { - $options = \array_merge(self::DEFAULT_OPTIONS, $options); + if (null !== $addr->getPostCode()->getCountry()->getCountryCode()) { + if ($addr->getPostCode()->getCountry()->getCountryCode() === 'FR') { + $res = trim($streetNumber . ', ' . $street, ', '); + } + } - return $this->templating - ->render('@ChillMain/Entity/address.html.twig', [ - 'address' => $addr, - 'render' => $options['render'] ?? 'bloc', - 'options' => $options - ]); + if (',' === $res) { + $res = ''; + } + + return $res; } } diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRender.php b/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRender.php index 8f8f58263..87a15955a 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRender.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRender.php @@ -1,34 +1,25 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Templating\Entity; /** - * Render an entity using `__toString()` + * Render an entity using `__toString()`. */ class ChillEntityRender extends AbstractChillEntityRender { public function renderBox($entity, array $options): string { - return $this->getDefaultOpeningBox('default').$entity - .$this->getDefaultClosingBox(); + return $this->getDefaultOpeningBox('default') . $entity + . $this->getDefaultClosingBox(); } public function renderString($entity, array $options): string diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRenderExtension.php b/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRenderExtension.php index 32aa12d85..e1eae3884 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRenderExtension.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRenderExtension.php @@ -1,45 +1,34 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Templating\Entity; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; /** - * Class ChillEntityRenderExtension - * - * @package Chill\MainBundle\Templating\Entity + * Class ChillEntityRenderExtension. */ class ChillEntityRenderExtension extends AbstractExtension { - /** - * @var ChillEntityRenderInterface - */ - protected $renders = []; - /** * @var ChillEntityRender */ protected $defaultRender; - + + /** + * @var ChillEntityRenderInterface + */ + protected $renders = []; + /** * ChillEntityRenderExtension constructor. */ @@ -47,7 +36,12 @@ class ChillEntityRenderExtension extends AbstractExtension { $this->defaultRender = new ChillEntityRender(); } - + + public function addRender(ChillEntityRenderInterface $render) + { + $this->renders[] = $render; + } + /** * @return array|TwigFilter[] */ @@ -55,54 +49,43 @@ class ChillEntityRenderExtension extends AbstractExtension { return [ new TwigFilter('chill_entity_render_string', [$this, 'renderString'], [ - 'is_safe' => [ 'html' ] + 'is_safe' => ['html'], ]), new TwigFilter('chill_entity_render_box', [$this, 'renderBox'], [ - 'is_safe' => [ 'html' ] - ]) + 'is_safe' => ['html'], + ]), ]; } - + /** * @param $entity - * @param array $options - * @return string - */ - public function renderString($entity, array $options = []): string - { - if (NULL === $entity) { - return ''; - } - return $this->getRender($entity, $options) - ->renderString($entity, $options); - } - - /** - * @param $entity - * @param array $options - * @return string */ public function renderBox($entity, array $options = []): string { - if (NULL === $entity) { + if (null === $entity) { return ''; } + return $this->getRender($entity, $options) ->renderBox($entity, $options); } - + /** - * @param ChillEntityRenderInterface $render + * @param $entity */ - public function addRender(ChillEntityRenderInterface $render) + public function renderString($entity, array $options = []): string { - $this->renders[] = $render; + if (null === $entity) { + return ''; + } + + return $this->getRender($entity, $options) + ->renderString($entity, $options); } - + /** * @param $entity * @param $options - * @return ChillEntityRenderInterface|null */ protected function getRender($entity, $options): ?ChillEntityRenderInterface { @@ -111,6 +94,7 @@ class ChillEntityRenderExtension extends AbstractExtension return $render; } } + return $this->defaultRender; } } diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRenderInterface.php b/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRenderInterface.php index d43ac3e52..d55b8bfd7 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRenderInterface.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/ChillEntityRenderInterface.php @@ -1,23 +1,14 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Templating\Entity; /** @@ -27,40 +18,34 @@ namespace Chill\MainBundle\Templating\Entity; interface ChillEntityRenderInterface { /** - * Return true if the class support this object for the given options - * - * @param type $entity - * @param array $options - * @return bool - */ - public function supports($entity, array $options): bool; - - /** - * Return the entity as a string. - * - * Example: returning the name of a person. - * - * @param object $entity - * @param array $options - * @return string - */ - public function renderString($entity, array $options): string; - - /** - * Return the entity in a box - * + * Return the entity in a box. + * * Example: return a person inside a box: - * + * * ```html * * Roger * Dupont * * ``` - * + * * @param type $entity - * @param array $options - * @return string */ public function renderBox($entity, array $options): string; + + /** + * Return the entity as a string. + * + * Example: returning the name of a person. + * + * @param object $entity + */ + public function renderString($entity, array $options): string; + + /** + * Return true if the class support this object for the given options. + * + * @param type $entity + */ + public function supports($entity, array $options): bool; } diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/CommentRender.php b/src/Bundle/ChillMainBundle/Templating/Entity/CommentRender.php index 9ff0ca90c..a6285d26b 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/CommentRender.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/CommentRender.php @@ -1,43 +1,33 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Templating\Entity; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Repository\UserRepository; -use Chill\MainBundle\Templating\Entity\AbstractChillEntityRender; use Symfony\Component\Templating\EngineInterface; +use function array_merge; class CommentRender extends AbstractChillEntityRender { /** - * @var \Chill\MainBundle\Repository\UserRepository - */ - private $userRepository; - - /** - * * @var EngineInterface */ private $engine; + /** + * @var \Chill\MainBundle\Repository\UserRepository + */ + private $userRepository; + public function __construct( UserRepository $userRepository, EngineInterface $engine @@ -48,42 +38,36 @@ class CommentRender extends AbstractChillEntityRender /** * @param CommentEmbeddable $entity - * @param array $options - * - * @return string */ public function renderBox($entity, array $options): string { // default options - $options = \array_merge([ - 'user' => [], - 'disable_markdown' => false, - 'limit_lines' => null, - 'metadata' => true - ], $options); - + $options = array_merge([ + 'user' => [], + 'disable_markdown' => false, + 'limit_lines' => null, + 'metadata' => true, + ], $options); + if ($entity->getUserId()) { $user = $this->userRepository->find($entity->getUserId()); } - + return $this->engine ->render( '@ChillMain/Entity/CommentEmbeddable.html.twig', [ 'opening_box' => $this->getDefaultOpeningBox('comment-embeddable'), 'closing_box' => $this->getDefaultClosingBox(), - 'user' => $user ?? NULL, + 'user' => $user ?? null, 'comment' => $entity, - 'options' => $options + 'options' => $options, ] ); } /** * @param CommentEmbeddable $entity - * @param array $options - * - * @return string */ public function renderString($entity, array $options): string { diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/CompilerPass.php b/src/Bundle/ChillMainBundle/Templating/Entity/CompilerPass.php index 7c25d2423..38f6284ca 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/CompilerPass.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/CompilerPass.php @@ -1,39 +1,29 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Templating\Entity; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; /** - * Add service tagged with `chill.render_entity` to appropriate service - * + * Add service tagged with `chill.render_entity` to appropriate service. */ class CompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $extension = $container->getDefinition(ChillEntityRenderExtension::class); - + foreach ($container->findTaggedServiceIds('chill.render_entity') as $id => $tags) { $extension->addMethodCall('addRender', [new Reference($id)]); } diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php b/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php index 121b94ead..9247b8ffb 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php @@ -1,20 +1,31 @@ true, + 'user_job' => true, + ]; + private EngineInterface $engine; - const DEFAULT_OPTIONS = [ - 'main_scope' => true, - 'user_job' => true - ]; + private TranslatableStringHelper $translatableStringHelper; public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine) { @@ -22,45 +33,40 @@ class UserRender implements ChillEntityRenderInterface $this->engine = $engine; } - /** - * @inheritDoc - */ - public function supports($entity, array $options): bool + public function renderBox($entity, array $options): string { - return $entity instanceof User; + $opts = array_merge(self::DEFAULT_OPTIONS, $options); + + return $this->engine->render('@ChillMain/Entity/user.html.twig', [ + 'user' => $entity, + 'opts' => $opts, + ]); } /** - * @inheritDoc * @param User $entity */ public function renderString($entity, array $options): string { - $opts = \array_merge(self::DEFAULT_OPTIONS, $options); + $opts = array_merge(self::DEFAULT_OPTIONS, $options); $str = $entity->getLabel(); - if (NULL !== $entity->getUserJob() && $opts['user_job']) { - $str .= ' ('.$this->translatableStringHelper - ->localize($entity->getUserJob()->getLabel()).')'; + + if (null !== $entity->getUserJob() && $opts['user_job']) { + $str .= ' (' . $this->translatableStringHelper + ->localize($entity->getUserJob()->getLabel()) . ')'; } - if (NULL !== $entity->getMainScope() && $opts['main_scope']) { - $str .= ' ('.$this->translatableStringHelper - ->localize($entity->getMainScope()->getName()).')'; + + if (null !== $entity->getMainScope() && $opts['main_scope']) { + $str .= ' (' . $this->translatableStringHelper + ->localize($entity->getMainScope()->getName()) . ')'; } return $str; } - /** - * @inheritDoc - */ - public function renderBox($entity, array $options): string + public function supports($entity, array $options): bool { - $opts = \array_merge(self::DEFAULT_OPTIONS, $options); - - return $this->engine->render('@ChillMain/Entity/user.html.twig', [ - 'user' => $entity, - 'opts' => $opts - ]); + return $entity instanceof User; } } diff --git a/src/Bundle/ChillMainBundle/Templating/Events/DelegatedBlockRenderingEvent.php b/src/Bundle/ChillMainBundle/Templating/Events/DelegatedBlockRenderingEvent.php index 6e554d1be..965c80fda 100644 --- a/src/Bundle/ChillMainBundle/Templating/Events/DelegatedBlockRenderingEvent.php +++ b/src/Bundle/ChillMainBundle/Templating/Events/DelegatedBlockRenderingEvent.php @@ -1,85 +1,75 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Templating\Events; +use ArrayAccess; +use RuntimeException; use Symfony\Component\EventDispatcher\Event; /** - * This event is transmitted on event chill_block.* - * - * You may access to the context as an array : - * + * This event is transmitted on event chill_block.*. + * + * You may access to the context as an array : + * * ``` * $var = $event['context_key'] * ``` - * - * The documentation for the bundle where the event is launched should give + * + * The documentation for the bundle where the event is launched should give * you the context keys. - * + * * The keys are read-only: if you try to update the context using array access * (example, using `$event['context_key'] = $bar;`, an error will be thrown. - * - * - * @author Julien Fastré */ -class DelegatedBlockRenderingEvent extends Event implements \ArrayAccess +class DelegatedBlockRenderingEvent extends Event implements ArrayAccess { /** - * - * @var mixed[] - */ - protected $context; - - /** - * The returned content of the event + * The returned content of the event. * * @var string */ protected $content = ''; - + + /** + * @var mixed[] + */ + protected $context; + public function __construct(array $context) { $this->context = $context; } - + /** * add content to the event. This content will be printed in the - * layout which launched the event - * + * layout which launched the event. + * * @param string $text */ public function addContent($text) { $this->content .= $text; } - + /** - * the content of the event - * + * the content of the event. + * * @return string */ public function getContent() { return $this->content; } - + public function offsetExists($offset) { return isset($this->context[$offset]); @@ -92,14 +82,13 @@ class DelegatedBlockRenderingEvent extends Event implements \ArrayAccess public function offsetSet($offset, $value) { - throw new \RuntimeException("The event context is read-only, you are not " - . "allowed to update it."); + throw new RuntimeException('The event context is read-only, you are not ' + . 'allowed to update it.'); } public function offsetUnset($offset) { - throw new \RuntimeException("The event context is read-only, you are not " - . "allowed to update it."); + throw new RuntimeException('The event context is read-only, you are not ' + . 'allowed to update it.'); } - } diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php index 0b9eb60bb..82b5f8295 100644 --- a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php +++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php @@ -1,18 +1,41 @@ requestStack = $requestStack; } + public function addCheckbox(string $name, array $choices, ?array $default = [], ?array $trans = []): self + { + $missing = count($choices) - count($trans) - 1; + $this->checkboxes[$name] = [ + 'choices' => $choices, 'default' => $default, + 'trans' => array_merge( + $trans, + 0 < $missing ? + array_fill(0, $missing, null) : [] + ), + ]; + + return $this; + } + + public function buildForm(): FormInterface + { + return $this->formFactory + ->createNamed($this->formName, $this->formType, $this->getDefaultData(), array_merge([ + 'helper' => $this, + 'method' => 'GET', + 'csrf_protection' => false, + ], $this->formOptions)) + ->handleRequest($this->requestStack->getCurrentRequest()); + } + + public function getCheckboxData(string $name): array + { + return $this->getFormData()['checkboxes'][$name]; + } + + public function getCheckboxes(): array + { + return $this->checkboxes; + } + + public function getQueryString(): ?string + { + return $this->getFormData()['q']; + } + + public function hasSearchBox(): bool + { + return null !== $this->searchBoxFields; + } + public function setSearchBox($searchBoxFields = null): self { $this->searchBoxFields = $searchBoxFields; @@ -28,33 +97,28 @@ class FilterOrderHelper return $this; } - public function hasSearchBox(): bool + private function getDefaultData(): array { - return $this->searchBoxFields !== null; + $r = []; + + if ($this->hasSearchBox()) { + $r['q'] = ''; + } + + foreach ($this->checkboxes as $name => $c) { + $r['checkboxes'][$name] = $c['default']; + } + + return $r; } private function getFormData(): array { - return [ - 'q' => $this->getQueryString() - ]; - } + if (null === $this->submitted) { + $this->submitted = $this->buildForm() + ->getData(); + } - public function getQueryString(): ?string - { - $q = $this->requestStack->getCurrentRequest() - ->query->get('q', null); - - return empty($q) ? NULL : $q; - } - - public function buildForm($name = null, string $type = FilterOrderType::class, array $options = []): FormInterface - { - return $this->formFactory - ->createNamed($name, $type, $this->getFormData(), \array_merge([ - 'helper' => $this, - 'method' => 'GET', - 'csrf_protection' => false, - ], $options)); + return $this->submitted; } } diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php index ec0f6b60b..9e6cb813c 100644 --- a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php +++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php @@ -1,5 +1,14 @@ requestStack = $requestStack; } + public function addCheckbox(string $name, array $choices, ?array $default = [], ?array $trans = []): self + { + $this->checkboxes[$name] = ['choices' => $choices, 'default' => $default, 'trans' => $trans]; + + return $this; + } + public function addSearchBox(?array $fields = [], ?array $options = []): self { $this->searchBoxFields = $fields; @@ -35,6 +55,14 @@ class FilterOrderHelperBuilder $helper->setSearchBox($this->searchBoxFields); + foreach ($this->checkboxes as $name => [ + 'choices' => $choices, + 'default' => $default, + 'trans' => $trans, + ]) { + $helper->addCheckbox($name, $choices, $default, $trans); + } + return $helper; } } diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php index 1b1c4c983..fd6b4622d 100644 --- a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php +++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php @@ -1,14 +1,23 @@ true, 'is_safe' => ['html'], - ]) - ]; - } - - public function renderFilterOrderHelper( - Environment $environment, - FilterOrderHelper $helper, - ?string $template = '@ChillMain/FilterOrder/base.html.twig', - ?array $options = [] - ) { - return $environment->render($template, [ - 'helper' => $helper, - 'form' => $helper->buildForm()->createView(), - 'options' => $options - ]); - } + ]), + ]; + } + public function renderFilterOrderHelper( + Environment $environment, + FilterOrderHelper $helper, + ?string $template = '@ChillMain/FilterOrder/base.html.twig', + ?array $options = [] + ) { + return $environment->render($template, [ + 'helper' => $helper, + 'form' => $helper->buildForm()->createView(), + 'options' => $options, + ]); + } } diff --git a/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php b/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php index 673b87dbe..88bb8f89d 100644 --- a/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php +++ b/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php @@ -1,92 +1,63 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Templating; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Translation\Translator; +use Symfony\Contracts\Translation\TranslatorInterface; +use function array_key_exists; -/** - * - * This helper helps to find the string in current locale from translatable_strings - * - * @author Julien Fastré - * - */ -class TranslatableStringHelper +final class TranslatableStringHelper implements TranslatableStringHelperInterface { - /** - * - * @var RequestStack - */ - private $requestStack; - - private $fallbackLocales; - - public function __construct(RequestStack $requestStack, Translator $translator) + private string $defaultLocale; + + private RequestStack $requestStack; + + private TranslatorInterface $translator; + + public function __construct(RequestStack $requestStack, TranslatorInterface $translator, ParameterBagInterface $parameterBag) { $this->requestStack = $requestStack; - $this->fallbackLocales = $translator->getFallbackLocales(); + $this->translator = $translator; + $this->defaultLocale = $parameterBag->get('kernel.default_locale'); } - - /** - * return the string in current locale if it exists. - * - * If it does not exists; return the name in the first language available. - * - * Return a blank string if any strings are available. - * Return NULL if $translatableString is NULL - * - * @param array $translatableStrings - * @return string - */ - public function localize(array $translatableStrings) - { - if (NULL === $translatableStrings) { - return NULL; - } - - $language = $this->requestStack->getCurrentRequest()->getLocale(); - - if (isset($translatableStrings[$language])) { - - return $translatableStrings[$language]; - } else { - foreach ($this->fallbackLocales as $locale) { - if (array_key_exists($locale, $translatableStrings)) { - - return $translatableStrings[$locale]; - } - } - + public function localize(array $translatableStrings): ?string + { + if ([] === $translatableStrings) { + return null; } - + + $request = $this->requestStack->getCurrentRequest(); + + $language = null === $request ? $this->defaultLocale : $request->getLocale(); + + if (array_key_exists($language, $translatableStrings)) { + return $translatableStrings[$language]; + } + + foreach ($this->translator->getFallbackLocales() as $locale) { + if (array_key_exists($locale, $translatableStrings)) { + return $translatableStrings[$locale]; + } + } + // no fallback translation... trying the first available $langs = array_keys($translatableStrings); - - if (count($langs) === 0) { + + if ([] === $langs) { return ''; } - + return $translatableStrings[$langs[0]]; - } - -} \ No newline at end of file +} diff --git a/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelperInterface.php b/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelperInterface.php new file mode 100644 index 000000000..0d7fe6d4c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelperInterface.php @@ -0,0 +1,24 @@ + - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Templating; use Symfony\Component\DependencyInjection\ContainerAwareTrait; @@ -28,42 +18,37 @@ use Twig\TwigFilter; class TranslatableStringTwig extends AbstractExtension { use ContainerAwareTrait; - + /** - * @var TranslatableStringHelper $helper + * @var TranslatableStringHelper */ private $helper; - + /** * TranslatableStringTwig constructor. - * - * @param TranslatableStringHelper $translatableStringHelper */ public function __construct(TranslatableStringHelper $translatableStringHelper) { $this->helper = $translatableStringHelper; } - - /* + + /** * Returns a list of filters to add to the existing list. - * + * * (non-PHPdoc) + * * @see Twig_Extension::getFilters() */ public function getFilters() { - return array( + return [ new TwigFilter( - 'localize_translatable_string', array($this, 'localize'))); + 'localize_translatable_string', + [$this, 'localize'] + ), ]; } - - public function localize(array $translatableStrings) - { - return $this->helper - ->localize($translatableStrings); - } - - /* + + /** * Returns the name of the extension. * * @return The name of the extension. @@ -73,4 +58,9 @@ class TranslatableStringTwig extends AbstractExtension return 'chill_main_localize'; } -} \ No newline at end of file + public function localize(array $translatableStrings) + { + return $this->helper + ->localize($translatableStrings); + } +} diff --git a/src/Bundle/ChillMainBundle/Templating/UI/CountNotificationUser.php b/src/Bundle/ChillMainBundle/Templating/UI/CountNotificationUser.php index 859cb673a..80dde8fa1 100644 --- a/src/Bundle/ChillMainBundle/Templating/UI/CountNotificationUser.php +++ b/src/Bundle/ChillMainBundle/Templating/UI/CountNotificationUser.php @@ -1,51 +1,42 @@ - * - * 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\MainBundle\Templating\UI; - -use Symfony\Component\Security\Core\User\UserInterface; -use Chill\MainBundle\Entity\User; /** - * Show a number of notification to user in the upper right corner + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Templating\UI; + +use Chill\MainBundle\Entity\User; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Show a number of notification to user in the upper right corner. */ class CountNotificationUser { /** - * * @var NotificationCounterInterface[] */ protected $counters = []; - + public function addNotificationCounter(NotificationCounterInterface $counter) { $this->counters[] = $counter; } - + public function getSumNotification(UserInterface $u): int { - $sum = 0; - + $sum = 0; + foreach ($this->counters as $counter) { $sum += $counter->addNotification($u); } - + return $sum; } } diff --git a/src/Bundle/ChillMainBundle/Templating/UI/NotificationCounterInterface.php b/src/Bundle/ChillMainBundle/Templating/UI/NotificationCounterInterface.php index f0621b81a..528903cbf 100644 --- a/src/Bundle/ChillMainBundle/Templating/UI/NotificationCounterInterface.php +++ b/src/Bundle/ChillMainBundle/Templating/UI/NotificationCounterInterface.php @@ -1,32 +1,22 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Templating\UI; use Symfony\Component\Security\Core\User\UserInterface; -/** - * - * @author Julien Fastré - */ interface NotificationCounterInterface { /** - * Add a number of notification + * Add a number of notification. */ public function addNotification(UserInterface $u): int; } diff --git a/src/Bundle/ChillMainBundle/Templating/Widget/WidgetInterface.php b/src/Bundle/ChillMainBundle/Templating/Widget/WidgetInterface.php index a768f0766..16f33057d 100644 --- a/src/Bundle/ChillMainBundle/Templating/Widget/WidgetInterface.php +++ b/src/Bundle/ChillMainBundle/Templating/Widget/WidgetInterface.php @@ -1,5 +1,14 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Templating\Widget; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Chill\MainBundle\Templating\Widget\WidgetInterface; use Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; +use function array_key_exists; +use function ksort; /** * Add the function `chill_delegated_block`. - * + * * In a template, you can now allow rendering of a block from other bundle. - * + * * The layout template must explicitly call the rendering of other block, * with the twig function - * + * * ``` * chill_delegated_block('block_name', { 'array' : 'with context' } ) * ``` - * + * * This will launch an event * `Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent` with * the event's name 'chill_block.block_name'. - * + * * You may add content to the page using the function * `DelegatedBlockRenderingEvent::addContent`. - * - * See also the documentation of + * + * See also the documentation of * `Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent` * for usage of this event class - * - * - * @author Julien Fastré */ class WidgetRenderingTwig extends AbstractExtension { - /** - * Contains the widget. This is a double dimension array : - * - * - first key is the place, - * - second key is the ordering ; - * - the value is an array where the widget is the first argument and the - * second is the config - * - * i.e : - * - * $widget['place']['ordering'] = array($widget, $config); - * - * - * - * @var array an array of widget by place and ordering - */ - protected $widget = array(); - - /** - * * @var EventDispatcherInterface */ protected $eventDispatcher; - + + /** + * Contains the widget. This is a double dimension array :. + * + * - first key is the place, + * - second key is the ordering ; + * - the value is an array where the widget is the first argument and the + * second is the config + * + * i.e : + * + * $widget['place']['ordering'] = array($widget, $config); + * + * @var array an array of widget by place and ordering + */ + protected $widget = []; + public function __construct(EventDispatcherInterface $eventDispatcher) { $this->eventDispatcher = $eventDispatcher; } - - + + /** + * add a widget to this class, which become available for a future call. + * + * This function is used by DependencyInjection\CompilerPass\WidgetCompilerPass, + * which add the widget to this class when it is created by from DI, according + * to the given config under `chill_main`. + * + * @param string $place + * @param WidgetInterface $widget + * @param mixed $ordering + */ + public function addWidget($place, $ordering, $widget, array $config = []) + { + $this->widget[$place][$ordering] = [$widget, $config]; + } + + public function getFunctions() + { + return [ + new TwigFunction( + 'chill_delegated_block', + [$this, 'renderingWidget'], + [ + 'is_safe' => ['html'], + 'needs_environment' => true, + 'deprecated' => true, 'alternative' => 'chill_widget', + ] + ), + new TwigFunction( + 'chill_widget', + [$this, 'renderingWidget'], + ['is_safe' => ['html'], 'needs_environment' => true] + ), + ]; + } + public function getName() { return 'chill_main_widget'; } - - public function getFunctions() - { - return array( - new TwigFunction('chill_delegated_block', - array($this, 'renderingWidget'), - array( - 'is_safe' => array('html'), - 'needs_environment' => true, - 'deprecated' => true, 'alternative' => 'chill_widget' - )), - new TwigFunction('chill_widget', - array($this, 'renderingWidget'), - array('is_safe' => array('html'), 'needs_environment' => true)) - ); - } - - public function renderingWidget(Environment $env, $block, array $context = array()) + + public function renderingWidget(Environment $env, $block, array $context = []) { // get the content of widgets $content = ''; + foreach ($this->getWidgetsArraysOrdered($block) as $a) { - /* @var $widget Widget\WidgetInterface */ + /** @var Widget\WidgetInterface $widget */ $widget = $a[0]; $config = $a[1]; - + $content .= $widget->render($env, $block, $context, $config); } - + // for old rendering events (deprecated) $event = new DelegatedBlockRenderingEvent($context); - - $this->eventDispatcher->dispatch('chill_block.'.$block, $event); - - return $content." ".$event->getContent(); + + $this->eventDispatcher->dispatch('chill_block.' . $block, $event); + + return $content . ' ' . $event->getContent(); } - + /** - * add a widget to this class, which become available for a future call. - * - * This function is used by DependencyInjection\CompilerPass\WidgetCompilerPass, - * which add the widget to this class when it is created by from DI, according - * to the given config under `chill_main`. - * - * @param string $place - * @param WidgetInterface $widget - * @param array $config - */ - public function addWidget($place, $ordering, $widget, array $config = array()) - { - $this->widget[$place][$ordering] = array($widget, $config); - } - - /** - * * @param string $place + * * @return array */ protected function getWidgetsArraysOrdered($place) { if (!array_key_exists($place, $this->widget)) { - $this->widget[$place] = array(); + $this->widget[$place] = []; } - - \ksort($this->widget[$place]); - + + ksort($this->widget[$place]); + return $this->widget[$place]; } - - - } diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php index d33a6f88d..f5b26b353 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php @@ -1,238 +1,260 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Test\Export; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\QueryBuilder; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Traversable; +use function call_user_func; +use function count; +use function is_array; +use function is_callable; +use function is_string; /** * Helper which creates a set of test for aggregators. * - * @author Julien Fastré + * @internal */ abstract class AbstractAggregatorTest extends KernelTestCase { /** - * Create an aggregator instance which will be used in tests. - * - * This method is always used after an eventuel `setUp` method. - * - * @return \Chill\MainBundle\Export\AggregatorInterface - */ - abstract public function getAggregator(); - - /** - * Create possible combinaison of data (produced by the form). - * - * This data will be used to generate data providers using this data. - * - * This method is executed before the `setUp` method. - * - * @return array an array of data. Example : `array( array(), array('fields' => array(1,2,3), ...)` where an empty array and `array(1,2,3)` are possible values - */ - public abstract function getFormData(); - - /** - * get an array of query builders that the aggregator will use. - * - * Those query builders will be used to test aggregator behaviour on this - * query builder. - * - * This method is executed before the `setUp` method. - * - * @return \Doctrine\DBAL\Query\QueryBuilder[] - */ - public abstract function getQueryBuilders(); - - /** - * prepare data for `testGetQueryKeys` - */ - public function dataProviderGetQueryKeys() - { - foreach ($this->getFormData() as $data) { - yield array($data); - } - } - - /** - * prepare date for method `testGetResultsAndLabels` - */ - public function dataProviderGetResultsAndLabels() - { - foreach ($this->getQueryBuilders() as $qb) { - foreach ($this->getFormData() as $data) { - yield array(clone $qb, $data); - } - } - } - - /** - * provide data for `testAlterQuery` + * provide data for `testAlterQuery`. */ public function dataProviderAlterQuery() { foreach ($this->getQueryBuilders() as $qb) { foreach ($this->getFormData() as $data) { - yield array(clone $qb, $data); + yield [clone $qb, $data]; } } } - + + /** + * prepare data for `testGetQueryKeys`. + */ + public function dataProviderGetQueryKeys() + { + foreach ($this->getFormData() as $data) { + yield [$data]; + } + } + + /** + * prepare date for method `testGetResultsAndLabels`. + */ + public function dataProviderGetResultsAndLabels() + { + foreach ($this->getQueryBuilders() as $qb) { + foreach ($this->getFormData() as $data) { + yield [clone $qb, $data]; + } + } + } + + /** + * Create an aggregator instance which will be used in tests. + * + * This method is always used after an eventuel `setUp` method. + * + * @return \Chill\MainBundle\Export\AggregatorInterface + */ + abstract public function getAggregator(); + + /** + * Create possible combinaison of data (produced by the form). + * + * This data will be used to generate data providers using this data. + * + * This method is executed before the `setUp` method. + * + * @return array an array of data. Example : `array( array(), array('fields' => array(1,2,3), ...)` where an empty array and `array(1,2,3)` are possible values + */ + abstract public function getFormData(); + + /** + * get an array of query builders that the aggregator will use. + * + * Those query builders will be used to test aggregator behaviour on this + * query builder. + * + * This method is executed before the `setUp` method. + * + * @return \Doctrine\DBAL\Query\QueryBuilder[] + */ + abstract public function getQueryBuilders(); + + /** + * test the alteration of query by the filter. + * + * @dataProvider dataProviderAlterQuery + * + * @param type $data + */ + public function testAlterQuery(QueryBuilder $query, $data) + { + // retains informations about query + $nbOfFrom = $query->getDQLPart('from') !== null ? + count($query->getDQLPart('from')) : 0; + $nbOfWhere = $query->getDQLPart('where') !== null ? + $query->getDQLPart('where')->count() : 0; + $nbOfSelect = $query->getDQLPart('select') !== null ? + count($query->getDQLPart('select')) : 0; + + $this->getAggregator()->alterQuery($query, $data); + + $this->assertGreaterThanOrEqual( + $nbOfFrom, + $query->getDQLPart('from') !== null ? count($query->getDQLPart('from')) : 0, + "Test that there are equal or more 'from' clause after that the filter has + altered the query" + ); + $this->assertGreaterThanOrEqual( + $nbOfWhere, + $query->getDQLPart('where') !== null ? $query->getDQLPart('where')->count() : 0, + "Test that there are equal or more 'where' clause after that the filter has" + . 'altered the query' + ); + $this->assertGreaterThanOrEqual( + $nbOfSelect, + $query->getDQLPart('select') !== null ? count($query->getDQLPart('select')) : 0, + "Test that the filter has no altered the 'select' part of the query" + ); + } + /** * Test the `applyOn` method. */ public function testApplyOn() { $filter = $this->getAggregator(); - - $this->assertInternalType('string', $filter->applyOn(), - "test that the internal type of \"applyOn\" is a string"); - $this->assertNotEmpty($filter->applyOn(), - "test that the \"applyOn\" method return a non-empty string"); + + $this->assertInternalType( + 'string', + $filter->applyOn(), + 'test that the internal type of "applyOn" is a string' + ); + $this->assertNotEmpty( + $filter->applyOn(), + 'test that the "applyOn" method return a non-empty string' + ); } - + /** - * test the `getTitle` method - */ - public function testGetTitle() - { - $title = $this->getAggregator()->getTitle(); - - $this->assertInternalType('string', $title); - $this->assertNotEmpty($title, - "test that the title is not empty"); - } - - /** - * Test that the query keys are strings - * - * @param array $data + * Test that the query keys are strings. + * * @dataProvider dataProviderGetQueryKeys */ public function testGetQueryKeys(array $data) { $queryKeys = $this->getAggregator()->getQueryKeys($data); - - $this->assertInternalType('array', $queryKeys, - "test that the query keys returned are an array"); - $this->assertContainsOnly("string", $queryKeys, - "test that the query keys returned by `getQueryKeys` are only strings"); - $this->assertGreaterThanOrEqual(1, count($queryKeys), - "test that there are at least one query key returned"); + + $this->assertInternalType( + 'array', + $queryKeys, + 'test that the query keys returned are an array' + ); + $this->assertContainsOnly( + 'string', + $queryKeys, + 'test that the query keys returned by `getQueryKeys` are only strings' + ); + $this->assertGreaterThanOrEqual( + 1, + count($queryKeys), + 'test that there are at least one query key returned' + ); } - + /** - * - * Test that - * + * Test that. + * * - the results have a correct form (are arrays or traversable) * - each key in a row are present in getQueryKeys ; * - each returned object of the `getLabels` method is callable * - each result can be converted to string using this callable * - each of this callable can provide a string for '_header' - * - * @param QueryBuilder $qb - * @param array $data - * + * * @dataProvider dataProviderGetResultsAndLabels */ public function testGetResultsAndLabels(QueryBuilder $qb, array $data) { // it is more convenient to group the `getResult` and `getLabels` test // due to the fact that testing both methods use the same tools. - + // limit the result for the query for performance reason $qb->setMaxResults(1); - + $queryKeys = $this->getAggregator()->getQueryKeys($data); $this->getAggregator()->alterQuery($qb, $data); - + $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); - + if (count($results) === 0) { - $this->markTestIncomplete("The result is empty. We cannot process tests " - . "on results"); + $this->markTestIncomplete('The result is empty. We cannot process tests ' + . 'on results'); } - + // testing the result $result = $results[0]; - - $this->assertTrue( $result instanceof \Traversable || is_array($result), - "test that each row in the result is traversable or an array"); - + + $this->assertTrue( + $result instanceof Traversable || is_array($result), + 'test that each row in the result is traversable or an array' + ); + foreach ($queryKeys as $key) { - $this->assertContains($key, array_keys($result), - "test that each key is present in `getQueryKeys`"); - - $closure = $this->getAggregator()->getLabels($key, array($result[$key]), $data); - - $this->assertTrue(is_callable($closure, false), - "test that the `getLabels` for key is a callable"); - $this->assertTrue(is_string((string) call_user_func($closure, $result[$key])), - sprintf("test that the callable return by `getLabels` for key %s " - . "is a string or an be converted to a string", $key)); - + $this->assertContains( + $key, + array_keys($result), + 'test that each key is present in `getQueryKeys`' + ); + + $closure = $this->getAggregator()->getLabels($key, [$result[$key]], $data); + + $this->assertTrue( + is_callable($closure, false), + 'test that the `getLabels` for key is a callable' + ); + $this->assertTrue( + is_string((string) call_user_func($closure, $result[$key])), + sprintf('test that the callable return by `getLabels` for key %s ' + . 'is a string or an be converted to a string', $key) + ); + $this->assertTrue( // conditions is_string((string) call_user_func($closure, '_header')) && !empty(call_user_func($closure, '_header')) && call_user_func($closure, '_header') !== '_header', // message - sprintf("Test that the callable return by `getLabels` for key %s " - . "can provide an header", $key) - ); + sprintf('Test that the callable return by `getLabels` for key %s ' + . 'can provide an header', $key) + ); } } - + /** - * test the alteration of query by the filter - * - * @dataProvider dataProviderAlterQuery - * @param QueryBuilder $query - * @param type $data + * test the `getTitle` method. */ - public function testAlterQuery(QueryBuilder $query, $data) + public function testGetTitle() { - // retains informations about query - $nbOfFrom = $query->getDQLPart('from') !== null ? - count($query->getDQLPart('from')) : 0; - $nbOfWhere = $query->getDQLPart('where') !== null ? - $query->getDQLPart('where')->count() : 0; - $nbOfSelect = $query->getDQLPart('select') !== null ? - count($query->getDQLPart('select')) : 0; - - $this->getAggregator()->alterQuery($query, $data); - - $this->assertGreaterThanOrEqual( - $nbOfFrom, - $query->getDQLPart('from') !== null ? count($query->getDQLPart('from')) : 0, - "Test that there are equal or more 'from' clause after that the filter has - altered the query"); - $this->assertGreaterThanOrEqual( - $nbOfWhere, - $query->getDQLPart('where') !== null ? $query->getDQLPart('where')->count() : 0, - "Test that there are equal or more 'where' clause after that the filter has" - . "altered the query"); - $this->assertGreaterThanOrEqual( - $nbOfSelect, - $query->getDQLPart('select') !== null ? count($query->getDQLPart('select')) : 0, - "Test that the filter has no altered the 'select' part of the query"); - + $title = $this->getAggregator()->getTitle(); + + $this->assertInternalType('string', $title); + $this->assertNotEmpty( + $title, + 'test that the title is not empty' + ); } } diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php index fcf82fca9..99e9779f1 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php @@ -1,28 +1,29 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Test\Export; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -use Doctrine\ORM\QueryBuilder; -use Doctrine\ORM\NativeQuery; -use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\Test\PrepareClientTrait; +use Doctrine\ORM\NativeQuery; +use Doctrine\ORM\QueryBuilder; +use RuntimeException; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Role\Role; +use Traversable; +use function call_user_func; +use function count; +use function is_array; +use function is_callable; +use function is_string; /** * This class provide a set of tests for exports. @@ -30,41 +31,35 @@ use Symfony\Component\HttpFoundation\Request; * The tests provided by this class will check basic things, like * the type of value are conform to the expected, etc. * - * @author julien.fastre@champs-libres.coop + * @internal */ abstract class AbstractExportTest extends WebTestCase { - use PrepareClientTrait; + public function dataProviderGetQueryKeys() + { + foreach ($this->getFormData() as $data) { + yield [$data]; + } + } /** - * Create an instance of the report to test - * - * @return \Chill\MainBundle\Export\ExportInterface an instance of the export to test + * create data for `ìnitiateQuery` method. */ - public abstract function getExport(); + public function dataProviderInitiateQuery() + { + $acl = $this->getAcl(); + + foreach ($this->getModifiersCombination() as $modifiers) { + foreach ($this->getFormData() as $data) { + yield [$modifiers, $acl, $data]; + } + } + } /** - * Create possible combinaison of data (produced by the form). - * - * This data will be used to generate data providers using this data. - * - * @return array an array of data. Example : `array( array(), array('fields' => array(1,2,3), ...)` where an empty array and `array(1,2,3)` are possible values - */ - public abstract function getFormData(); - - /** - * get the possible modifiers which could apply in combination to this - * export. - * . - * - * @return array of string[] an array which contains an array of possible modifiers. Example : `array( array('modifier_1', 'modifier_2'), array('modifier_1'), ...)` - */ - abstract public function getModifiersCombination(); - - /** - * Return an array usable as ACL + * Return an array usable as ACL. * * If this method is overridden, the returned result must be an array * with this form : @@ -77,11 +72,10 @@ abstract class AbstractExportTest extends WebTestCase * ) * ); * ``` - * */ public function getACL() { - if (static::$kernel === null) { + if (null === static::$kernel) { static::bootKernel(); } @@ -94,169 +88,103 @@ abstract class AbstractExportTest extends WebTestCase ->findAll(); if (count($centers) === 0) { - throw new \RuntimeException("No center found. Did you forget to " - . "run `doctrine:fixtures:load` command before ?"); - } - if (count($circles) === 0) { - throw new \RuntimeException("No circle found. Did you forget to " - . "run `doctrine:fixtures:load` command before ?"); + throw new RuntimeException('No center found. Did you forget to ' + . 'run `doctrine:fixtures:load` command before ?'); } - return array([ + if (count($circles) === 0) { + throw new RuntimeException('No circle found. Did you forget to ' + . 'run `doctrine:fixtures:load` command before ?'); + } + + return [[ 'center' => $centers[0], 'circles' => [ - $circles - ]]); - } - - /** - * Test that the getType method return a string - */ - public function testGetType() - { - $export = $this->getExport(); - - $this->assertInternalType('string', $export->getType(), - "Assert that the `getType` method return a string"); - $this->assertNotEmpty($export->getType(), "Assert that the `getType` method" - . " does not return an empty string."); - } - - /** - * Test that the description is not empty - */ - public function testGetDescription() - { - $export = $this->getExport(); - - $this->assertInternalType('string', $export->getDescription(), - "Assert that the `getDescription` method return a string"); - $this->assertNotEmpty($export->getDescription(), - "Assert that the `getDescription` method does not return an empty " - . "string."); - } - - /** - * create data for `ìnitiateQuery` method - */ - public function dataProviderInitiateQuery() - { - $acl = $this->getAcl(); - - foreach($this->getModifiersCombination() as $modifiers) { - - foreach($this->getFormData() as $data) { - - yield array($modifiers, $acl, $data); - } - } - } - - public function dataProviderGetQueryKeys() - { - foreach($this->getFormData() as $data) { - yield array($data); - } + $circles, + ], ]]; } /** + * Create an instance of the report to test. * - * test that the query returned is a QueryBuilder or a NativeQuery. - * - * If the query is a QueryBuilder, test that select and from is not empty. - * - * If the query is a native sql, test the query is not empty (length is - * > 0). - * - * @dataProvider dataProviderInitiateQuery + * @return \Chill\MainBundle\Export\ExportInterface an instance of the export to test */ - public function testInitiateQuery($modifiers, $acl, $data) - { - $query = $this->getExport()->initiateQuery($modifiers, $acl, $data); - - $this->assertTrue($query instanceof QueryBuilder || $query instanceof NativeQuery, - sprintf("Assert that the returned query is an instance of %s or %s", - QueryBuilder::class, Query::class)); - - if ($query instanceof QueryBuilder) { - - $this->assertGreaterThanOrEqual(1, count($query->getDQLPart('select')), - "assert there is at least one 'select' part"); - - $this->assertGreaterThanOrEqual(1, count($query->getDQLPart('from')), - "assert there is at least one 'from' part"); - - } elseif ($query instanceof NativeQuery) { - $this->assertNotEmpty($query->getSQL(), - "check that the SQL query is not empty"); - } - } + abstract public function getExport(); /** - * Test that supportsModifier return : + * Create possible combinaison of data (produced by the form). * - * - an array of string, if the query is a QueryBuilder ; - * - nothing, if the query is a native SQL + * This data will be used to generate data providers using this data. * - * @dataProvider dataProviderInitiateQuery + * @return array an array of data. Example : `array( array(), array('fields' => array(1,2,3), ...)` where an empty array and `array(1,2,3)` are possible values */ - public function testSupportsModifier($modifiers, $acl, $data) - { - $export = $this->getExport(); - $query = $export->initiateQuery($modifiers, $acl, $data); - - if ($query instanceof QueryBuilder) { - $this->assertContainsOnly('string', $export->supportsModifiers(), - "Test that the `supportsModifiers` method returns only strings"); - } elseif ($query instanceof NativeQuery) { - $this->assertTrue($export->supportsModifiers() === null || - count($export->supportsModifiers()) === 0, - "Test that the `supportsModifier` methods returns null or an empty array"); - } - } + abstract public function getFormData(); /** - * Test required role is an instance of Role + * get the possible modifiers which could apply in combination to this + * export. + * . + * + * @return array of string[] an array which contains an array of possible modifiers. Example : `array( array('modifier_1', 'modifier_2'), array('modifier_1'), ...)` */ - public function testRequiredRole() - { - $role = $this->getExport()->requiredRole(); - - $this->assertInstanceOf(Role::class, $role, - sprintf("test that the returned value of `requiredRole` is an instance " - . "of %s", Role::class)); - } + abstract public function getModifiersCombination(); /** - * Test the formatters type are string + * Test the formatters type are string. */ public function testGetAllowedFormattersType() { $formattersTypes = $this->getExport()->getAllowedFormattersTypes(); - $this->assertContainsOnly("string", $formattersTypes, - "Test that the method `getAllowedFormattersTypes` returns an array of string"); + $this->assertContainsOnly( + 'string', + $formattersTypes, + 'Test that the method `getAllowedFormattersTypes` returns an array of string' + ); } /** - * Test that the query keys are strings + * Test that the description is not empty. + */ + public function testGetDescription() + { + $export = $this->getExport(); + + $this->assertInternalType( + 'string', + $export->getDescription(), + 'Assert that the `getDescription` method return a string' + ); + $this->assertNotEmpty( + $export->getDescription(), + 'Assert that the `getDescription` method does not return an empty ' + . 'string.' + ); + } + + /** + * Test that the query keys are strings. * - * @param array $data * @dataProvider dataProviderGetQueryKeys */ public function testGetQueryKeys(array $data) { $queryKeys = $this->getExport()->getQueryKeys($data); - $this->assertContainsOnly("string", $queryKeys, - "test that the query keys returned by `getQueryKeys` are only strings"); - $this->assertGreaterThanOrEqual(1, count($queryKeys), - "test that there are at least one query key returned"); + $this->assertContainsOnly( + 'string', + $queryKeys, + 'test that the query keys returned by `getQueryKeys` are only strings' + ); + $this->assertGreaterThanOrEqual( + 1, + count($queryKeys), + 'test that there are at least one query key returned' + ); } /** - * - * Test that + * Test that. * * - the results have a correct form (are arrays or traversable) * - each key in a row are present in getQueryKeys ; @@ -266,7 +194,6 @@ abstract class AbstractExportTest extends WebTestCase * * @param string[] $modifiers * @param array $acl - * @param array $data * * @dataProvider dataProviderInitiateQuery */ @@ -276,7 +203,7 @@ abstract class AbstractExportTest extends WebTestCase // due to the fact that testing both methods use the same tools. $queryKeys = $this->getExport()->getQueryKeys($data); - $query = $this->getExport()->initiateQuery($modifiers, $acl, $data); + $query = $this->getExport()->initiateQuery($modifiers, $acl, $data); // limit the result for the query for performance reason (only for QueryBuilder, // not possible in NativeQuery) @@ -284,33 +211,45 @@ abstract class AbstractExportTest extends WebTestCase $query->setMaxResults(1); } - $results = $this->getExport()->getResult($query, $data); + $results = $this->getExport()->getResult($query, $data); - $this->assertInternalType('array', $results, - "assert that the returned result is an array"); + $this->assertInternalType( + 'array', + $results, + 'assert that the returned result is an array' + ); if (count($results) === 0) { - $this->markTestIncomplete("The result is empty. We cannot process tests " - . "on results"); + $this->markTestIncomplete('The result is empty. We cannot process tests ' + . 'on results'); } // testing the result $result = $results[0]; - $this->assertTrue( $result instanceof \Traversable || is_array($result), - "test that each row in the result is traversable or an array"); + $this->assertTrue( + $result instanceof Traversable || is_array($result), + 'test that each row in the result is traversable or an array' + ); foreach ($result as $key => $value) { - $this->assertContains($key, $queryKeys, - "test that each key is present in `getQueryKeys`"); + $this->assertContains( + $key, + $queryKeys, + 'test that each key is present in `getQueryKeys`' + ); - $closure = $this->getExport()->getLabels($key, array($value), $data); + $closure = $this->getExport()->getLabels($key, [$value], $data); - $this->assertTrue(is_callable($closure, false), - "test that the `getLabels` for key is a callable"); - $this->assertTrue(is_string((string) call_user_func($closure, $value)), - sprintf("test that the callable return by `getLabels` for key %s " - . "is a string or an be converted to a string", $key)); + $this->assertTrue( + is_callable($closure, false), + 'test that the `getLabels` for key is a callable' + ); + $this->assertTrue( + is_string((string) call_user_func($closure, $value)), + sprintf('test that the callable return by `getLabels` for key %s ' + . 'is a string or an be converted to a string', $key) + ); $this->assertTrue( // conditions @@ -318,24 +257,85 @@ abstract class AbstractExportTest extends WebTestCase && !empty(call_user_func($closure, '_header')) && call_user_func($closure, '_header') !== '_header', // message - sprintf("Test that the callable return by `getLabels` for key %s " - . "can provide an header", $key) - ); + sprintf('Test that the callable return by `getLabels` for key %s ' + . 'can provide an header', $key) + ); } - } + /** + * Test that the getType method return a string. + */ + public function testGetType() + { + $export = $this->getExport(); + + $this->assertInternalType( + 'string', + $export->getType(), + 'Assert that the `getType` method return a string' + ); + $this->assertNotEmpty($export->getType(), 'Assert that the `getType` method' + . ' does not return an empty string.'); + } + + /** + * test that the query returned is a QueryBuilder or a NativeQuery. + * + * If the query is a QueryBuilder, test that select and from is not empty. + * + * If the query is a native sql, test the query is not empty (length is + * > 0). + * + * @dataProvider dataProviderInitiateQuery + * + * @param mixed $modifiers + * @param mixed $acl + * @param mixed $data + */ + public function testInitiateQuery($modifiers, $acl, $data) + { + $query = $this->getExport()->initiateQuery($modifiers, $acl, $data); + + $this->assertTrue( + $query instanceof QueryBuilder || $query instanceof NativeQuery, + sprintf( + 'Assert that the returned query is an instance of %s or %s', + QueryBuilder::class, + Query::class + ) + ); + + if ($query instanceof QueryBuilder) { + $this->assertGreaterThanOrEqual( + 1, + count($query->getDQLPart('select')), + "assert there is at least one 'select' part" + ); + + $this->assertGreaterThanOrEqual( + 1, + count($query->getDQLPart('from')), + "assert there is at least one 'from' part" + ); + } elseif ($query instanceof NativeQuery) { + $this->assertNotEmpty( + $query->getSQL(), + 'check that the SQL query is not empty' + ); + } + } /** * Test that the translated title of the export is present the list, - * and that the list of exports (under `/fr/exports/`) is still successfull + * and that the list of exports (under `/fr/exports/`) is still successfull. */ public function testListExportPage() { - /* @var $client \Symfony\Component\BrowserKit\Client */ + /** @var \Symfony\Component\BrowserKit\Client $client */ $client = $this->getClient(); $export = $this->getExport(); - $prophet= new \Prophecy\Prophet; + $prophet = new \Prophecy\Prophet(); $container = static::$kernel->getContainer(); // store the locale in a request @@ -343,16 +343,68 @@ abstract class AbstractExportTest extends WebTestCase $request->setLocale('fr'); $container->get('request_stack')->push($request); // translate the title - $title = $container->get('translator')->trans($export->getTitle()); + $title = $container->get('translator')->trans($export->getTitle()); // performs the request to /fr/exports $crawler = $client->request('GET', '/fr/exports/'); // and finally make tests - $this->assertTrue($client->getResponse()->isSuccessful(), - "test that the response of /fr/exports/ is successful"); - $this->assertContains($title, $crawler->text(), - "test that the page /fr/exports/ contains the title of the " - . "exports ('$title')"); + $this->assertTrue( + $client->getResponse()->isSuccessful(), + 'test that the response of /fr/exports/ is successful' + ); + $this->assertContains( + $title, + $crawler->text(), + 'test that the page /fr/exports/ contains the title of the ' + . "exports ('{$title}')" + ); + } + + /** + * Test required role is an instance of Role. + */ + public function testRequiredRole() + { + $role = $this->getExport()->requiredRole(); + + $this->assertInstanceOf( + Role::class, + $role, + sprintf('test that the returned value of `requiredRole` is an instance ' + . 'of %s', Role::class) + ); + } + + /** + * Test that supportsModifier return :. + * + * - an array of string, if the query is a QueryBuilder ; + * - nothing, if the query is a native SQL + * + * @dataProvider dataProviderInitiateQuery + * + * @param mixed $modifiers + * @param mixed $acl + * @param mixed $data + */ + public function testSupportsModifier($modifiers, $acl, $data) + { + $export = $this->getExport(); + $query = $export->initiateQuery($modifiers, $acl, $data); + + if ($query instanceof QueryBuilder) { + $this->assertContainsOnly( + 'string', + $export->supportsModifiers(), + 'Test that the `supportsModifiers` method returns only strings' + ); + } elseif ($query instanceof NativeQuery) { + $this->assertTrue( + $export->supportsModifiers() === null + || count($export->supportsModifiers()) === 0, + 'Test that the `supportsModifier` methods returns null or an empty array' + ); + } } } diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php index 7721b9212..78cf0083a 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php @@ -1,190 +1,199 @@ - * - * 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\MainBundle\Test\Export; - -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\QueryBuilder; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; /** - * Helper to test filters + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Test\Export; + +use Doctrine\ORM\QueryBuilder; +use Exception; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use function count; +use function get_class; +use function is_array; +use function is_string; + +/** + * Helper to test filters. + * + * @internal */ abstract class AbstractFilterTest extends KernelTestCase { /** - * * @var \Prophecy\Prophet */ protected $prophet; - - /** - * Create a filter which will be used in tests. - * - * This method is always used after an eventuel `setUp` method. - * - * @return \Chill\MainBundle\Export\FilterInterface - */ - abstract public function getFilter(); - - /** - * Create possible combinaison of data (produced by the form). - * - * This data will be used to generate data providers using this data. - * - * As all data providers, this method is executed **before** calling - * the `setUp` method. - * - * @return array an array of data. Example : `array( array(), array('fields' => array(1,2,3), ...)` where an empty array and `array(1,2,3)` are possible values - */ - public abstract function getFormData(); - - /** - * Return an array with different minimal query builders - * - * As all data providers, this method is executed **before** calling - * the `setUp` method. - * - * @return QueryBuilder[] an array of query builder - */ - public abstract function getQueryBuilders(); - - public function dataProviderAlterQuery() - { - foreach ($this->getQueryBuilders() as $qb) { - foreach ($this->getFormData() as $data) { - yield array($qb, $data); - } - } - } - - public function dataProviderDescriptionAction() - { - foreach ($this->getFormData() as $data) { - yield array($data); - } - } - + public function setUp() { $this->prepareProphet(); } - - - public function testApplyOn() + + public function dataProviderAlterQuery() { - $filter = $this->getFilter(); - - $this->assertInternalType('string', $filter->applyOn()); + foreach ($this->getQueryBuilders() as $qb) { + foreach ($this->getFormData() as $data) { + yield [$qb, $data]; + } + } } - + + public function dataProviderDescriptionAction() + { + foreach ($this->getFormData() as $data) { + yield [$data]; + } + } + /** - * test the alteration of query by the filter - * + * Create a filter which will be used in tests. + * + * This method is always used after an eventuel `setUp` method. + * + * @return \Chill\MainBundle\Export\FilterInterface + */ + abstract public function getFilter(); + + /** + * Create possible combinaison of data (produced by the form). + * + * This data will be used to generate data providers using this data. + * + * As all data providers, this method is executed **before** calling + * the `setUp` method. + * + * @return array an array of data. Example : `array( array(), array('fields' => array(1,2,3), ...)` where an empty array and `array(1,2,3)` are possible values + */ + abstract public function getFormData(); + + /** + * Return an array with different minimal query builders. + * + * As all data providers, this method is executed **before** calling + * the `setUp` method. + * + * @return QueryBuilder[] an array of query builder + */ + abstract public function getQueryBuilders(); + + /** + * test the alteration of query by the filter. + * * @dataProvider dataProviderAlterQuery - * @param QueryBuilder $query + * * @param type $data */ public function testAlterQuery(QueryBuilder $query, $data) { // retains informations about query - $nbOfFrom = $query->getDQLPart('from') !== null ? + $nbOfFrom = $query->getDQLPart('from') !== null ? count($query->getDQLPart('from')) : 0; - $nbOfWhere = $query->getDQLPart('where') !== null ? + $nbOfWhere = $query->getDQLPart('where') !== null ? $query->getDQLPart('where')->count() : 0; $nbOfSelect = $query->getDQLPart('select') !== null ? count($query->getDQLPart('select')) : 0; - + $this->getFilter()->alterQuery($query, $data); - + $this->assertGreaterThanOrEqual( - $nbOfFrom, + $nbOfFrom, $query->getDQLPart('from') !== null ? count($query->getDQLPart('from')) : 0, - "Test that there are equal or more 'from' clause after that the filter has + "Test that there are equal or more 'from' clause after that the filter has altered the query" - ); + ); $this->assertGreaterThanOrEqual( - $nbOfWhere, + $nbOfWhere, $query->getDQLPart('where') !== null ? $query->getDQLPart('where')->count() : 0, "Test that there are equal or more 'where' clause after that the filter has" - . "altered the query" - ); + . 'altered the query' + ); $this->assertEquals( - $nbOfSelect, + $nbOfSelect, $query->getDQLPart('select') !== null ? count($query->getDQLPart('select')) : 0, "Test that the filter has no altered the 'select' part of the query" - ); - + ); } - - public function testGetTitle() + + public function testApplyOn() { - $title = $this->getFilter()->getTitle(); - - $this->assertInternalType('string', $title); - $this->assertNotEmpty($title, - "test that the title is not empty"); + $filter = $this->getFilter(); + + $this->assertInternalType('string', $filter->applyOn()); } - + /** - * * @dataProvider dataProviderDescriptionAction + * * @param array $data */ public function testDescriptionAction($data) { $description = $this->getFilter()->describeAction($data); - + $this->assertTrue( is_string($description) || is_array($description), - "test that the description is a string or an array" - ); - + 'test that the description is a string or an array' + ); + if (is_string($description)) { - $this->assertNotEmpty($description, - "test that the description is not empty"); + $this->assertNotEmpty( + $description, + 'test that the description is not empty' + ); } elseif (is_array($description)) { - $this->assertInternalType('string', $description[0], - "test that the first element in the description array is a string"); - + $this->assertInternalType( + 'string', + $description[0], + 'test that the first element in the description array is a string' + ); + // test that the message is translated try { - if (static::$kernel === null) { + if (null === static::$kernel) { static::bootKernel(); } - + $catalogue = static::$kernel->getContainer() ->get('translator') ->getCatalogue(); - - } catch (\Exception $ex) { + } catch (Exception $ex) { $this->markTestIncomplete( - sprintf("This test is incomplete due to %s thrown by 'translator' : %s, " - . "complete stack : %s", get_class($ex), $ex->getMessage(), - $ex->getTraceAsString())); + sprintf( + "This test is incomplete due to %s thrown by 'translator' : %s, " + . 'complete stack : %s', + get_class($ex), + $ex->getMessage(), + $ex->getTraceAsString() + ) + ); } - $this->assertTrue($catalogue->has($description[0], - isset($description[2]) ? $description[2] : 'messages'), - sprintf("Test that the message returned by getDescriptionAction is " - . "present in the catalogue of translations. HINT : check that \"%s\" " - . "is correctly translated", $description[0])); + $this->assertTrue( + $catalogue->has( + $description[0], + $description[2] ?? 'messages' + ), + sprintf('Test that the message returned by getDescriptionAction is ' + . 'present in the catalogue of translations. HINT : check that "%s" ' + . 'is correctly translated', $description[0]) + ); } - + } + + public function testGetTitle() + { + $title = $this->getFilter()->getTitle(); + + $this->assertInternalType('string', $title); + $this->assertNotEmpty( + $title, + 'test that the title is not empty' + ); } } diff --git a/src/Bundle/ChillMainBundle/Test/PrepareCenterTrait.php b/src/Bundle/ChillMainBundle/Test/PrepareCenterTrait.php index 39d530a29..9e4d6ea68 100644 --- a/src/Bundle/ChillMainBundle/Test/PrepareCenterTrait.php +++ b/src/Bundle/ChillMainBundle/Test/PrepareCenterTrait.php @@ -1,59 +1,47 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Test; - /** - * A trait to prepare center - * + * A trait to prepare center. + * * **Usage :** You must set up trait with `setUpTrait` before use * and use tearDownTrait after usage. * - * @author Julien Fastré * @codeCoverageIgnore */ trait PrepareCenterTrait { - private $centerProphet; - + /** - * prepare a mocked center, with and id and name given - * + * prepare a mocked center, with and id and name given. + * * @param int $id * @param string $name - * @return \Chill\MainBundle\Entity\Center + * + * @return \Chill\MainBundle\Entity\Center */ protected function prepareCenter($id, $name) { - - if ($this->centerProphet === NULL) { + if (null === $this->centerProphet) { $this->centerProphet = new \Prophecy\Prophet(); } - + $center = $this->centerProphet->prophesize(); $center->willExtend('\Chill\MainBundle\Entity\Center'); $center->getId()->willReturn($id); $center->getName()->willReturn($name); - + return $center->reveal(); } - } diff --git a/src/Bundle/ChillMainBundle/Test/PrepareCircleTrait.php b/src/Bundle/ChillMainBundle/Test/PrepareCircleTrait.php index df150e556..3aa3ebd11 100644 --- a/src/Bundle/ChillMainBundle/Test/PrepareCircleTrait.php +++ b/src/Bundle/ChillMainBundle/Test/PrepareCircleTrait.php @@ -1,57 +1,47 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Test; /** - * A trait to prepare center - * + * A trait to prepare center. + * * **Usage :** You must set up trait with `setUpTrait` before use * and use tearDownTrait after usage. - * * - * @author Julien Fastré * @codeCoverageIgnore */ trait PrepareCircleTrait { private $circleProphet; - + /** - * prepare a mocked center, with and id and name given - * + * prepare a mocked center, with and id and name given. + * * @param int $id * @param string $name - * @return \Chill\MainBundle\Entity\Center + * + * @return \Chill\MainBundle\Entity\Center */ protected function prepareCircle($id, $name) { - - if ($this->circleProphet === NULL) { + if (null === $this->circleProphet) { $this->circleProphet = new \Prophecy\Prophet(); } - + $scope = $this->circleProphet->prophesize(); $scope->willExtend('\Chill\MainBundle\Entity\Scope'); $scope->getId()->willReturn($id); $scope->getName()->willReturn($name); - + return $scope->reveal(); } } diff --git a/src/Bundle/ChillMainBundle/Test/PrepareClientTrait.php b/src/Bundle/ChillMainBundle/Test/PrepareClientTrait.php index 5df0b57dd..213eaed60 100644 --- a/src/Bundle/ChillMainBundle/Test/PrepareClientTrait.php +++ b/src/Bundle/ChillMainBundle/Test/PrepareClientTrait.php @@ -1,27 +1,22 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Test; +use LogicException; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** - * Prepare a client authenticated with a user + * Prepare a client authenticated with a user. */ trait PrepareClientTrait { @@ -30,21 +25,21 @@ trait PrepareClientTrait * * @param string $username the username (default 'center a_social') * @param string $password the password (default 'password') - * @return \Symfony\Component\BrowserKit\Client - * @throws \LogicException + * + * @throws LogicException */ public function getClientAuthenticated( $username = 'center a_social', $password = 'password' ): KernelBrowser { if (!$this instanceof WebTestCase) { - throw new \LogicException(sprintf("The current class does not " - . "implements %s", WebTestCase::class)); + throw new LogicException(sprintf('The current class does not ' + . 'implements %s', WebTestCase::class)); } - return static::createClient(array(), array( - 'PHP_AUTH_USER' => $username, - 'PHP_AUTH_PW' => $password, - )); + return static::createClient([], [ + 'PHP_AUTH_USER' => $username, + 'PHP_AUTH_PW' => $password, + ]); } } diff --git a/src/Bundle/ChillMainBundle/Test/PrepareScopeTrait.php b/src/Bundle/ChillMainBundle/Test/PrepareScopeTrait.php index 01de3c0f6..286322f5e 100644 --- a/src/Bundle/ChillMainBundle/Test/PrepareScopeTrait.php +++ b/src/Bundle/ChillMainBundle/Test/PrepareScopeTrait.php @@ -1,45 +1,38 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Test; /** - * A trait to prepare center - * + * A trait to prepare center. + * * **Usage :** You must set up trait with `setUpTrait` before use * and use tearDownTrait after usage. - * * - * @author Julien Fastré * @codeCoverageIgnore + * * @deprecated use PrepareCircleTrait instead */ trait PrepareScopeTrait { use PrepareCircleTrait; - + /** - * prepare a mocked center, with and id and name given - * + * prepare a mocked center, with and id and name given. + * * @param int $id * @param string $name - * @return \Chill\MainBundle\Entity\Center + * + * @return \Chill\MainBundle\Entity\Center + * * @deprecated */ protected function prepareScope($id, $name) diff --git a/src/Bundle/ChillMainBundle/Test/PrepareUserTrait.php b/src/Bundle/ChillMainBundle/Test/PrepareUserTrait.php index 5eccf2770..662438a98 100644 --- a/src/Bundle/ChillMainBundle/Test/PrepareUserTrait.php +++ b/src/Bundle/ChillMainBundle/Test/PrepareUserTrait.php @@ -1,46 +1,36 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Test; -use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\GroupCenter; -use Chill\MainBundle\Entity\RoleScope; use Chill\MainBundle\Entity\PermissionsGroup; +use Chill\MainBundle\Entity\RoleScope; +use Chill\MainBundle\Entity\User; +use LogicException; /** - * A trait to prepare user with permission. May be used + * A trait to prepare user with permission. May be used * within tests. - * + * * **Usage : ** You must set up trait with `setUpTrait` before use * and use tearDownTrait after usage. - * * - * @author Julien Fastré * @codeCoverageIgnore */ trait PrepareUserTrait { - /** - * prepare a user with correct permissions - * + * prepare a user with correct permissions. + * * Example of permissions: * ``` * array( @@ -52,38 +42,36 @@ trait PrepareUserTrait * ) * ) * ``` - * Scope must be an int. Scope created have this int as id, and the + * Scope must be an int. Scope created have this int as id, and the * int converted to string as name. - * - * + * * @param array $permissions an array of permissions, with key 'center' for the center and key 'attrs' for an array of ['role' => (string), 'scope' => (int)] + * + * @throws LogicException if the trait is not set up + * * @return User - * @throws \LogicException if the trait is not set up */ protected function prepareUser(array $permissions) { $user = new User(); - + foreach ($permissions as $permission) { $groupCenter = (new GroupCenter()) - ->setCenter($permission['center']); + ->setCenter($permission['center']); $permissionGroup = new PermissionsGroup(); - + foreach ($permission['permissionsGroup'] as $pg) { - $roleScope = (new RoleScope()) - ->setRole($pg['role']) - ->setScope($pg['scope']); - ; + ->setRole($pg['role']) + ->setScope($pg['scope']); + $permissionGroup->addRoleScope($roleScope); - } - + $groupCenter->setPermissionsGroup($permissionGroup); $user->addGroupCenter($groupCenter); - - } - + } + return $user; } } diff --git a/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php b/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php index d471dd624..a25624719 100644 --- a/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php +++ b/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php @@ -1,53 +1,40 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Test; /** - * a trait to prepare prophecy - * + * a trait to prepare prophecy. + * * **Usage : ** You must set up trait with `setUpTrait` before use * and use tearDownTrait after usage. * - * @author Julien Fastré * @codeCoverageIgnore */ trait ProphecyTrait { - /** - * * @var \Prophecy\Prophet() */ private $prophet; - + /** - * * @return \Prophecy\Prophet */ public function getProphet() { - if ($this->prophet === NULL) { + if (null === $this->prophet) { $this->prophet = new \Prophecy\Prophet(); } - + return $this->prophet; } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Authorization/ParentRoleHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Authorization/ParentRoleHelperTest.php new file mode 100644 index 000000000..de3b95a31 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Authorization/ParentRoleHelperTest.php @@ -0,0 +1,50 @@ +parentRoleHelper = self::$container->get(ParentRoleHelper::class); + } + + public function testGetReachableRoles() + { + // this test will be valid until the role hierarchy for person is changed. + // this is not perfect but spare us a mock + + $parentRoles = $this->parentRoleHelper->getParentRoles(PersonVoter::SEE); + + $this->assertCount(3, $parentRoles); + $this->assertContains(PersonVoter::CREATE, $parentRoles); + $this->assertContains(PersonVoter::UPDATE, $parentRoles); + $this->assertContains(PersonVoter::SEE, $parentRoles); + } + + public function testIsRoleReached() + { + $this->assertTrue($this->parentRoleHelper->isRoleReached(PersonVoter::SEE, PersonVoter::CREATE)); + $this->assertFalse($this->parentRoleHelper->isRoleReached(PersonVoter::SEE, 'foo')); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/AddressControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/AddressControllerTest.php index 2000a05bf..b36a655c5 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/AddressControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/AddressControllerTest.php @@ -1,36 +1,37 @@ client = $this->getClientAuthenticated(); } - /** - * @dataProvider generateAddressIds - * @param int $addressId - */ - public function testDuplicate(int $addressId) - { - $this->client->request('POST', "/api/1.0/main/address/$addressId/duplicate.json"); - - $this->assertResponseIsSuccessful('test that duplicate is successful'); - } - public function generateAddressIds() { self::bootKernel(); @@ -43,7 +44,17 @@ class AddressControllerTest extends \Symfony\Bundle\FrameworkBundle\Test\WebTest ->getResult(); foreach ($addresses as $a) { - yield [ $a->getId() ]; + yield [$a->getId()]; } } + + /** + * @dataProvider generateAddressIds + */ + public function testDuplicate(int $addressId) + { + $this->client->request('POST', "/api/1.0/main/address/{$addressId}/duplicate.json"); + + $this->assertResponseIsSuccessful('test that duplicate is successful'); + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/AddressReferenceApiControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/AddressReferenceApiControllerTest.php new file mode 100644 index 000000000..b62d56b0d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Controller/AddressReferenceApiControllerTest.php @@ -0,0 +1,67 @@ +get(EntityManagerInterface::class); + + $postalCode = $em->createQueryBuilder() + ->select('pc') + ->from(PostalCode::class, 'pc') + ->where('pc.origin = :origin') + ->setParameter('origin', 0) + ->setMaxResults(1) + ->getQuery() + ->getSingleResult(); + + yield [$postalCode->getId(), 'rue']; + } + + /** + * @dataProvider provideData + */ + public function testSearch(int $postCodeId, string $pattern) + { + $client = $this->getClientAuthenticated(); + + $client->request( + 'GET', + "/api/1.0/main/address-reference/by-postal-code/{$postCodeId}/search.json", + ['q' => $pattern] + ); + + $this->assertResponseIsSuccessful(); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/CenterControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/CenterControllerTest.php index ede351e8b..981bb3094 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/CenterControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/CenterControllerTest.php @@ -1,52 +1,73 @@ 'admin', - 'PHP_AUTH_PW' => 'password', - 'HTTP_ACCEPT_LANGUAGE' => 'fr_FR' - )); + $client = self::createClient([], [ + 'PHP_AUTH_USER' => 'admin', + 'PHP_AUTH_PW' => 'password', + 'HTTP_ACCEPT_LANGUAGE' => 'fr_FR', + ]); // Create a new entry in the database $crawler = $client->request('GET', '/fr/admin/center/'); - $this->assertEquals(200, $client->getResponse()->getStatusCode(), - "Unexpected HTTP status code for GET /fr/admin/center/"); + $this->assertEquals( + 200, + $client->getResponse()->getStatusCode(), + 'Unexpected HTTP status code for GET /fr/admin/center/' + ); $crawler = $client->click($crawler->selectLink('Créer un nouveau centre')->link()); // Fill in the form and submit it - $form = $crawler->selectButton('Créer')->form(array( - 'chill_mainbundle_center[name]' => 'Test center', - )); + $form = $crawler->selectButton('Créer')->form([ + 'chill_mainbundle_center[name]' => 'Test center', + ]); $client->submit($form); $crawler = $client->followRedirect(); // Check data in the show view - $this->assertGreaterThan(0, - $crawler->filter('td:contains("Test center")')->count(), - 'Missing element td:contains("Test center")'); + $this->assertGreaterThan( + 0, + $crawler->filter('td:contains("Test center")')->count(), + 'Missing element td:contains("Test center")' + ); // Edit the entity $crawler = $client->click($crawler->selectLink('Modifier')->link()); - $form = $crawler->selectButton('Mettre à jour')->form(array( - 'chill_mainbundle_center[name]' => 'Foo', - )); + $form = $crawler->selectButton('Mettre à jour')->form([ + 'chill_mainbundle_center[name]' => 'Foo', + ]); $client->submit($form); $crawler = $client->followRedirect(); // Check the element contains an attribute with value equals "Foo" - $this->assertGreaterThan(0, $crawler->filter('[value="Foo"]')->count(), - 'Missing element [value="Foo"]'); + $this->assertGreaterThan( + 0, + $crawler->filter('[value="Foo"]')->count(), + 'Missing element [value="Foo"]' + ); $crawler = $client->request('GET', '/fr/admin/center/'); diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php index d75f2cc71..33dbcc4ee 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php @@ -1,45 +1,39 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** - * Test the export + * Test the export. * - * @author Julien Fastré + * @internal + * @coversNothing */ -class ExportControllerTest extends WebTestCase +final class ExportControllerTest extends WebTestCase { public function testIndex() { - $client = static::createClient(array(), array( - 'PHP_AUTH_USER' => 'center a_social', - 'PHP_AUTH_PW' => 'password', - 'HTTP_ACCEPT_LANGUAGE' => 'fr_FR' - )); - + $client = self::createClient([], [ + 'PHP_AUTH_USER' => 'center a_social', + 'PHP_AUTH_PW' => 'password', + 'HTTP_ACCEPT_LANGUAGE' => 'fr_FR', + ]); + $client->request('GET', '/fr/exports/'); - $this->assertTrue($client->getResponse()->isSuccessful(), - "assert the list is shown"); + $this->assertTrue( + $client->getResponse()->isSuccessful(), + 'assert the list is shown' + ); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/LoginControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/LoginControllerTest.php index a621b897e..bed40b372 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/LoginControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/LoginControllerTest.php @@ -1,60 +1,70 @@ request('GET', '/login'); $this->assertTrue($client->getResponse()->isSuccessful()); - + $buttonCrawlerNode = $crawler->selectButton('login'); $form = $buttonCrawlerNode->form(); - - $client->submit($form, array( - '_username' => 'center a_social', - '_password' => 'password' - )); - + + $client->submit($form, [ + '_username' => 'center a_social', + '_password' => 'password', + ]); + //the response is a redirection $this->assertTrue($client->getResponse()->isRedirect()); - + //the response is not a login page, but on a new page $this->assertNotRegExp('/\/login$/', $client->getResponse() - ->headers - ->get('location')); - + ->headers + ->get('location')); + //on the home page, there must be a logout link $client->followRedirects(true); $crawler = $client->request('GET', '/'); - + $this->assertRegExp('/center a_social/', $client->getResponse() - ->getContent()); + ->getContent()); $logoutLinkFilter = $crawler->filter('a:contains("Se déconnecter")'); - + //check there is > 0 logout link $this->assertGreaterThan(0, $logoutLinkFilter->count(), 'check that a logout link is present'); - + //click on logout link $client->followRedirects(false); $client->click($crawler->selectLink('Se déconnecter')->link()); - + $this->assertTrue($client->getResponse()->isRedirect()); - $client->followRedirect(); #redirect to login page - + $client->followRedirect(); //redirect to login page + //check we are back on login page $this->assertRegExp('/\/login$/', $client->getResponse() - ->headers - ->get('location')); - - + ->headers + ->get('location')); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/PermissionApiControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/PermissionApiControllerTest.php new file mode 100644 index 000000000..aec392045 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Controller/PermissionApiControllerTest.php @@ -0,0 +1,74 @@ +getClientAuthenticated(); + + $client->request( + 'POST', + '/api/1.0/main/permissions/info.json', + [], // parameters + [], // files + [], // server + json_encode([ + 'object' => [ + 'datetime' => '1969-07-09T00:00:00+0100', + ], + 'class' => DateTime::class, + 'roles' => ['FOO_ROLE'], + ]) + ); + + $this->assertResponseIsSuccessful(); + + $data = json_decode($client->getResponse()->getContent(), true); + $this->assertFalse($data['roles']['FOO_ROLE']); + } + + public function testNullObject() + { + $client = $this->getClientAuthenticated(); + + $client->request( + 'POST', + '/api/1.0/main/permissions/info.json', + [], // parameters + [], // files + [], // server + json_encode([ + 'object' => null, + 'class' => null, + 'roles' => ['ROLE_USER', 'ROLE_ADMIN'], + ]) + ); + + $this->assertResponseIsSuccessful(); + + $data = json_decode($client->getResponse()->getContent(), true); + $this->assertTrue($data['roles']['ROLE_USER']); + $this->assertFalse($data['roles']['ROLE_ADMIN']); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/PermissionsGroupControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/PermissionsGroupControllerTest.php index 9657c4800..f27eaa7a7 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/PermissionsGroupControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/PermissionsGroupControllerTest.php @@ -1,15 +1,29 @@ markTestSkipped(); } + /* public function testCompleteScenario() { @@ -55,5 +69,5 @@ class PermissionsGroupControllerTest extends WebTestCase $this->assertNotRegExp('/Foo/', $client->getResponse()->getContent()); } - */ + */ } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/PostalCodeApiControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/PostalCodeApiControllerTest.php new file mode 100644 index 000000000..5df1e1290 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Controller/PostalCodeApiControllerTest.php @@ -0,0 +1,57 @@ +getClientAuthenticated(); + + $client->request( + 'GET', + '/api/1.0/main/postal-code/search.json', + ['q' => 'fontenay le comte'] + ); + + $this->assertResponseIsSuccessful(); + + $data = json_decode($client->getResponse()->getContent(), true); + + $this->assertEquals('Fontenay Le Comte', $data['results'][0]['name']); + + // test response with invalid search pattern + $client->request( + 'GET', + '/api/1.0/main/postal-code/search.json', + ['q' => ''] + ); + $this->assertResponseStatusCodeSame(400); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/ScopeControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/ScopeControllerTest.php index ad8318002..806153220 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/ScopeControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/ScopeControllerTest.php @@ -1,30 +1,46 @@ 'admin', - 'PHP_AUTH_PW' => 'password', - 'HTTP_ACCEPT_LANGUAGE' => 'fr_FR' - )); + $client = self::createClient([], [ + 'PHP_AUTH_USER' => 'admin', + 'PHP_AUTH_PW' => 'password', + 'HTTP_ACCEPT_LANGUAGE' => 'fr_FR', + ]); // Create a new entry in the database $crawler = $client->request('GET', '/fr/admin/scope/'); - $this->assertEquals(200, $client->getResponse()->getStatusCode(), - "Unexpected HTTP status code for GET /fr/admin/scope/"); + $this->assertEquals( + 200, + $client->getResponse()->getStatusCode(), + 'Unexpected HTTP status code for GET /fr/admin/scope/' + ); $crawler = $client->click($crawler->selectLink('Créer un nouveau cercle')->link()); // Fill in the form and submit it - $form = $crawler->selectButton('Créer')->form(array( - 'chill_mainbundle_scope[name][fr]' => 'Test en fr', - 'chill_mainbundle_scope[name][en]' => 'Test en en' - )); + $form = $crawler->selectButton('Créer')->form([ + 'chill_mainbundle_scope[name][fr]' => 'Test en fr', + 'chill_mainbundle_scope[name][en]' => 'Test en en', + ]); $client->submit($form/*, array( 'chill_mainbundle_scope' => array( @@ -36,25 +52,26 @@ class ScopeControllerTest extends WebTestCase ) )*/); $crawler = $client->followRedirect(); - + // Check data in the show view - $this->assertGreaterThan(0, $crawler->filter('td:contains("Test en fr")')->count(), - 'Missing element td:contains("Test en fr")'); + $this->assertGreaterThan( + 0, + $crawler->filter('td:contains("Test en fr")')->count(), + 'Missing element td:contains("Test en fr")' + ); // Edit the entity $crawler = $client->click($crawler->selectLink('Modifier')->link()); - - $form = $crawler->selectButton('Mettre à jour')->form(array( - 'chill_mainbundle_scope[name][fr]' => 'Foo', - 'chill_mainbundle_scope[name][en]' => 'Foo en', - )); + + $form = $crawler->selectButton('Mettre à jour')->form([ + 'chill_mainbundle_scope[name][fr]' => 'Foo', + 'chill_mainbundle_scope[name][en]' => 'Foo en', + ]); $client->submit($form); $crawler = $client->followRedirect(); // Check the element contains an attribute with value equals "Foo" $this->assertGreaterThan(0, $crawler->filter('[value="Foo"]')->count(), 'Missing element [value="Foo"]'); - } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/SearchApiControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/SearchApiControllerTest.php index 1c76d8975..a2cf41951 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/SearchApiControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/SearchApiControllerTest.php @@ -1,15 +1,39 @@ request( Request::METHOD_GET, '/api/1.0/search.json', - [ 'q' => $pattern, 'type' => $types ] + ['q' => $pattern, 'type' => $types] ); $this->assertResponseIsSuccessful(); } - - public function generateSearchData() - { - yield ['per', ['person', 'thirdparty'] ]; - yield ['per', ['thirdparty'] ]; - yield ['per', ['person'] ]; - yield ['fjklmeqjfkdqjklrmefdqjklm', ['person', 'thirdparty'] ]; - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/SearchControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/SearchControllerTest.php index a78ed1f28..4e101e370 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/SearchControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/SearchControllerTest.php @@ -1,102 +1,100 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -use Chill\MainBundle\Search\SearchInterface; - - /** - * Test the search controller + * Test the search controller. * - * @author Julien Fastré + * @internal + * @coversNothing */ -class SearchControllerTest extends WebTestCase +final class SearchControllerTest extends WebTestCase { - + public function testDomainUnknow() + { + $client = $this->getAuthenticatedClient(); + + $crawler = $client->request('GET', '/fr/search', ['q' => '@unknow domain']); + + $this->assertTrue( + $client->getResponse()->isSuccessful(), + 'The page is loaded without errors' + ); + $this->assertGreaterThan( + 0, + $crawler->filter('*:contains("Le domaine de recherche "unknow" est inconnu.")')->count(), + 'Message domain unknow is shown' + ); + } + + public function testParsingIncorrect() + { + $client = $this->getAuthenticatedClient(); + + $crawler = $client->request( + 'GET', + '/fr/search', + ['q' => '@domaine @domain double domaine'] + ); + + $this->assertGreaterThan(0, $crawler->filter('*:contains("Recherche invalide")') + ->count()); + } + /** - * Test the behaviour when no domain is provided in the search pattern : - * the default search should be enabled + * Test the behaviour when no domain is provided in the search pattern : + * the default search should be enabled. */ public function testSearchPath() { $client = $this->getAuthenticatedClient(); - - $crawler = $client->request('GET', '/fr/search', array('q' => 'default search')); - $this->assertTrue($client->getResponse()->isSuccessful(), - "The page is loaded without errors"); - + $crawler = $client->request('GET', '/fr/search', ['q' => 'default search']); + + $this->assertTrue( + $client->getResponse()->isSuccessful(), + 'The page is loaded without errors' + ); } - + public function testSearchPathEmpty() { $client = $this->getAuthenticatedClient(); - + $crawler = $client->request('GET', '/fr/search?q='); $this->assertGreaterThan(0, $crawler->filter('*:contains("Merci de fournir des termes de recherche.")')->count()); } - - public function testDomainUnknow() - { - $client = $this->getAuthenticatedClient(); - - $crawler = $client->request('GET', '/fr/search', array('q' => '@unknow domain')); - $this->assertTrue($client->getResponse()->isSuccessful(), - "The page is loaded without errors"); - $this->assertGreaterThan(0, $crawler->filter('*:contains("Le domaine de recherche "unknow" est inconnu.")')->count(), - "Message domain unknow is shown"); - - } - - public function testParsingIncorrect() - { - $client = $this->getAuthenticatedClient(); - - $crawler = $client->request('GET', '/fr/search', - array('q' => '@domaine @domain double domaine')); - - $this->assertGreaterThan(0, $crawler->filter('*:contains("Recherche invalide")') - ->count()); - } - public function testUnknowName() { $client = $this->getAuthenticatedClient(); - - $client->request('GET', '/fr/search', - array('q' => 'default search', 'name' => 'unknow')); - + + $client->request( + 'GET', + '/fr/search', + ['q' => 'default search', 'name' => 'unknow'] + ); + $this->assertTrue($client->getResponse()->isNotFound()); } - + private function getAuthenticatedClient() { - return static::createClient(array(), array( - 'PHP_AUTH_USER' => 'center b_social', - 'PHP_AUTH_PW' => 'password', - )); + return self::createClient([], [ + 'PHP_AUTH_USER' => 'center b_social', + 'PHP_AUTH_PW' => 'password', + ]); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/UserApiControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/UserApiControllerTest.php index 85d9895fc..813e9bc40 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/UserApiControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/UserApiControllerTest.php @@ -1,15 +1,44 @@ getClientAuthenticated(); + + $client->request(Request::METHOD_GET, '/api/1.0/main/user/' . $existingUser['id'] . '.json'); + + $this->assertResponseIsSuccessful(); + } + public function testIndex() { $client = $this->getClientAuthenticated(); @@ -18,26 +47,14 @@ class UserApiControllerTest extends WebTestCase $this->assertResponseIsSuccessful(); - $data = \json_decode($client->getResponse()->getContent(), true); - $this->assertTrue(\array_key_exists('count', $data)); + $data = json_decode($client->getResponse()->getContent(), true); + $this->assertTrue(array_key_exists('count', $data)); $this->assertGreaterThan(0, $data['count']); - $this->assertTrue(\array_key_exists('results', $data)); + $this->assertTrue(array_key_exists('results', $data)); return $data['results'][0]; } - /** - * @depends testIndex - */ - public function testEntity($existingUser) - { - $client = $this->getClientAuthenticated(); - - $client->request(Request::METHOD_GET, '/api/1.0/main/user/'.$existingUser['id'].'.json'); - - $this->assertResponseIsSuccessful(); - } - public function testWhoami() { $client = $this->getClientAuthenticated(); diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php index 0017fbb4e..00bec29f6 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php @@ -1,5 +1,14 @@ client = static::createClient(array(), array( - 'PHP_AUTH_USER' => 'admin', - 'PHP_AUTH_PW' => 'password', - 'HTTP_ACCEPT_LANGUAGE' => 'fr_FR' - )); - } - - public function testList() - { - // get the list - $crawler = $this->client->request('GET', '/fr/admin/main/user'); - $this->assertEquals(200, $this->client->getResponse()->getStatusCode(), - "Unexpected HTTP status code for GET /admin/main/user"); - } - - public function testNew() - { - $crawler = $this->client->request('GET', '/fr/admin/main/user/new'); - - $username = 'Test_user'. uniqid(); - $password = 'Password1234!'; - // Fill in the form and submit it - $form = $crawler->selectButton('Créer')->form(array( - 'chill_mainbundle_user[username]' => $username, - 'chill_mainbundle_user[plainPassword][first]' => $password, - 'chill_mainbundle_user[plainPassword][second]' => $password, - 'chill_mainbundle_user[email]' => $username.'@gmail.com', - 'chill_mainbundle_user[label]' => $username, - - )); - - $this->client->submit($form); - $crawler = $this->client->followRedirect(); - - // Check data in the show view - $this->assertGreaterThan(0, $crawler->filter('td:contains("Test_user")')->count(), - 'Missing element td:contains("Test user")'); - - //test the auth of the new client - $this->isPasswordValid($username, $password); - } - - protected function isPasswordValid($username, $password) - { - /* @var $passwordEncoder \Symfony\Component\Security\Core\Encoder\UserPasswordEncoder */ - $passwordEncoder = self::$container - ->get(UserPasswordEncoderInterface::class); - - $user = self::$kernel->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository('ChillMainBundle:User') - ->findOneBy(array('username' => $username)); - - $this->assertTrue($passwordEncoder->isPasswordValid($user, $password)); - } - - /** - * - * @dataProvider dataGenerateUserId - */ - public function testUpdate(int $userId, string $username) - { - $crawler = $this->client->request('GET', "/fr/admin/main/user/$userId/edit"); - - $username = 'Foo bar '.uniqid(); - $form = $crawler->selectButton('Enregistrer & fermer')->form(array( - 'chill_mainbundle_user[username]' => $username, - )); - - $this->client->submit($form); - $crawler = $this->client->followRedirect(); - // Check the element contains an attribute with value equals "Foo" - $this->assertGreaterThan(0, $crawler->filter('[data-username="'.$username.'"]')->count(), - 'Missing element [data-username="Foo bar"]'); - } - - /** - * - * @dataProvider dataGenerateUserId - */ - public function testUpdatePassword(int $userId, $username) - { - $crawler = $this->client->request('GET', "/fr/admin/user/$userId/edit_password"); - $newPassword = '1234Password!'; - - $form = $crawler->selectButton('Changer le mot de passe')->form(array( - 'chill_mainbundle_user_password[new_password][first]' => $newPassword, - 'chill_mainbundle_user_password[new_password][second]' => $newPassword, - )); - - $this->client->submit($form); - - $this->assertTrue($this->client->getResponse()->isRedirect(), - "the response is a redirection"); - - $this->isPasswordValid($username, $newPassword); + $this->client = self::createClient([], [ + 'PHP_AUTH_USER' => 'admin', + 'PHP_AUTH_PW' => 'password', + 'HTTP_ACCEPT_LANGUAGE' => 'fr_FR', + ]); } protected function tearDown() @@ -120,7 +42,7 @@ class UserControllerTest extends WebTestCase self::bootKernel(); $em = self::$container->get(EntityManagerInterface::class); - foreach ($this->toDelete as list($class, $id)) { + foreach ($this->toDelete as [$class, $id]) { $obj = $em->getRepository($class)->find($id); $em->remove($obj); } @@ -134,15 +56,118 @@ class UserControllerTest extends WebTestCase $em = self::$container->get(EntityManagerInterface::class); $user = new User(); - $user->setUsername('Test_user '.uniqid()); - $user->setPassword(self::$container->get(UserPasswordEncoderInterface::class)->encodePassword($user, - 'password')); + $user->setUsername('Test_user ' . uniqid()); + $user->setPassword(self::$container->get(UserPasswordEncoderInterface::class)->encodePassword( + $user, + 'password' + )); $em->persist($user); $em->flush(); $this->toDelete[] = [User::class, $user->getId()]; - yield [ $user->getId(), $user->getUsername() ]; + yield [$user->getId(), $user->getUsername()]; + } + + public function testList() + { + // get the list + $crawler = $this->client->request('GET', '/fr/admin/main/user'); + $this->assertEquals( + 200, + $this->client->getResponse()->getStatusCode(), + 'Unexpected HTTP status code for GET /admin/main/user' + ); + } + + public function testNew() + { + $crawler = $this->client->request('GET', '/fr/admin/main/user/new'); + + $username = 'Test_user' . uniqid(); + $password = 'Password1234!'; + // Fill in the form and submit it + $form = $crawler->selectButton('Créer')->form([ + 'chill_mainbundle_user[username]' => $username, + 'chill_mainbundle_user[plainPassword][first]' => $password, + 'chill_mainbundle_user[plainPassword][second]' => $password, + 'chill_mainbundle_user[email]' => $username . '@gmail.com', + 'chill_mainbundle_user[label]' => $username, + ]); + + $this->client->submit($form); + $crawler = $this->client->followRedirect(); + + // Check data in the show view + $this->assertGreaterThan( + 0, + $crawler->filter('td:contains("Test_user")')->count(), + 'Missing element td:contains("Test user")' + ); + + //test the auth of the new client + $this->isPasswordValid($username, $password); + } + + /** + * @dataProvider dataGenerateUserId + */ + public function testUpdate(int $userId, string $username) + { + $crawler = $this->client->request('GET', "/fr/admin/main/user/{$userId}/edit"); + + $username = 'Foo bar ' . uniqid(); + $form = $crawler->selectButton('Enregistrer & fermer')->form([ + 'chill_mainbundle_user[username]' => $username, + ]); + + $this->client->submit($form); + $crawler = $this->client->followRedirect(); + // Check the element contains an attribute with value equals "Foo" + $this->assertGreaterThan( + 0, + $crawler->filter('[data-username="' . $username . '"]')->count(), + 'Missing element [data-username="Foo bar"]' + ); + } + + /** + * @dataProvider dataGenerateUserId + * + * @param mixed $username + */ + public function testUpdatePassword(int $userId, $username) + { + $crawler = $this->client->request('GET', "/fr/admin/user/{$userId}/edit_password"); + $newPassword = '1234Password!'; + + $form = $crawler->selectButton('Changer le mot de passe')->form([ + 'chill_mainbundle_user_password[new_password][first]' => $newPassword, + 'chill_mainbundle_user_password[new_password][second]' => $newPassword, + ]); + + $this->client->submit($form); + + $this->assertTrue( + $this->client->getResponse()->isRedirect(), + 'the response is a redirection' + ); + + $this->isPasswordValid($username, $newPassword); + } + + protected function isPasswordValid($username, $password) + { + /** @var \Symfony\Component\Security\Core\Encoder\UserPasswordEncoder $passwordEncoder */ + $passwordEncoder = self::$container + ->get(UserPasswordEncoderInterface::class); + + $user = self::$kernel->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository('ChillMainBundle:User') + ->findOneBy(['username' => $username]); + + $this->assertTrue($passwordEncoder->isPasswordValid($user, $password)); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Doctrine/Model/PointTest.php b/src/Bundle/ChillMainBundle/Tests/Doctrine/Model/PointTest.php index 5632bf96a..bcb5b2d26 100644 --- a/src/Bundle/ChillMainBundle/Tests/Doctrine/Model/PointTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Doctrine/Model/PointTest.php @@ -1,49 +1,75 @@ + * @internal + * @coversNothing */ -class ExportControllerTest extends KernelTestCase +final class PointTest extends KernelTestCase { - - public function testToWKT() + public function testFromArrayGeoJson() { + $array = [ + 'type' => 'Point', + 'coordinates' => [4.8634, 50.47382], + ]; $lon = 4.8634; $lat = 50.47382; $point = $this->preparePoint($lon, $lat); - $this->assertEquals($point->toWKT(),'SRID=4326;POINT(4.8634 50.47382)'); + $this->assertEquals($point, Point::fromArrayGeoJson($array)); } - public function testToGeojson() + public function testFromGeoJson() { + $geojson = '{"type":"Point","coordinates":[4.8634,50.47382]}'; $lon = 4.8634; $lat = 50.47382; $point = $this->preparePoint($lon, $lat); - $this->assertEquals($point->toGeoJson(),'{"type":"Point","coordinates":[4.8634,50.47382]}'); + $this->assertEquals($point, Point::fromGeoJson($geojson)); } - public function testToArrayGeoJson() + public function testFromLonLat() { $lon = 4.8634; $lat = 50.47382; $point = $this->preparePoint($lon, $lat); - $this->assertEquals( - $point->toArrayGeoJson(), - [ - 'type' => 'Point', - 'coordinates' => [4.8634, 50.47382] - ] - ); + $this->assertEquals($point, Point::fromLonLat($lon, $lat)); + } + + public function testGetLat() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat); + + $this->assertEquals($lat, $point->getLat()); + } + + public function testGetLon() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat); + + $this->assertEquals($lon, $point->getLon()); } public function testJsonSerialize() @@ -56,64 +82,46 @@ class ExportControllerTest extends KernelTestCase $point->jsonSerialize(), [ 'type' => 'Point', - 'coordinates' => [4.8634, 50.47382] + 'coordinates' => [4.8634, 50.47382], ] ); } - public function testFromGeoJson() + public function testToArrayGeoJson() { - $geojson = '{"type":"Point","coordinates":[4.8634,50.47382]}'; $lon = 4.8634; $lat = 50.47382; - $point = $this->preparePoint($lon, $lat);; + $point = $this->preparePoint($lon, $lat); - $this->assertEquals($point, Point::fromGeoJson($geojson)); + $this->assertEquals( + $point->toArrayGeoJson(), + [ + 'type' => 'Point', + 'coordinates' => [4.8634, 50.47382], + ] + ); } - public function testFromLonLat() + public function testToGeojson() { $lon = 4.8634; $lat = 50.47382; - $point = $this->preparePoint($lon, $lat);; + $point = $this->preparePoint($lon, $lat); - $this->assertEquals($point, Point::fromLonLat($lon, $lat)); + $this->assertEquals($point->toGeoJson(), '{"type":"Point","coordinates":[4.8634,50.47382]}'); } - public function testFromArrayGeoJson() - { - $array = [ - 'type' => 'Point', - 'coordinates' => [4.8634, 50.47382] - ]; - $lon = 4.8634; - $lat = 50.47382; - $point = $this->preparePoint($lon, $lat);; - - $this->assertEquals($point, Point::fromArrayGeoJson($array)); - } - - public function testGetLat() + public function testToWKT() { $lon = 4.8634; $lat = 50.47382; - $point = $this->preparePoint($lon, $lat);; + $point = $this->preparePoint($lon, $lat); - $this->assertEquals($lat, $point->getLat()); - } - - public function testGetLon() - { - $lon = 4.8634; - $lat = 50.47382; - $point = $this->preparePoint($lon, $lat);; - - $this->assertEquals($lon, $point->getLon()); + $this->assertEquals($point->toWKT(), 'SRID=4326;POINT(4.8634 50.47382)'); } private function preparePoint($lon, $lat) { return Point::fromLonLat($lon, $lat); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php index d71997c9e..645dff22e 100644 --- a/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php @@ -1,243 +1,398 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests\Export; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Chill\MainBundle\Export\ExportManager; -use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\Export\AggregatorInterface; -use Chill\MainBundle\Export\FilterInterface; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Chill\MainBundle\Export\ExportInterface; -use Prophecy\Argument; -use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\Export\ExportType; +use Doctrine\ORM\QueryBuilder; +use Prophecy\Argument; +use RuntimeException; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\HttpFoundation\Response; -use Chill\MainBundle\Form\Type\Export\PickCenterType; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Role\Role; +use function count; /** - * Test the export manager - * + * Test the export manager. * - * @author Julien Fastré + * @internal + * @coversNothing */ -class ExportManagerTest extends KernelTestCase +final class ExportManagerTest extends KernelTestCase { - use \Chill\MainBundle\Test\PrepareCenterTrait; - use \Chill\MainBundle\Test\PrepareUserTrait; use \Chill\MainBundle\Test\PrepareScopeTrait; - - + use \Chill\MainBundle\Test\PrepareUserTrait; + /** - * - * @var Prophecy\Prophet + * @var Prophecy\Prophet */ private $prophet; - - - - - + public function setUp() { self::bootKernel(); - - $this->prophet = new \Prophecy\Prophet; + + $this->prophet = new \Prophecy\Prophet(); } - + public function tearDown() { $this->prophet->checkPredictions(); } - - /** - * Create an ExportManager where every element may be replaced by a double. - * - * If null is provided for an element, this is replaced by the equivalent - * from the container; if the user provided is null, this is replaced by the - * user 'center a_social' from database. - * - * @param \Psr\Log\LoggerInterface $logger - * @param \Doctrine\ORM\EntityManagerInterface $em - * @param \Symfony\Component\Security\Core\Authorization\AuthorizationChecker $authorizationChecker - * @param \Chill\MainBundle\Security\Authorization\AuthorizationHelper $authorizationHelper - * @param \Symfony\Component\Security\Core\User\UserInterface $user - * @return ExportManager - */ - protected function createExportManager( - \Psr\Log\LoggerInterface $logger = null, - \Doctrine\ORM\EntityManagerInterface $em = null, - \Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface $authorizationChecker = null, - \Chill\MainBundle\Security\Authorization\AuthorizationHelper $authorizationHelper = null, - \Symfony\Component\Security\Core\User\UserInterface $user = null - ) - { - $localUser = $user === NULL ? self::$container->get('doctrine.orm.entity_manager') - ->getRepository('ChillMainBundle:User') - ->findOneBy(array('username' => 'center a_social')) : - $user; - $token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($localUser, 'password', 'provider'); - $tokenStorage = new \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage(); - $tokenStorage->setToken($token); - - return new ExportManager( - $logger === NULL ? self::$container->get('logger') : $logger, - $em === NULL ? self::$container->get('doctrine.orm.entity_manager') : $em, - $authorizationChecker === NULL ? self::$container->get('security.authorization_checker') : $authorizationChecker, - $authorizationHelper === NULL ? self::$container->get('chill.main.security.authorization.helper') : $authorizationHelper, - $tokenStorage) - ; - } - - - public function testGetExportsWithoutGranting() - { - $exportManager = $this->createExportManager(); - - //create an export and add it to ExportManager - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $exportManager->addExport($export->reveal(), 'dummy'); - - $exports = iterator_to_array($exportManager->getExports(false)); - $this->assertGreaterThan(0, count($exports)); - $this->assertContains($export->reveal(), $exports); - $this->assertContains('dummy', array_keys($exports)); - } - - public function testGetExistingExportsTypes() + public function testAggregatorsApplyingOn() { - $exportManager = $this->createExportManager(); - - //create an export and add it to ExportManager - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $export->getType()->willReturn('my_type'); - $exportManager->addExport($export->reveal(), 'dummy'); - - $this->assertContains('my_type', $exportManager->getExistingExportsTypes()); - - } - - public function testGetExport() - { - $exportManager = $this->createExportManager(); - - //create an export and add it to ExportManager - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $exportManager->addExport($export->reveal(), 'dummy'); - - $obtained = $exportManager->getExport('dummy'); + $center = $this->prepareCenter(100, 'center'); + $centers = [$center]; + $user = $this->prepareUser([]); - $this->assertInstanceof(ExportInterface::class, $obtained); - } - - /** - * @expectedException \RuntimeException - */ - public function testGetExportNonExistant() - { - $exportManager = $this->createExportManager(); - - $exportManager->getExport('non existing'); - } - - public function testGetFilter() - { - $exportManager = $this->createExportManager(); - - //create a filter and add it to ExportManager - $filter = $this->prophet->prophesize(); - $filter->willImplement('Chill\MainBundle\Export\FilterInterface'); - $exportManager->addFilter($filter->reveal(), 'dummy'); - - $obtained = $exportManager->getFilter('dummy'); + $authorizationChecker = $this->prophet->prophesize(); + $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); + $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) + ->willReturn(true); - $this->assertInstanceof('Chill\MainBundle\Export\FilterInterface', $obtained); + $exportManager = $this->createExportManager( + null, + null, + $authorizationChecker->reveal(), + null, + $user + ); + + $exportFooBar = $this->prophet->prophesize(); + $exportFooBar->willImplement(ExportInterface::class); + $exportFooBar->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + $exportFooBar->supportsModifiers()->willReturn(['foo', 'bar']); + + $aggregatorBar = $this->prophet->prophesize(); + $aggregatorBar->willImplement(AggregatorInterface::class); + $aggregatorBar->applyOn()->willReturn('bar'); + $aggregatorBar->addRole()->willReturn(null); + $exportManager->addAggregator($aggregatorBar->reveal(), 'bar'); + + $exportBar = $this->prophet->prophesize(); + $exportBar->willImplement(ExportInterface::class); + $exportBar->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + $exportBar->supportsModifiers()->willReturn(['bar']); + + $aggregatorFoo = $this->prophet->prophesize(); + $aggregatorFoo->willImplement(AggregatorInterface::class); + $aggregatorFoo->applyOn()->willReturn('foo'); + $aggregatorFoo->addRole()->willReturn(null); + $exportManager->addAggregator($aggregatorFoo->reveal(), 'foo'); + + $exportFoo = $this->prophet->prophesize(); + $exportFoo->willImplement(ExportInterface::class); + $exportFoo->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + $exportFoo->supportsModifiers()->willReturn(['foo']); + + $obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFoo->reveal(), $centers)); + $this->assertEquals(1, count($obtained)); + $this->assertContains('foo', array_keys($obtained)); + + $obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportBar->reveal(), $centers)); + $this->assertEquals(1, count($obtained)); + $this->assertContains('bar', array_keys($obtained)); + + $obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFooBar->reveal(), $centers)); + $this->assertEquals(2, count($obtained)); + $this->assertContains('bar', array_keys($obtained)); + $this->assertContains('foo', array_keys($obtained)); + + // test with empty centers + $obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFooBar->reveal(), [])); + $this->assertEquals(0, count($obtained)); } - - - /** - * @expectedException \RuntimeException - */ - public function testGetFilterNonExistant() + + public function testFiltersApplyingOn() { - $exportManager = $this->createExportManager(); - - $exportManager->getFilter('non existing'); - } - - public function testGetFilters() - { - $exportManager = $this->createExportManager(); - - //create three filters and add them to ExportManager - $filterFoo = $this->prophet->prophesize(); - $filterFoo->willImplement('Chill\MainBundle\Export\FilterInterface'); + $center = $this->prepareCenter(100, 'center'); + $centers = [$center]; + $user = $this->prepareUser([]); + + $authorizationChecker = $this->prophet->prophesize(); + $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); + $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) + ->willReturn(true); + + $exportManager = $this->createExportManager( + null, + null, + $authorizationChecker->reveal(), + null, + $user + ); + + $exportFooBar = $this->prophet->prophesize(); + $exportFooBar->willImplement(ExportInterface::class); + $exportFooBar->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + $exportFooBar->supportsModifiers()->willReturn(['foo', 'bar']); + $filterBar = $this->prophet->prophesize(); - $filterBar->willImplement('Chill\MainBundle\Export\FilterInterface'); - $filterFooBar = $this->prophet->prophesize(); - $filterFooBar->willImplement('Chill\MainBundle\Export\FilterInterface'); - $exportManager->addFilter($filterFoo->reveal(), 'foo'); + $filterBar->willImplement(FilterInterface::class); + $filterBar->applyOn()->willReturn('bar'); + $filterBar->addRole()->willReturn(null); + $filterBar->describeAction(Argument::cetera())->willReturn('string'); $exportManager->addFilter($filterBar->reveal(), 'bar'); - $exportManager->addFilter($filterFooBar->reveal(), 'foobar'); - - $obtained = iterator_to_array($exportManager->getFilters(array('foo', 'bar'))); - $this->assertContains($filterBar->reveal(), $obtained); - $this->assertContains($filterFoo->reveal(), $obtained); - $this->assertNotContains($filterFooBar->reveal(), $obtained); + $exportBar = $this->prophet->prophesize(); + $exportBar->willImplement(ExportInterface::class); + $exportBar->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + $exportBar->supportsModifiers()->willReturn(['bar']); + + $filterFoo = $this->prophet->prophesize(); + $filterFoo->willImplement(FilterInterface::class); + $filterFoo->applyOn()->willReturn('foo'); + $filterFoo->addRole()->willReturn(null); + $filterFoo->describeAction(Argument::cetera())->willReturn('string'); + $exportManager->addFilter($filterFoo->reveal(), 'foo'); + + $exportFoo = $this->prophet->prophesize(); + $exportFoo->willImplement(ExportInterface::class); + $exportFoo->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + $exportFoo->supportsModifiers()->willReturn(['foo']); + + $obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFoo->reveal(), $centers)); + $this->assertEquals(1, count($obtained)); + $this->assertContains('foo', array_keys($obtained)); + + $obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportBar->reveal(), $centers)); + $this->assertEquals(1, count($obtained)); + $this->assertContains('bar', array_keys($obtained)); + + $obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFooBar->reveal(), $centers)); + $this->assertEquals(2, count($obtained)); + $this->assertContains('bar', array_keys($obtained)); + $this->assertContains('foo', array_keys($obtained)); + + $obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFooBar->reveal(), [])); + $this->assertEquals(0, count($obtained)); } - + + public function testFormattersByTypes() + { + $exportManager = $this->createExportManager(); + + //create a formatter + $formatterFoo = $this->prophet->prophesize(); + $formatterFoo->willImplement('Chill\MainBundle\Export\FormatterInterface'); + $formatterFoo->getType()->willReturn('foo'); + $formatterBar = $this->prophet->prophesize(); + $formatterBar->willImplement('Chill\MainBundle\Export\FormatterInterface'); + $formatterBar->getType()->willReturn('bar'); + $exportManager->addFormatter($formatterFoo->reveal(), 'foo'); + $exportManager->addFormatter($formatterBar->reveal(), 'bar'); + + $obtained = $exportManager->getFormattersByTypes(['foo']); + + $this->assertContains($formatterFoo->reveal(), $obtained); + $this->assertNotContains($formatterBar->reveal(), $obtained); + } + + /** + * Test the generation of an export. + */ + public function testGenerate() + { + $center = $this->prepareCenter(100, 'center'); + $user = $this->prepareUser([]); + + $authorizationChecker = $this->prophet->prophesize(); + $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); + $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) + ->willReturn(true); + + $exportManager = $this->createExportManager( + null, + null, + $authorizationChecker->reveal(), + null, + $user + ); + + $export = $this->prophet->prophesize(); + $export->willImplement(ExportInterface::class); + $em = self::$container->get('doctrine.orm.entity_manager'); + $export->initiateQuery( + Argument::is(['foo']), + Argument::Type('array'), + Argument::is(['a' => 'b']) + ) + ->will(static function () use ($em) { + $qb = $em->createQueryBuilder(); + + return $qb->addSelect('COUNT(user.id) as export') + ->from('ChillMainBundle:User', 'user'); + }); + //$export->initiateQuery()->shouldBeCalled(); + $export->supportsModifiers()->willReturn(['foo']); + $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + $export->getResult(Argument::Type(QueryBuilder::class), Argument::Type('array'))->willReturn([ + [ + 'aggregator' => 'cat a', + 'export' => 0, + ], + [ + 'aggregator' => 'cat b', + 'export' => 1, + ], + ]); + $export->getLabels( + Argument::is('export'), + Argument::is([0, 1]), + Argument::Type('array') + ) + ->willReturn(static function ($value) { + switch ($value) { + case 0: + case 1: + return $value; + + case '_header': + return 'export'; + + default: throw new RuntimeException(sprintf('The value %s is not valid', $value)); + } + }); + + $export->getQueryKeys(Argument::Type('array'))->willReturn(['export']); + $export->getTitle()->willReturn('dummy title'); + $exportManager->addExport($export->reveal(), 'dummy'); + + $filter = $this->prophet->prophesize(); + $filter->willImplement(FilterInterface::class); + //$filter->alterQuery()->shouldBeCalled(); + $filter->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) + ->willReturn(null); + $filter->addRole()->shouldBeCalled(); + //$filter->addRole()->shouldBeCalled(); + $filter->applyOn()->willReturn('foo'); + $filter->describeAction(Argument::cetera())->willReturn('filtered string'); + $exportManager->addFilter($filter->reveal(), 'filter_foo'); + + $aggregator = $this->prophet->prophesize(); + $aggregator->willImplement(AggregatorInterface::class); + $aggregator->applyOn()->willReturn('foo'); + $aggregator->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) + ->willReturn(null); + //$aggregator->alterQuery()->shouldBeCalled(); + $aggregator->getQueryKeys(Argument::Type('array'))->willReturn(['aggregator']); + $aggregator->getLabels( + Argument::is('aggregator'), + Argument::is(['cat a', 'cat b']), + Argument::is([]) + ) + ->willReturn(static function ($value) { + switch ($value) { + case '_header': return 'foo_header'; + + case 'cat a': return 'label cat a'; + + case 'cat b': return 'label cat b'; + + default: + throw new RuntimeException(sprintf('This value (%s) is not valid', $value)); + } + }); + $aggregator->addRole()->willReturn(null); + //$aggregator->addRole()->shouldBeCalled(); + $exportManager->addAggregator($aggregator->reveal(), 'aggregator_foo'); + + //add formatter interface + $formatter = new \Chill\MainBundle\Export\Formatter\SpreadSheetFormatter( + self::$container->get('translator'), + $exportManager + ); + $exportManager->addFormatter($formatter, 'spreadsheet'); + + //ob_start(); + $response = $exportManager->generate( + 'dummy', + [$center], + [ + ExportType::FILTER_KEY => [ + 'filter_foo' => [ + 'enabled' => true, + 'form' => [], + ], + ], + ExportType::AGGREGATOR_KEY => [ + 'aggregator_foo' => [ + 'enabled' => true, + 'form' => [], + ], + ], + ExportType::PICK_FORMATTER_KEY => [ + 'alias' => 'spreadsheet', + ], + ExportType::EXPORT_KEY => [ + 'a' => 'b', + ], + ], + [ + 'format' => 'csv', + 'aggregator_foo' => [ + 'order' => 1, + ], + ] + ); + //$content = ob_get_clean(); + $this->assertInstanceOf(Response::class, $response); + $expected = <<<'EOT' + "dummy title","" + "","" + "filtered string","" + "foo_header","_header" + "label cat a","" + "label cat b","1" + + EOT; + + $this->assertEquals($expected, $response->getContent()); + } + public function testGetAggregator() { $exportManager = $this->createExportManager(); - + //create a filter and add it to ExportManager $agg = $this->prophet->prophesize(); $agg->willImplement('Chill\MainBundle\Export\AggregatorInterface'); $exportManager->addAggregator($agg->reveal(), 'dummy'); - + $obtained = $exportManager->getAggregator('dummy'); $this->assertInstanceof('Chill\MainBundle\Export\AggregatorInterface', $obtained); } - - + /** * @expectedException \RuntimeException */ public function testGetAggregatorNonExistant() { $exportManager = $this->createExportManager(); - + $exportManager->getAggregator('non existing'); } - + public function testGetAggregators() { $exportManager = $this->createExportManager(); - + //create three filters and add them to ExportManager $aggFoo = $this->prophet->prophesize(); $aggFoo->willImplement('Chill\MainBundle\Export\AggregatorInterface'); @@ -248,425 +403,289 @@ class ExportManagerTest extends KernelTestCase $exportManager->addAggregator($aggFoo->reveal(), 'foo'); $exportManager->addAggregator($aggBar->reveal(), 'bar'); $exportManager->addAggregator($aggFooBar->reveal(), 'foobar'); - - $obtained = iterator_to_array($exportManager->getAggregators(array('foo', 'bar'))); + + $obtained = iterator_to_array($exportManager->getAggregators(['foo', 'bar'])); $this->assertContains($aggBar->reveal(), $obtained); $this->assertContains($aggFoo->reveal(), $obtained); $this->assertNotContains($aggFooBar->reveal(), $obtained); } - + + public function testGetExistingExportsTypes() + { + $exportManager = $this->createExportManager(); + + //create an export and add it to ExportManager + $export = $this->prophet->prophesize(); + $export->willImplement(ExportInterface::class); + $export->getType()->willReturn('my_type'); + $exportManager->addExport($export->reveal(), 'dummy'); + + $this->assertContains('my_type', $exportManager->getExistingExportsTypes()); + } + + public function testGetExport() + { + $exportManager = $this->createExportManager(); + + //create an export and add it to ExportManager + $export = $this->prophet->prophesize(); + $export->willImplement(ExportInterface::class); + $exportManager->addExport($export->reveal(), 'dummy'); + + $obtained = $exportManager->getExport('dummy'); + + $this->assertInstanceof(ExportInterface::class, $obtained); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetExportNonExistant() + { + $exportManager = $this->createExportManager(); + + $exportManager->getExport('non existing'); + } + + public function testGetExportsWithoutGranting() + { + $exportManager = $this->createExportManager(); + + //create an export and add it to ExportManager + $export = $this->prophet->prophesize(); + $export->willImplement(ExportInterface::class); + $exportManager->addExport($export->reveal(), 'dummy'); + + $exports = iterator_to_array($exportManager->getExports(false)); + + $this->assertGreaterThan(0, count($exports)); + $this->assertContains($export->reveal(), $exports); + $this->assertContains('dummy', array_keys($exports)); + } + + public function testGetFilter() + { + $exportManager = $this->createExportManager(); + + //create a filter and add it to ExportManager + $filter = $this->prophet->prophesize(); + $filter->willImplement('Chill\MainBundle\Export\FilterInterface'); + $exportManager->addFilter($filter->reveal(), 'dummy'); + + $obtained = $exportManager->getFilter('dummy'); + + $this->assertInstanceof('Chill\MainBundle\Export\FilterInterface', $obtained); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetFilterNonExistant() + { + $exportManager = $this->createExportManager(); + + $exportManager->getFilter('non existing'); + } + + public function testGetFilters() + { + $exportManager = $this->createExportManager(); + + //create three filters and add them to ExportManager + $filterFoo = $this->prophet->prophesize(); + $filterFoo->willImplement('Chill\MainBundle\Export\FilterInterface'); + $filterBar = $this->prophet->prophesize(); + $filterBar->willImplement('Chill\MainBundle\Export\FilterInterface'); + $filterFooBar = $this->prophet->prophesize(); + $filterFooBar->willImplement('Chill\MainBundle\Export\FilterInterface'); + $exportManager->addFilter($filterFoo->reveal(), 'foo'); + $exportManager->addFilter($filterBar->reveal(), 'bar'); + $exportManager->addFilter($filterFooBar->reveal(), 'foobar'); + + $obtained = iterator_to_array($exportManager->getFilters(['foo', 'bar'])); + + $this->assertContains($filterBar->reveal(), $obtained); + $this->assertContains($filterFoo->reveal(), $obtained); + $this->assertNotContains($filterFooBar->reveal(), $obtained); + } + public function testGetFormatter() { $exportManager = $this->createExportManager(); - + //create a formatter $formatter = $this->prophet->prophesize(); $formatter->willImplement('Chill\MainBundle\Export\FormatterInterface'); $exportManager->addFormatter($formatter->reveal(), 'dummy'); - + $obtained = $exportManager->getFormatter('dummy'); - + $this->assertInstanceOf('Chill\MainBundle\Export\FormatterInterface', $obtained); } - + + public function testIsGrantedForElementWithExportAndUserIsGranted() + { + $center = $this->prepareCenter(100, 'center A'); + $user = $this->prepareUser([]); + + $authorizationChecker = $this->prophet->prophesize(); + $authorizationChecker->willImplement('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'); + $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) + ->willReturn(true); + + $exportManager = $this->createExportManager( + null, + null, + $authorizationChecker->reveal(), + null, + $user + ); + + $export = $this->prophet->prophesize(); + $export->willImplement(ExportInterface::class); + $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + + $result = $exportManager->isGrantedForElement($export->reveal(), null, [$center]); + + $this->assertTrue($result); + } + + public function testIsGrantedForElementWithExportAndUserIsGrantedNotForAllCenters() + { + $center = $this->prepareCenter(100, 'center A'); + $centerB = $this->prepareCenter(102, 'center B'); + $user = $this->prepareUser([]); + + $authorizationChecker = $this->prophet->prophesize(); + $authorizationChecker->willImplement('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'); + $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) + ->willReturn(true); + $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $centerB) + ->willReturn(false); + + $exportManager = $this->createExportManager( + null, + null, + $authorizationChecker->reveal(), + null, + $user + ); + + $export = $this->prophet->prophesize(); + $export->willImplement(ExportInterface::class); + $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + + $result = $exportManager->isGrantedForElement($export->reveal(), null, [$center, $centerB]); + + $this->assertFalse($result); + } + + public function testIsGrantedForElementWithExportEmptyCenters() + { + $user = $this->prepareUser([]); + + $exportManager = $this->createExportManager( + null, + null, + null, + null, + $user + ); + + $export = $this->prophet->prophesize(); + $export->willImplement(\Chill\MainBundle\Export\ExportInterface::class); + $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + + $result = $exportManager->isGrantedForElement($export->reveal(), null, []); + + $this->assertFalse($result); + } + + public function testIsGrantedForElementWithModifierFallbackToExport() + { + $center = $this->prepareCenter(100, 'center A'); + $centerB = $this->prepareCenter(102, 'center B'); + $user = $this->prepareUser([]); + + $authorizationChecker = $this->prophet->prophesize(); + $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); + $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) + ->willReturn(true); + $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $centerB) + ->willReturn(false); + + $exportManager = $this->createExportManager( + null, + null, + $authorizationChecker->reveal(), + null, + $user + ); + + $modifier = $this->prophet->prophesize(); + $modifier->willImplement(\Chill\MainBundle\Export\ModifierInterface::class); + $modifier->addRole()->willReturn(null); + + $export = $this->prophet->prophesize(); + $export->willImplement(ExportInterface::class); + $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); + + $result = $exportManager->isGrantedForElement( + $modifier->reveal(), + $export->reveal(), + [$center, $centerB] + ); + + $this->assertFalse($result); + } + /** * @expectedException \RuntimeException */ public function testNonExistingFormatter() { $exportManager = $this->createExportManager(); - + $exportManager->getFormatter('non existing'); } - - public function testFormattersByTypes() - { - $exportManager = $this->createExportManager(); - - //create a formatter - $formatterFoo = $this->prophet->prophesize(); - $formatterFoo->willImplement('Chill\MainBundle\Export\FormatterInterface'); - $formatterFoo->getType()->willReturn('foo'); - $formatterBar = $this->prophet->prophesize(); - $formatterBar->willImplement('Chill\MainBundle\Export\FormatterInterface'); - $formatterBar->getType()->willReturn('bar'); - $exportManager->addFormatter($formatterFoo->reveal(), 'foo'); - $exportManager->addFormatter($formatterBar->reveal(), 'bar'); - - $obtained = $exportManager->getFormattersByTypes(array('foo')); - - $this->assertContains($formatterFoo->reveal(), $obtained); - $this->assertNotContains($formatterBar->reveal(), $obtained); - } - - public function testIsGrantedForElementWithExportAndUserIsGranted() - { - $center = $this->prepareCenter(100, 'center A'); - $user = $this->prepareUser(array()); - - $authorizationChecker = $this->prophet->prophesize(); - $authorizationChecker->willImplement('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) - ->willReturn(True); - - $exportManager = $this->createExportManager(null, null, - $authorizationChecker->reveal(), null, $user); - - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - - $result = $exportManager->isGrantedForElement($export->reveal(), null, array($center)); - - $this->assertTrue($result); - - } - - public function testIsGrantedForElementWithExportAndUserIsGrantedNotForAllCenters() - { - $center = $this->prepareCenter(100, 'center A'); - $centerB = $this->prepareCenter(102, 'center B'); - $user = $this->prepareUser(array()); - - $authorizationChecker = $this->prophet->prophesize(); - $authorizationChecker->willImplement('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) - ->willReturn(true); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $centerB) - ->willReturn(false); - - $exportManager = $this->createExportManager(null, null, - $authorizationChecker->reveal(), null, $user); - - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - - $result = $exportManager->isGrantedForElement($export->reveal(), null, array($center, $centerB)); - - $this->assertFalse($result); - - } - - public function testIsGrantedForElementWithExportEmptyCenters() - { - $user = $this->prepareUser(array()); - - $exportManager = $this->createExportManager(null, null, - null, null, $user); - - $export = $this->prophet->prophesize(); - $export->willImplement(\Chill\MainBundle\Export\ExportInterface::class); - $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - - $result = $exportManager->isGrantedForElement($export->reveal(), null, array()); - - $this->assertFalse($result); - - } - - public function testIsGrantedForElementWithModifierFallbackToExport() - { - $center = $this->prepareCenter(100, 'center A'); - $centerB = $this->prepareCenter(102, 'center B'); - $user = $this->prepareUser(array()); - - $authorizationChecker = $this->prophet->prophesize(); - $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) - ->willReturn(true); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $centerB) - ->willReturn(false); - - $exportManager = $this->createExportManager(null, null, - $authorizationChecker->reveal(), null, $user); - - $modifier = $this->prophet->prophesize(); - $modifier->willImplement(\Chill\MainBundle\Export\ModifierInterface::class); - $modifier->addRole()->willReturn(NULL); - - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - - $result = $exportManager->isGrantedForElement($modifier->reveal(), - $export->reveal(), array($center, $centerB)); - - $this->assertFalse($result); - - } - - public function testAggregatorsApplyingOn() - { - $center = $this->prepareCenter(100, 'center'); - $centers = array($center); - $user = $this->prepareUser(array()); - - $authorizationChecker = $this->prophet->prophesize(); - $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) - ->willReturn(true); - - $exportManager = $this->createExportManager(null, null, - $authorizationChecker->reveal(), null, $user); - - $exportFooBar = $this->prophet->prophesize(); - $exportFooBar->willImplement(ExportInterface::class); - $exportFooBar->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - $exportFooBar->supportsModifiers()->willReturn(array('foo', 'bar')); - - $aggregatorBar = $this->prophet->prophesize(); - $aggregatorBar->willImplement(AggregatorInterface::class); - $aggregatorBar->applyOn()->willReturn('bar'); - $aggregatorBar->addRole()->willReturn(null); - $exportManager->addAggregator($aggregatorBar->reveal(), 'bar'); - - $exportBar = $this->prophet->prophesize(); - $exportBar->willImplement(ExportInterface::class); - $exportBar->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - $exportBar->supportsModifiers()->willReturn(array('bar')); - - $aggregatorFoo = $this->prophet->prophesize(); - $aggregatorFoo->willImplement(AggregatorInterface::class); - $aggregatorFoo->applyOn()->willReturn('foo'); - $aggregatorFoo->addRole()->willReturn(null); - $exportManager->addAggregator($aggregatorFoo->reveal(), 'foo'); - - $exportFoo = $this->prophet->prophesize(); - $exportFoo->willImplement(ExportInterface::class); - $exportFoo->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - $exportFoo->supportsModifiers()->willReturn(array('foo')); - - $obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFoo->reveal(), $centers)); - $this->assertEquals(1, count($obtained)); - $this->assertContains('foo', array_keys($obtained)); - - $obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportBar->reveal(), $centers)); - $this->assertEquals(1, count($obtained)); - $this->assertContains('bar', array_keys($obtained)); - - $obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFooBar->reveal(), $centers)); - $this->assertEquals(2, count($obtained)); - $this->assertContains('bar', array_keys($obtained)); - $this->assertContains('foo', array_keys($obtained)); - - // test with empty centers - $obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFooBar->reveal(), array())); - $this->assertEquals(0, count($obtained)); - - } - - public function testFiltersApplyingOn() - { - $center = $this->prepareCenter(100, 'center'); - $centers = array($center); - $user = $this->prepareUser(array()); - - $authorizationChecker = $this->prophet->prophesize(); - $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) - ->willReturn(true); - - $exportManager = $this->createExportManager(null, null, - $authorizationChecker->reveal(), null, $user); - - $exportFooBar = $this->prophet->prophesize(); - $exportFooBar->willImplement(ExportInterface::class); - $exportFooBar->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - $exportFooBar->supportsModifiers()->willReturn(array('foo', 'bar')); - - $filterBar = $this->prophet->prophesize(); - $filterBar->willImplement(FilterInterface::class); - $filterBar->applyOn()->willReturn('bar'); - $filterBar->addRole()->willReturn(null); - $filterBar->describeAction(Argument::cetera())->willReturn('string'); - $exportManager->addFilter($filterBar->reveal(), 'bar'); - - $exportBar = $this->prophet->prophesize(); - $exportBar->willImplement(ExportInterface::class); - $exportBar->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - $exportBar->supportsModifiers()->willReturn(array('bar')); - - $filterFoo = $this->prophet->prophesize(); - $filterFoo->willImplement(FilterInterface::class); - $filterFoo->applyOn()->willReturn('foo'); - $filterFoo->addRole()->willReturn(null); - $filterFoo->describeAction(Argument::cetera())->willReturn('string'); - $exportManager->addFilter($filterFoo->reveal(), 'foo'); - - $exportFoo = $this->prophet->prophesize(); - $exportFoo->willImplement(ExportInterface::class); - $exportFoo->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - $exportFoo->supportsModifiers()->willReturn(array('foo')); - - - $obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFoo->reveal(), $centers)); - $this->assertEquals(1, count($obtained)); - $this->assertContains('foo', array_keys($obtained)); - - $obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportBar->reveal(), $centers)); - $this->assertEquals(1, count($obtained)); - $this->assertContains('bar', array_keys($obtained)); - - $obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFooBar->reveal(), $centers)); - $this->assertEquals(2, count($obtained)); - $this->assertContains('bar', array_keys($obtained)); - $this->assertContains('foo', array_keys($obtained)); - - $obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFooBar->reveal(), array())); - $this->assertEquals(0, count($obtained)); - } - /** - * Test the generation of an export + * Create an ExportManager where every element may be replaced by a double. + * + * If null is provided for an element, this is replaced by the equivalent + * from the container; if the user provided is null, this is replaced by the + * user 'center a_social' from database. + * + * @param \Psr\Log\LoggerInterface $logger + * @param \Doctrine\ORM\EntityManagerInterface $em + * @param \Symfony\Component\Security\Core\Authorization\AuthorizationChecker $authorizationChecker + * @param \Chill\MainBundle\Security\Authorization\AuthorizationHelper $authorizationHelper + * @param \Symfony\Component\Security\Core\User\UserInterface $user + * + * @return ExportManager */ - public function testGenerate() - { - $center = $this->prepareCenter(100, 'center'); - $user = $this->prepareUser(array()); - - $authorizationChecker = $this->prophet->prophesize(); - $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) - ->willReturn(true); - - $exportManager = $this->createExportManager(null, null, - $authorizationChecker->reveal(), null, $user); - - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $em = self::$container->get('doctrine.orm.entity_manager'); - $export->initiateQuery( - Argument::is(array('foo')), - Argument::Type('array'), - Argument::is(array('a' => 'b')) - ) - ->will(function () use ($em) { - $qb = $em->createQueryBuilder(); - return $qb->addSelect('COUNT(user.id) as export') - ->from('ChillMainBundle:User', 'user'); - - }); - //$export->initiateQuery()->shouldBeCalled(); - $export->supportsModifiers()->willReturn(array('foo')); - $export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY')); - $export->getResult(Argument::Type(QueryBuilder::class), Argument::Type('array'))->willReturn(array( - array( - 'aggregator' => 'cat a', - 'export' => 0, - ), - array( - 'aggregator' => 'cat b', - 'export' => 1 - ) - )); - $export->getLabels( - Argument::is('export'), - Argument::is(array(0, 1)), - Argument::Type('array') - ) - ->willReturn(function($value) { - switch($value) { - case 0: - case 1: - return $value; - case '_header': - return 'export'; - default: throw new \RuntimeException(sprintf("The value %s is not valid", $value)); - } - }); - - $export->getQueryKeys(Argument::Type('array'))->willReturn(array('export')); - $export->getTitle()->willReturn('dummy title'); - $exportManager->addExport($export->reveal(), 'dummy'); - - $filter = $this->prophet->prophesize(); - $filter->willImplement(FilterInterface::class); - //$filter->alterQuery()->shouldBeCalled(); - $filter->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->willReturn(null); - $filter->addRole()->shouldBeCalled(); - //$filter->addRole()->shouldBeCalled(); - $filter->applyOn()->willReturn('foo'); - $filter->describeAction(Argument::cetera())->willReturn('filtered string'); - $exportManager->addFilter($filter->reveal(), 'filter_foo'); - - $aggregator = $this->prophet->prophesize(); - $aggregator->willImplement(AggregatorInterface::class); - $aggregator->applyOn()->willReturn('foo'); - $aggregator->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->willReturn(null); - //$aggregator->alterQuery()->shouldBeCalled(); - $aggregator->getQueryKeys(Argument::Type('array'))->willReturn(array('aggregator')); - $aggregator->getLabels( - Argument::is('aggregator'), - Argument::is(array('cat a', 'cat b')), - Argument::is(array()) - ) - ->willReturn(function($value) { - switch ($value) { - case '_header': return 'foo_header'; - case 'cat a' : return 'label cat a'; - case 'cat b' : return 'label cat b'; - default: - throw new \RuntimeException(sprintf("This value (%s) is not valid", $value)); - } - }); - $aggregator->addRole()->willReturn(null); - //$aggregator->addRole()->shouldBeCalled(); - $exportManager->addAggregator($aggregator->reveal(), 'aggregator_foo'); - - //add formatter interface - $formatter = new \Chill\MainBundle\Export\Formatter\SpreadSheetFormatter( - self::$container->get('translator'), $exportManager); - $exportManager->addFormatter($formatter, 'spreadsheet'); - - //ob_start(); - $response = $exportManager->generate( - 'dummy', - array($center), - array( - ExportType::FILTER_KEY => array( - 'filter_foo' => array( - 'enabled' => true, - 'form' => array() - ) - ), - ExportType::AGGREGATOR_KEY => array( - 'aggregator_foo' => array( - 'enabled' => true, - 'form' => array() - ) - ), - ExportType::PICK_FORMATTER_KEY => array( - 'alias' => 'spreadsheet' - ), - ExportType::EXPORT_KEY => array( - 'a' => 'b' - ) - ), - array( - 'format' => 'csv', - 'aggregator_foo' => array( - 'order' => 1 - ) - ) - ); - //$content = ob_get_clean(); - $this->assertInstanceOf(Response::class, $response); - $expected = <<get('doctrine.orm.entity_manager') + ->getRepository('ChillMainBundle:User') + ->findOneBy(['username' => 'center a_social']) : + $user; + $token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($localUser, 'password', 'provider'); + $tokenStorage = new \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage(); + $tokenStorage->setToken($token); -EOT; - - $this->assertEquals($expected, $response->getContent()); + return new ExportManager( + null === $logger ? self::$container->get('logger') : $logger, + null === $em ? self::$container->get('doctrine.orm.entity_manager') : $em, + null === $authorizationChecker ? self::$container->get('security.authorization_checker') : $authorizationChecker, + null === $authorizationHelper ? self::$container->get('chill.main.security.authorization.helper') : $authorizationHelper, + $tokenStorage + ); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Form/Type/PickCenterTypeTest.php b/src/Bundle/ChillMainBundle/Tests/Form/Type/PickCenterTypeTest.php index a72360301..55f9b64c5 100644 --- a/src/Bundle/ChillMainBundle/Tests/Form/Type/PickCenterTypeTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Form/Type/PickCenterTypeTest.php @@ -1,83 +1,28 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Form\Type; -use Symfony\Component\Form\Test\TypeTestCase; -use Chill\MainBundle\Form\Type\CenterType; -use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\GroupCenter; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Chill\MainBundle\Entity\User; use Symfony\Bridge\Doctrine\Form\Type\EntityType; - +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Test\TypeTestCase; /** - * - * - * @author Julien Fastré + * @internal + * @coversNothing */ -class CenterTypeTest extends TypeTestCase +final class PickCenterTypeTest extends TypeTestCase { - /** - * Test that a user which can reach only one center - * render as an hidden field - */ - public function testUserCanReachSingleCenter() - { - $this->markTestSkipped(); - //prepare user - $center = $this->prepareCenter(1, 'center'); - $groupCenter = (new GroupCenter()) - ->setCenter($center) - ; - $user = (new User()) - ->addGroupCenter($groupCenter); - - $type = $this->prepareType($user); - - $this->assertEquals(HiddenType::class, $type->getParent()); - } - - /** - * Test that a user which can reach only one center - * render as an hidden field - */ - public function testUserCanReachMultipleSameCenter() - { - $this->markTestSkipped(); - //prepare user - $center = $this->prepareCenter(1, 'center'); - $groupCenterA = (new GroupCenter()) - ->setCenter($center) - ; - $groupCenterB = (new GroupCenter()) - ->setCenter($center) - ; - $user = (new User()) - ->addGroupCenter($groupCenterA) - ->addGroupCenter($groupCenterB); - - $type = $this->prepareType($user); - - $this->assertEquals(HiddenType::class, $type->getParent()); - } - /** * Test that a user which can reach multiple center * make CenterType render as "entity" type. @@ -89,15 +34,12 @@ class CenterTypeTest extends TypeTestCase $centerA = $this->prepareCenter(1, 'centerA'); $centerB = $this->prepareCenter(2, 'centerB'); $groupCenterA = (new GroupCenter()) - ->setCenter($centerA) - ; + ->setCenter($centerA); $groupCenterB = (new GroupCenter()) - ->setCenter($centerB) - ; + ->setCenter($centerB); $user = (new User()) - ->addGroupCenter($groupCenterA) - ->addGroupCenter($groupCenterB) - ; + ->addGroupCenter($groupCenterA) + ->addGroupCenter($groupCenterB); $type = $this->prepareType($user); @@ -105,15 +47,57 @@ class CenterTypeTest extends TypeTestCase } /** - * prepare a mocked center, with and id and name given + * Test that a user which can reach only one center + * render as an hidden field. + */ + public function testUserCanReachMultipleSameCenter() + { + $this->markTestSkipped(); + //prepare user + $center = $this->prepareCenter(1, 'center'); + $groupCenterA = (new GroupCenter()) + ->setCenter($center); + $groupCenterB = (new GroupCenter()) + ->setCenter($center); + $user = (new User()) + ->addGroupCenter($groupCenterA) + ->addGroupCenter($groupCenterB); + + $type = $this->prepareType($user); + + $this->assertEquals(HiddenType::class, $type->getParent()); + } + + /** + * Test that a user which can reach only one center + * render as an hidden field. + */ + public function testUserCanReachSingleCenter() + { + $this->markTestSkipped(); + //prepare user + $center = $this->prepareCenter(1, 'center'); + $groupCenter = (new GroupCenter()) + ->setCenter($center); + $user = (new User()) + ->addGroupCenter($groupCenter); + + $type = $this->prepareType($user); + + $this->assertEquals(HiddenType::class, $type->getParent()); + } + + /** + * prepare a mocked center, with and id and name given. * * @param int $id * @param string $name + * * @return \Chill\MainBundle\Entity\Center */ private function prepareCenter($id, $name) { - $prophet = new \Prophecy\Prophet; + $prophet = new \Prophecy\Prophet(); $prophecyCenter = $prophet->prophesize(); $prophecyCenter->willExtend('\Chill\MainBundle\Entity\Center'); @@ -123,36 +107,35 @@ class CenterTypeTest extends TypeTestCase return $prophecyCenter->reveal(); } - /** - * prepare the type with mocked center transformer and token storage + * prepare the type with mocked center transformer and token storage. * * @param User $user the user for wich the form will be prepared + * * @return CenterType */ private function prepareType(User $user) { - $prophet = new \Prophecy\Prophet; + $prophet = new \Prophecy\Prophet(); //create a center transformer $centerTransformerProphecy = $prophet->prophesize(); $centerTransformerProphecy - ->willExtend('Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer'); + ->willExtend('Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer'); $transformer = $centerTransformerProphecy->reveal(); $tokenProphecy = $prophet->prophesize(); $tokenProphecy - ->willImplement('\Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + ->willImplement('\Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); $tokenProphecy->getUser()->willReturn($user); $token = $tokenProphecy->reveal(); $tokenStorageProphecy = $prophet->prophesize(); $tokenStorageProphecy - ->willExtend('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage'); + ->willExtend('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage'); $tokenStorageProphecy->getToken()->willReturn($token); $tokenStorage = $tokenStorageProphecy->reveal(); return new CenterType($tokenStorage, $transformer); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Pagination/PageTest.php b/src/Bundle/ChillMainBundle/Tests/Pagination/PageTest.php index 3f0dd2367..c527a1f6a 100644 --- a/src/Bundle/ChillMainBundle/Tests/Pagination/PageTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Pagination/PageTest.php @@ -1,148 +1,137 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests\Pagination; +use Chill\MainBundle\Pagination\Page; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Chill\MainBundle\Pagination\Page; /** - * Test the Page class + * Test the Page class. * - * @author Julien Fastré - * @author Champs Libres + * @internal + * @coversNothing */ -class PageTest extends KernelTestCase +final class PageTest extends KernelTestCase { protected $paginator; - + protected $prophet; - - public function setUp() { - $this->prophet = new \Prophecy\Prophet; - - + + public function setUp() + { + $this->prophet = new \Prophecy\Prophet(); } - + /** - * - * @param int $maxResult - * @param int $itemPerPage - * @param string $route - * @param array $routeParameters - * @return Page - */ - protected function generatePage( - $number = 1, - $itemPerPage = 10, - $route = 'route', - array $routeParameters = array(), - $totalItems = 100 - ) { - $urlGenerator = $this->prophet->prophesize(); - $urlGenerator->willImplement(UrlGeneratorInterface::class); - - return new Page( - $number, - $itemPerPage, - $urlGenerator->reveal(), - $route, - $routeParameters, - $totalItems - ); - } - - public function testPageNumber() { - $page = $this->generatePage(1); - - $this->assertEquals(1, $page->getNumber()); - } - - /** - * return a set of element to testGetFirstItemNumber - * - * the set contains : + * return a set of element to testGetFirstItemNumber. + * + * the set contains : * - the page number ; * - the number of item per page ; * - the expected first item number - * + * * @return array */ - public function generateGetFirstItemNumber() { - return array( - [1, 10, 0], - [2, 10, 10] - ); + public function generateGetFirstItemNumber() + { + return [ + [1, 10, 0], + [2, 10, 10], + ]; } - + + /** + * return a set of element to testGetLastItemNumber. + * + * the set contains : + * - the page number ; + * - the number of item per page ; + * - the expected last item number + * + * @return array + */ + public function generateGetLastItemNumber() + { + return [ + [1, 10, 9], + [2, 10, 19], + ]; + } + /** - * * @param int $number * @param int $itemPerPage * @param int $expectedItemPerPage * @dataProvider generateGetFirstItemNumber */ public function testGetFirstItemNumber( - $number, - $itemPerPage, - $expectedItemPerPage + $number, + $itemPerPage, + $expectedItemPerPage ) { $page = $this->generatePage($number, $itemPerPage); - + $this->assertEquals($expectedItemPerPage, $page->getFirstItemNumber()); } - + /** - * return a set of element to testGetLastItemNumber - * - * the set contains : - * - the page number ; - * - the number of item per page ; - * - the expected last item number - * - * @return array - */ - public function generateGetLastItemNumber() { - return array( - [1, 10, 9], - [2, 10, 19] - ); - } - - /** - * * @param int $number * @param int $itemPerPage * @param int $expectedItemPerPage * @dataProvider generateGetLastItemNumber */ public function testGetLastItemNumber( - $number, - $itemPerPage, - $expectedItemPerPage + $number, + $itemPerPage, + $expectedItemPerPage ) { $page = $this->generatePage($number, $itemPerPage); - + $this->assertEquals($expectedItemPerPage, $page->getLastItemNumber()); } - - + + public function testPageNumber() + { + $page = $this->generatePage(1); + + $this->assertEquals(1, $page->getNumber()); + } + + /** + * @param int $itemPerPage + * @param string $route + * @param mixed $number + * @param mixed $totalItems + * + * @return Page + */ + protected function generatePage( + $number = 1, + $itemPerPage = 10, + $route = 'route', + array $routeParameters = [], + $totalItems = 100 + ) { + $urlGenerator = $this->prophet->prophesize(); + $urlGenerator->willImplement(UrlGeneratorInterface::class); + + return new Page( + $number, + $itemPerPage, + $urlGenerator->reveal(), + $route, + $routeParameters, + $totalItems + ); + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Pagination/PaginatorTest.php b/src/Bundle/ChillMainBundle/Tests/Pagination/PaginatorTest.php index b1a234b94..2a92ef222 100644 --- a/src/Bundle/ChillMainBundle/Tests/Pagination/PaginatorTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Pagination/PaginatorTest.php @@ -1,133 +1,113 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests\Pagination; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Chill\MainBundle\Pagination\Paginator; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use function count; /** - * Test the paginator class + * Test the paginator class. * - * @author Julien Fastré - * @author Champs Libres + * @internal + * @coversNothing */ -class PaginatorTest extends KernelTestCase +final class PaginatorTest extends KernelTestCase { protected $paginator; - + protected $prophet; - - public function setUp() + + public function setUp() { - $this->prophet = new \Prophecy\Prophet; + $this->prophet = new \Prophecy\Prophet(); } - - /** - * - * @param int $maxResult - * @param int $itemPerPage - * @param string $route - * @param array $routeParameters - * @return Paginator - */ - protected function generatePaginator( - $totalItems, - $itemPerPage, - $currentPageNumber = 1, - $route = '', - array $routeParameters = array() - ) { - $urlGenerator = $this->prophet->prophesize(); - $urlGenerator->willImplement(UrlGeneratorInterface::class); - - return new Paginator( - $totalItems, - $itemPerPage, - $currentPageNumber, - $route, - $routeParameters, - $urlGenerator->reveal(), - 'page', - 'item_per_page' - ); - } - - /** - * generate a set of pages with different maxItem, itemPerPage, and - * expected page number - * - * @return array - */ - public function generatePageNumber() - { - return array( - [12, 10, 2], - [20, 10, 2], - [21, 10, 3], - [19, 10, 2], - [1, 10, 1], - [0, 10, 1], - [10, 10, 1] - ); - } - - /** - * Test that the countPages method (and his alias `count`) return - * valid results. - * - * @param int $totalItems - * @param int $itemPerPage - * @param int $expectedPageNumber - * @dataProvider generatePageNumber - */ - public function testPageNumber($totalItems, $itemPerPage, $expectedPageNumber) - { - $paginator = $this->generatePaginator($totalItems, $itemPerPage); - - $this->assertEquals($expectedPageNumber, $paginator->countPages()); - $this->assertEquals($expectedPageNumber, count($paginator)); - } - + /** * generate an array with a set of page with : * - total items ; * - item per page ; * - current page number ; - * - expected hasNextPage result - * + * - expected hasNextPage result. + * * @return array */ - public function generateHasNextPage() + public function generateHasNextPage() { - return array( - [10, 10, 1, false], - [20, 10, 1, true], - [12, 10, 1, true], - [12, 10, 2, false] - ); + return [ + [10, 10, 1, false], + [20, 10, 1, true], + [12, 10, 1, true], + [12, 10, 2, false], + ]; } - + + public function generateHasPage() + { + return [ + [10, 10, -1, false], + [12, 10, 1, true], + [12, 10, 2, true], + [12, 10, 3, false], + ]; + } + + /** + * generate an array with a set of page with : + * - total items ; + * - item per page ; + * - current page number ; + * - expected hasPreviousPage result. + * + * @return array + */ + public function generateHasPreviousPage() + { + return [ + [10, 10, 1, false], + [20, 10, 1, false], + [12, 10, 1, false], + [12, 10, 2, true], + ]; + } + + /** + * generate a set of pages with different maxItem, itemPerPage, and + * expected page number. + * + * @return array + */ + public function generatePageNumber() + { + return [ + [12, 10, 2], + [20, 10, 2], + [21, 10, 3], + [19, 10, 2], + [1, 10, 1], + [0, 10, 1], + [10, 10, 1], + ]; + } + + public function testGetPage() + { + $paginator = $this->generatePaginator(105, 10); + + $this->assertEquals(5, $paginator->getPage(5)->getNumber()); + } + /** - * * @param int $totalItems * @param int $itemPerPage * @param int $currentPage @@ -135,103 +115,130 @@ class PaginatorTest extends KernelTestCase * @dataProvider generateHasNextPage */ public function testHasNextPage( - $totalItems, - $itemPerPage, - $currentPage, - $expectedHasNextPage + $totalItems, + $itemPerPage, + $currentPage, + $expectedHasNextPage ) { $paginator = $this->generatePaginator($totalItems, $itemPerPage, $currentPage); - + $this->assertEquals($expectedHasNextPage, $paginator->hasNextPage()); } - + /** - * generate an array with a set of page with : - * - total items ; - * - item per page ; - * - current page number ; - * - expected hasPreviousPage result - * - * @return array - */ - public function generateHasPreviousPage() - { - return array( - [10, 10, 1, false], - [20, 10, 1, false], - [12, 10, 1, false], - [12, 10, 2, true], - ); - } - - /** - * - * @param int $totalItems - * @param int $itemPerPage - * @param int $currentPage - * @param bool $expectedHasPreviousPage - * @dataProvider generateHasPreviousPage - */ - public function testHasPreviousPage( - $totalItems, - $itemPerPage, - $currentPage, - $expectedHasNextPage - ) { - $paginator = $this->generatePaginator($totalItems, $itemPerPage, $currentPage); - - $this->assertEquals($expectedHasNextPage, $paginator->hasPreviousPage()); - } - - public function generateHasPage() - { - return array( - [10, 10, -1, false], - [12, 10, 1, true], - [12, 10, 2, true], - [12, 10, 3, false] - ); - } - - /** - * test the HasPage function - * - * + * test the HasPage function. + * * @param int $totalItems * @param int $itemPerpage * @param int $pageNumber * @param bool $expectedHasPage * @dataProvider generateHasPage */ - public function testHasPage($totalItems, $itemPerpage, $pageNumber, - $expectedHasPage) - { + public function testHasPage( + $totalItems, + $itemPerpage, + $pageNumber, + $expectedHasPage + ) { $paginator = $this->generatePaginator($totalItems, $itemPerpage); - + $this->assertEquals($expectedHasPage, $paginator->hasPage($pageNumber)); } - - public function testGetPage() - { - $paginator = $this->generatePaginator(105, 10); - - $this->assertEquals(5, $paginator->getPage(5)->getNumber()); + + /** + * @param int $totalItems + * @param int $itemPerPage + * @param int $currentPage + * @param mixed $expectedHasNextPage + * @dataProvider generateHasPreviousPage + */ + public function testHasPreviousPage( + $totalItems, + $itemPerPage, + $currentPage, + $expectedHasNextPage + ) { + $paginator = $this->generatePaginator($totalItems, $itemPerPage, $currentPage); + + $this->assertEquals($expectedHasNextPage, $paginator->hasPreviousPage()); } - + + /** + * Test that the countPages method (and his alias `count`) return + * valid results. + * + * @param int $totalItems + * @param int $itemPerPage + * @param int $expectedPageNumber + * @dataProvider generatePageNumber + */ + public function testPageNumber($totalItems, $itemPerPage, $expectedPageNumber) + { + $paginator = $this->generatePaginator($totalItems, $itemPerPage); + + $this->assertEquals($expectedPageNumber, $paginator->countPages()); + $this->assertEquals($expectedPageNumber, count($paginator)); + } + public function testPagesGenerator() { $paginator = $this->generatePaginator(105, 10); - + $generator = $paginator->getPagesGenerator(); - + $i = 1; - foreach($generator as $page) { - $this->assertEquals($i, $page->getNumber(), - "assert that the current page number is $i"); - $i++; + + foreach ($generator as $page) { + $this->assertEquals( + $i, + $page->getNumber(), + "assert that the current page number is {$i}" + ); + ++$i; } - - $this->assertEquals(11, $page->getNumber(), - "assert that the last page number is 11"); + + $this->assertEquals( + 11, + $page->getNumber(), + 'assert that the last page number is 11' + ); + } + + public function testPagesWithoutResult() + { + $paginator = $this->generatePaginator(0, 10); + + $this->assertEquals(0, $paginator->getCurrentPageFirstItemNumber()); + $this->assertEquals(10, $paginator->getItemsPerPage()); + } + + /** + * @param int $itemPerPage + * @param string $route + * @param mixed $totalItems + * @param mixed $currentPageNumber + * + * @return Paginator + */ + protected function generatePaginator( + $totalItems, + $itemPerPage, + $currentPageNumber = 1, + $route = '', + array $routeParameters = [] + ) { + $urlGenerator = $this->prophet->prophesize(); + $urlGenerator->willImplement(UrlGeneratorInterface::class); + + return new Paginator( + $totalItems, + $itemPerPage, + $currentPageNumber, + $route, + $routeParameters, + $urlGenerator->reveal(), + 'page', + 'item_per_page' + ); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Routing/Loader/RouteLoaderTest.php b/src/Bundle/ChillMainBundle/Tests/Routing/Loader/RouteLoaderTest.php index 359c47a7b..ff8ee423e 100644 --- a/src/Bundle/ChillMainBundle/Tests/Routing/Loader/RouteLoaderTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Routing/Loader/RouteLoaderTest.php @@ -1,52 +1,41 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests\Routing\Loader; -use Chill\MainBundle\Routing\Loader\ChillRoutesLoader; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; - /** - * Test the route loader + * Test the route loader. * - * @author Julien Fastré + * @internal + * @coversNothing */ -class RouteLoaderTest extends KernelTestCase +final class RouteLoaderTest extends KernelTestCase { private $router; - + public function setUp() { - static::bootKernel(); - $this->router = static::$kernel->getContainer()->get('router'); + self::bootKernel(); + $this->router = self::$kernel->getContainer()->get('router'); } - + /** - * Test that the route loader loads at least homepage + * Test that the route loader loads at least homepage. */ public function testRouteFromMainBundleAreLoaded() { $homepage = $this->router->getRouteCollection()->get('chill_main_homepage'); - + $this->assertNotNull($homepage); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Search/AbstractSearchTest.php b/src/Bundle/ChillMainBundle/Tests/Search/AbstractSearchTest.php index c0f1827fb..c0c3b176a 100644 --- a/src/Bundle/ChillMainBundle/Tests/Search/AbstractSearchTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Search/AbstractSearchTest.php @@ -1,52 +1,43 @@ - * - * 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 . +/** + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests\Search; /** - * Description of AbstractSearch + * Description of AbstractSearch. * - * @author Julien Fastré + * @internal + * @coversNothing */ -class AbstractSearchTest extends \PHPUnit\Framework\TestCase +final class AbstractSearchTest extends \PHPUnit\Framework\TestCase { /** * @var \Chill\MainBundle\Search\AbstractSearch */ private $stub; - + public function setUp() { $this->stub = $this->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch'); } - + public function testParseDateRegular() { - $date = $this->stub->parseDate('2014-05-01'); $this->assertEquals('2014', $date->format('Y')); $this->assertEquals('05', $date->format('m')); $this->assertEquals('01', $date->format('d')); } - + public function testRecompose() { $this->markTestSkipped(); diff --git a/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php index 2e63f24e0..339145d9f 100644 --- a/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php @@ -1,27 +1,60 @@ addSelectClause('bada', ['one', 'two']) + ->addSelectClause('boum', ['three', 'four']) + ->setWhereClauses('mywhere', ['six', 'seven']); + + $params = $q->buildParameters(); + + $this->assertEquals(['six', 'seven'], $q->buildParameters(true)); + $this->assertEquals(['one', 'two', 'three', 'four', 'six', 'seven'], $q->buildParameters()); + } + public function testMultipleWhereClauses() { $q = new SearchApiQuery(); $q->setSelectJsonbMetadata('boum') ->setSelectKey('bim') - ->setSelectPertinence('1') + ->setSelectPertinence('?', ['gamma']) ->setFromClause('badaboum') - ->andWhereClause('foo', [ 'alpha' ]) - ->andWhereClause('bar', [ 'beta' ]) - ; + ->andWhereClause('foo', ['alpha']) + ->andWhereClause('bar', ['beta']); $query = $q->buildQuery(); - $this->assertStringContainsString('foo AND bar', $query); - $this->assertEquals(['alpha', 'beta'], $q->buildParameters()); + $this->assertStringContainsString('(foo) AND (bar)', $query); + $this->assertEquals(['gamma', 'alpha', 'beta'], $q->buildParameters()); + + $query = $q->buildQuery(true); + + $this->assertStringContainsString('(foo) AND (bar)', $query); + $this->assertEquals(['gamma', 'alpha', 'beta'], $q->buildParameters()); } public function testWithoutWhereClause() @@ -30,11 +63,9 @@ class SearchApiQueryTest extends TestCase $q->setSelectJsonbMetadata('boum') ->setSelectKey('bim') ->setSelectPertinence('1') - ->setFromClause('badaboum') - ; + ->setFromClause('badaboum'); - $this->assertTrue(\is_string($q->buildQuery())); + $this->assertTrue(is_string($q->buildQuery())); $this->assertEquals([], $q->buildParameters()); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Search/SearchProviderTest.php b/src/Bundle/ChillMainBundle/Tests/Search/SearchProviderTest.php index 554b67b9a..3bbc2b94b 100644 --- a/src/Bundle/ChillMainBundle/Tests/Search/SearchProviderTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Search/SearchProviderTest.php @@ -1,197 +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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Test\Search; -use Chill\MainBundle\Search\SearchProvider; use Chill\MainBundle\Search\SearchInterface; +use Chill\MainBundle\Search\SearchProvider; use PHPUnit\Framework\TestCase; - -class SearchProviderTest extends TestCase +/** + * @internal + * @coversNothing + */ +final class SearchProviderTest extends TestCase { - /** - * - * @var SearchProvider + * @var SearchProvider */ private $search; - + public function setUp() { $this->search = new SearchProvider(); - + //add a default service $this->addSearchService( - $this->createDefaultSearchService('I am default', 10), 'default' - ); + $this->createDefaultSearchService('I am default', 10), + 'default' + ); //add a domain service $this->addSearchService( - $this->createNonDefaultDomainSearchService('I am domain bar', 20, FALSE), 'bar' - ); + $this->createNonDefaultDomainSearchService('I am domain bar', 20, false), + 'bar' + ); } - + + public function testAccentued() + { + //$this->markTestSkipped('accentued characters must be implemented'); + + $terms = $this->p('manço bélier aztèque à saloù ê'); + + $this->assertEquals([ + '_domain' => null, + '_default' => 'manco belier azteque a salou e', + ], $terms); + } + + public function testAccentuedCapitals() + { + //$this->markTestSkipped('accentued characters must be implemented'); + + $terms = $this->p('MANÉÀ oÛ lÎ À'); + + $this->assertEquals([ + '_domain' => null, + '_default' => 'manea ou li a', + ], $terms); + } + + public function testArgumentNameWithTrait() + { + $terms = $this->p('date-from:2016-05-04'); + + $this->assertEquals([ + '_domain' => null, + 'date-from' => '2016-05-04', + '_default' => '', + ], $terms); + } + + public function testCapitalLetters() + { + $terms = $this->p('Foo:Bar LOL marCi @PERSON'); + + $this->assertEquals([ + '_domain' => 'person', + '_default' => 'lol marci', + 'foo' => 'bar', + ], $terms); + } + + public function testDoubleParenthesis() + { + $terms = $this->p('@papamobile name:"my beautiful name" residual surname:"i love techno"'); + + $this->assertEquals([ + '_domain' => 'papamobile', + 'name' => 'my beautiful name', + '_default' => 'residual', + 'surname' => 'i love techno', + ], $terms); + } + /** * @expectedException \Chill\MainBundle\Search\UnknowSearchNameException */ public function testInvalidSearchName() { - $this->search->getByName("invalid name"); + $this->search->getByName('invalid name'); } - - public function testSimplePattern() - { - $terms = $this->p("@person birthdate:2014-01-02 name:(my name) is not my name"); - - $this->assertEquals(array( - '_domain' => 'person', - 'birthdate' => '2014-01-02', - '_default' => 'is not my name', - 'name' => 'my name' - ), $terms); - } - - public function testWithoutDomain() - { - $terms = $this->p('foo:bar residual'); - - $this->assertEquals(array( - '_domain' => null, - 'foo' => 'bar', - '_default' => 'residual' - ), $terms); - } - - public function testWithoutDefault() - { - $terms = $this->p('@person foo:bar'); - - $this->assertEquals(array( - '_domain' => 'person', - 'foo' => 'bar', - '_default' => '' - ), $terms); - } - - public function testCapitalLetters() - { - $terms = $this->p('Foo:Bar LOL marCi @PERSON'); - - $this->assertEquals(array( - '_domain' => 'person', - '_default' => 'lol marci', - 'foo' => 'bar' - ), $terms); - } - + /** - * @expectedException Chill\MainBundle\Search\ParsingException + * @expectedException \Chill\MainBundle\Search\ParsingException */ public function testMultipleDomainError() { - $term = $this->p("@person @report"); + $term = $this->p('@person @report'); } - - public function testDoubleParenthesis() - { - $terms = $this->p("@papamobile name:(my beautiful name) residual " - . "surname:(i love techno)"); - - $this->assertEquals(array( - '_domain' => 'papamobile', - 'name' => 'my beautiful name', - '_default' => 'residual', - 'surname' => 'i love techno' - ), $terms); - } - - public function testAccentued() - { - //$this->markTestSkipped('accentued characters must be implemented'); - - $terms = $this->p('manço bélier aztèque à saloù ê'); - - $this->assertEquals(array( - '_domain' => NULL, - '_default' => 'manco belier azteque a salou e' - ), $terms); - } - - public function testAccentuedCapitals() - { - //$this->markTestSkipped('accentued characters must be implemented'); - - $terms = $this->p('MANÉÀ oÛ lÎ À'); - - $this->assertEquals(array( - '_domain' => null, - '_default' => 'manea ou li a' - ), $terms); - } - - public function testTrimInParenthesis() - { - $terms = $this->p('foo:(bar )'); - - $this->assertEquals(array( - '_domain' => null, - 'foo' => 'bar', - '_default' => '' - ), $terms); - } - - public function testTrimInDefault() - { - $terms = $this->p(' foo bar '); - - $this->assertEquals(array( - '_domain' => null, - '_default' => 'foo bar' - ), $terms); - } - - public function testArgumentNameWithTrait() - { - $terms = $this->p('date-from:2016-05-04'); - - $this->assertEquals(array( - '_domain' => null, - 'date-from' => '2016-05-04', - '_default' => '' - ), $terms); - } - + /** - * Test the behaviour when no domain is provided in the search pattern : - * the default search should be enabled + * Test the behaviour when no domain is provided in the search pattern : + * the default search should be enabled. */ public function testSearchResultDefault() { $response = $this->search->getSearchResults('default search'); //$this->markTestSkipped(); - - $this->assertEquals(array( - "I am default" - ), $response); + + $this->assertEquals([ + 'I am default', + ], $response); } - + + public function testSearchResultDomainSearch() + { + //add a search service which will be supported + $this->addSearchService( + $this->createNonDefaultDomainSearchService('I am domain foo', 100, true), + 'foo' + ); + + $response = $this->search->getSearchResults('@foo default search'); + + $this->assertEquals([ + 'I am domain foo', + ], $response); + } + /** * @expectedException \Chill\MainBundle\Search\UnknowSearchDomainException */ @@ -200,116 +154,154 @@ class SearchProviderTest extends TestCase $response = $this->search->getSearchResults('@unknow domain'); //$this->markTestSkipped(); - } - - public function testSearchResultDomainSearch() - { - //add a search service which will be supported - $this->addSearchService( - $this->createNonDefaultDomainSearchService("I am domain foo", 100, TRUE), 'foo' - ); - - $response = $this->search->getSearchResults('@foo default search'); - - $this->assertEquals(array( - "I am domain foo" - ), $response); - - } - + public function testSearchWithinSpecificSearchName() { //add a search service which will be supported $this->addSearchService( - $this->createNonDefaultDomainSearchService("I am domain foo", 100, TRUE), 'foo' - ); - + $this->createNonDefaultDomainSearchService('I am domain foo', 100, true), + 'foo' + ); + $response = $this->search->getResultByName('@foo search', 'foo'); - + $this->assertEquals('I am domain foo', $response); - } - + /** * @expectedException \Chill\MainBundle\Search\ParsingException */ public function testSearchWithinSpecificSearchNameInConflictWithSupport() { $response = $this->search->getResultByName('@foo default search', 'bar'); - } - + + public function testSimplePattern() + { + $terms = $this->p('@person birthdate:2014-01-02 name:"my name" is not my name'); + + $this->assertEquals([ + '_domain' => 'person', + 'birthdate' => '2014-01-02', + '_default' => 'is not my name', + 'name' => 'my name', + ], $terms); + } + + public function testTrimInDefault() + { + $terms = $this->p(' foo bar '); + + $this->assertEquals([ + '_domain' => null, + '_default' => 'foo bar', + ], $terms); + } + + public function testTrimInParenthesis() + { + $terms = $this->p('foo:"bar "'); + + $this->assertEquals([ + '_domain' => null, + 'foo' => 'bar', + '_default' => '', + ], $terms); + } + + public function testWithoutDefault() + { + $terms = $this->p('@person foo:bar'); + + $this->assertEquals([ + '_domain' => 'person', + 'foo' => 'bar', + '_default' => '', + ], $terms); + } + + public function testWithoutDomain() + { + $terms = $this->p('foo:bar residual'); + + $this->assertEquals([ + '_domain' => null, + 'foo' => 'bar', + '_default' => 'residual', + ], $terms); + } + /** - * shortcut for executing parse method - * + * Add a search service to the chill.main.search_provider. + * + * Useful for mocking the SearchInterface + * + * @param string $name + */ + private function addSearchService(SearchInterface $search, $name) + { + $this->search + ->addSearchService($search, $name); + } + + private function createDefaultSearchService($result, $order) + { + $mock = $this + ->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch'); + + //set the mock as default + $mock->expects($this->any()) + ->method('isActiveByDefault') + ->will($this->returnValue(true)); + + $mock->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($order)); + + //set the return value + $mock->expects($this->any()) + ->method('renderResult') + ->will($this->returnValue($result)); + + return $mock; + } + + private function createNonDefaultDomainSearchService($result, $order, $domain) + { + $mock = $this + ->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch'); + + //set the mock as default + $mock->expects($this->any()) + ->method('isActiveByDefault') + ->will($this->returnValue(false)); + + $mock->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($order)); + + $mock->expects($this->any()) + ->method('supports') + ->will($this->returnValue($domain)); + + //set the return value + $mock->expects($this->any()) + ->method('renderResult') + ->will($this->returnValue($result)); + + return $mock; + } + + /** + * shortcut for executing parse method. + * * @param unknown $pattern + * * @return string[] */ private function p($pattern) { return $this->search->parse($pattern); } - - /** - * Add a search service to the chill.main.search_provider - * - * Useful for mocking the SearchInterface - * - * @param SearchInterface $search - * @param string $name - */ - private function addSearchService(SearchInterface $search, $name) - { - $this->search - ->addSearchService($search, $name); - } - - private function createDefaultSearchService($result, $order) - { - $mock = $this - ->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch'); - - //set the mock as default - $mock->expects($this->any()) - ->method('isActiveByDefault') - ->will($this->returnValue(TRUE)); - - $mock->expects($this->any()) - ->method('getOrder') - ->will($this->returnValue($order)); - - //set the return value - $mock->expects($this->any()) - ->method('renderResult') - ->will($this->returnValue($result)); - - return $mock; - } - - private function createNonDefaultDomainSearchService($result, $order, $domain) - { - $mock = $this - ->getMockForAbstractClass('Chill\MainBundle\Search\AbstractSearch'); - - //set the mock as default - $mock->expects($this->any()) - ->method('isActiveByDefault') - ->will($this->returnValue(FALSE)); - - $mock->expects($this->any()) - ->method('getOrder') - ->will($this->returnValue($order)); - - $mock->expects($this->any()) - ->method('supports') - ->will($this->returnValue($domain)); - - //set the return value - $mock->expects($this->any()) - ->method('renderResult') - ->will($this->returnValue($result)); - - return $mock; - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php new file mode 100644 index 000000000..310fac60e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php @@ -0,0 +1,65 @@ +extractDates($subject); + + $this->assertCount($count, $result->getFound()); + $this->assertEquals($filtered, $result->getFilteredSubject()); + $this->assertContainsOnlyInstancesOf(DateTimeImmutable::class, $result->getFound()); + + $dates = array_map( + static function (DateTimeImmutable $d) { + return $d->format('Y-m-d'); + }, + $result->getFound() + ); + + foreach ($datesSearched as $date) { + $this->assertContains($date, $dates); + } + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php new file mode 100644 index 000000000..f792c0a04 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php @@ -0,0 +1,56 @@ +extractPhonenumber($subject); + + $this->assertCount($expectedCount, $result->getFound()); + $this->assertEquals($filteredSubject, $result->getFilteredSubject()); + $this->assertEquals($expected, $result->getFound()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Security/Authorization/AuthorizationHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Security/Authorization/AuthorizationHelperTest.php index 764d6c429..4618ba3b1 100644 --- a/src/Bundle/ChillMainBundle/Tests/Security/Authorization/AuthorizationHelperTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Security/Authorization/AuthorizationHelperTest.php @@ -1,386 +1,46 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests\Security\Authorization; +use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCentersInterface; use Chill\MainBundle\Entity\HasScopeInterface; use Chill\MainBundle\Entity\HasScopesInterface; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Chill\MainBundle\Test\PrepareUserTrait; +use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Test\PrepareCenterTrait; use Chill\MainBundle\Test\PrepareScopeTrait; +use Chill\MainBundle\Test\PrepareUserTrait; use Chill\MainBundle\Test\ProphecyTrait; -use Chill\MainBundle\Entity\User; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Security\Core\Role\Role; -use Chill\MainBundle\Entity\Scope; -use Chill\MainBundle\Entity\Center; +use function array_map; +use function in_array; /** - * - * - * @author Julien Fastré + * @internal + * @coversNothing */ -class AuthorizationHelperTest extends KernelTestCase +final class AuthorizationHelperTest extends KernelTestCase { - - use PrepareUserTrait, PrepareCenterTrait, PrepareScopeTrait, ProphecyTrait; + use PrepareCenterTrait; + use PrepareScopeTrait; + use PrepareUserTrait; + use ProphecyTrait; public function setUp() { - static::bootKernel(); - } - - /** - * - * @return \Chill\MainBundle\Security\Authorization\AuthorizationHelper - */ - private function getAuthorizationHelper() - { - return static::$container - ->get('chill.main.security.authorization.helper') - ; - } - - /** - * Test function userCanReach of helper. - * - * A user can reach center => the function should return true. - */ - public function testUserCanReachCenter_UserShouldReach() - { - $center = $this->prepareCenter(1, 'center'); - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'ANY_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - - $this->assertTrue($helper->userCanReachCenter($user, $center)); - } - - /** - * Test function userCanReach of helper - * - * A user can not reachcenter =>W the function should return false - */ - public function testUserCanReachCenter_UserShouldNotReach() - { - $centerA = $this->prepareCenter(1, 'center'); - $centerB = $this->prepareCenter(2, 'centerB'); - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $centerA, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'ANY_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - - $this->assertFalse($helper->userCanReachCenter($user, $centerB)); - - } - - public function testUserHasAccess_shouldHaveAccess_EntityWithoutScope() - { - $center = $this->prepareCenter(1, 'center'); - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); - $entity->getCenter()->willReturn($center); - - $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), - 'CHILL_ROLE')); - } - - public function testUserHasAccess_ShouldHaveAccessWithInheritance_EntityWithoutScope() - { - $center = $this->prepareCenter(1, 'center'); - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'CHILL_MASTER_ROLE'] - ) - ) - )); - - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); - $entity->getCenter()->willReturn($center); - - $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), - 'CHILL_INHERITED_ROLE_1')); - } - - - public function testuserHasAccess_UserHasNoRole_EntityWithoutScope() - { - $center = $this->prepareCenter(1, 'center'); - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'ANY_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); - $entity->getCenter()->willReturn($center); - - $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - /** - * test that a user has no access on a entity, but is granted on the same role - * on another center - */ - public function testUserHasAccess_userHasNoRole_UserHasRoleOnAnotherCenter_EntityWithoutScope() - { - $centerA = $this->prepareCenter(1, 'center'); - $centerB = $this->prepareCenter(2, 'centerB'); - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $centerA, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'ANY_ROLE'] - ), - array( - 'centerB' => $centerB, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'ANY_ROLE'], - ['scope' => $scope, 'role' => 'CHILL_ROLE'] - ) - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); - $entity->getCenter()->willReturn($centerA); - - $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - public function testtestUserHasAccess_UserShouldHaveAccess_EntityWithScope() - { - $center = $this->prepareCenter(1, 'center'); - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); - $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); - $entity->getCenter()->willReturn($center); - $entity->getScope()->willReturn($scope); - - $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - public function testUserHasAccess_UserHasNoRole_EntityWithScope() - { - $center = $this->prepareCenter(1, 'center'); - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); - $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); - $entity->getCenter()->willReturn($center); - $entity->getScope()->willReturn($scope); - - $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'ANOTHER_ROLE')); - } - - public function testUserHasAccess_UserHasNoCenter_EntityWithScope() - { - $centerA = $this->prepareCenter(1, 'center'); //the user will have this center - $centerB = $this->prepareCenter(2, 'centerB'); //the entity will have another center - $scope = $this->prepareScope(1, 'default'); - $user = $this->prepareUser(array( - array( - 'center' => $centerA, 'permissionsGroup' => array( - ['scope' => $scope, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); - $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); - $entity->getCenter()->willReturn($centerB); - $entity->getScope()->willReturn($scope); - - $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - public function testUserHasAccess_UserHasNoScope_EntityWithScope() - { - $center = $this->prepareCenter(1, 'center'); - $scopeA = $this->prepareScope(1, 'default'); //the entity will have this scope - $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scopeB, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement(HasCenterInterface::class); - $entity->willImplement(HasScopeInterface::class); - $entity->getCenter()->willReturn($center); - $entity->getScope()->willReturn($scopeA); - - $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - public function testUserHasAccess_MultiCenter_EntityWithoutScope() - { - $center = $this->prepareCenter(1, 'center'); - $centerB = $this->prepareCenter(1, 'centerB'); - $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scopeB, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement(HasCentersInterface::class); - $entity->getCenters()->willReturn([$center, $centerB]); - - $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - public function testUserHasNoAccess_MultiCenter_EntityWithoutScope() - { - $center = $this->prepareCenter(1, 'center'); - $centerB = $this->prepareCenter(1, 'centerB'); - $centerC = $this->prepareCenter(1, 'centerC'); - $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope - $user = $this->prepareUser(array( - array( - 'center' => $center, 'permissionsGroup' => array( - ['scope' => $scopeB, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement(HasCentersInterface::class); - $entity->getCenters()->willReturn([$centerB, $centerC]); - - $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - public function testUserHasNoAccess_EntityMultiScope() - { - $centerA = $this->prepareCenter(1, 'center'); - $centerB = $this->prepareCenter(1, 'centerB'); - $scopeA = $this->prepareScope(2, 'other'); //the user will be granted this scope - $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope - $scopeC = $this->prepareScope(2, 'other'); //the user will be granted this scope - $user = $this->prepareUser(array( - array( - 'center' => $centerA, 'permissionsGroup' => array( - ['scope' => $scopeA, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement(HasCentersInterface::class); - $entity->willImplement(HasScopesInterface::class); - $entity->getCenters()->willReturn([$centerA, $centerB]); - $entity->getScopes()->willReturn([$scopeB, $scopeC]); - - $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - public function testUserHasAccess_EntityMultiScope() - { - $centerA = $this->prepareCenter(1, 'center'); - $centerB = $this->prepareCenter(1, 'centerB'); - $scopeA = $this->prepareScope(2, 'other'); //the user will be granted this scope - $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope - $user = $this->prepareUser(array( - array( - 'center' => $centerA, 'permissionsGroup' => array( - ['scope' => $scopeA, 'role' => 'CHILL_ROLE'] - ) - ) - )); - $helper = $this->getAuthorizationHelper(); - $entity = $this->getProphet()->prophesize(); - $entity->willImplement(HasCentersInterface::class); - $entity->willImplement(HasScopesInterface::class); - $entity->getCenters()->willReturn([$centerA, $centerB]); - $entity->getScopes()->willReturn([$scopeA, $scopeB]); - - $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); - } - - - - /** - * - * @dataProvider dataProvider_getReachableCenters - * @param Center $shouldHaveCenter - * @param User $user - * @param Role $role - * @param Scope $scope - */ - public function testGetReachableCenters($test, $result, $msg) - { - $this->assertEquals($test, $result, $msg); + self::bootKernel(); } public function dataProvider_getReachableCenters() @@ -392,93 +52,92 @@ class AuthorizationHelperTest extends KernelTestCase $scopeB = $this->prepareScope(2, 'scope B'); $scopeC = $this->prepareScope(3, 'scope C'); - $userA = $this->prepareUser(array( - array( + $userA = $this->prepareUser([ + [ 'center' => $centerA, - 'permissionsGroup' => array( + 'permissionsGroup' => [ ['scope' => $scopeB, 'role' => 'CHILL_ROLE_1'], - ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'] - ) - ), - array( - 'center' => $centerB, - 'permissionsGroup' => array( - ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'], - ['scope' => $scopeC, 'role' => 'CHILL_ROLE_2'] - ) - ) - - )); + ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'], + ], + ], + [ + 'center' => $centerB, + 'permissionsGroup' => [ + ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'], + ['scope' => $scopeC, 'role' => 'CHILL_ROLE_2'], + ], + ], + ]); $ah = $this->getAuthorizationHelper(); - return array( - // without scopes - array( + return [ + // without scopes + [ true, - in_array($centerA, $ah->getReachableCenters($userA, - new Role('CHILL_ROLE_1'), null)), - 'center A should be available for userA, with role 1 ' - ), - array( + in_array($centerA, $ah->getReachableCenters( + $userA, + 'CHILL_ROLE_1', + null + ), true), + 'center A should be available for userA, with role 1 ', + ], + [ true, - in_array($centerA, $ah->getReachableCenters($userA, - new Role('CHILL_ROLE_2'), null)), - 'center A should be available for userA, with role 2 ' - ), - array( + in_array($centerA, $ah->getReachableCenters( + $userA, + 'CHILL_ROLE_2', + null + ), true), + 'center A should be available for userA, with role 2 ', + ], + [ true, - in_array($centerB, $ah->getReachableCenters($userA, - new Role('CHILL_ROLE_2'), null)), - 'center A should be available for userA, with role 2 ' - ), - array( + in_array($centerB, $ah->getReachableCenters( + $userA, + 'CHILL_ROLE_2', + null + ), true), + 'center A should be available for userA, with role 2 ', + ], + [ false, - in_array($centerB, $ah->getReachableCenters($userA, - new Role('CHILL_ROLE_1'), null)), - 'center B should NOT be available for userA, with role 1 ' - ), - // with scope - array( + in_array($centerB, $ah->getReachableCenters( + $userA, + 'CHILL_ROLE_1', + null + ), true), + 'center B should NOT be available for userA, with role 1 ', + ], + // with scope + [ true, - in_array($centerA, $ah->getReachableCenters($userA, - new Role('CHILL_ROLE_1'), $scopeB)), - 'center A should be available for userA, with role 1, scopeC ' - ), - array( + in_array($centerA, $ah->getReachableCenters( + $userA, + 'CHILL_ROLE_1', + $scopeB + ), true), + 'center A should be available for userA, with role 1, scopeC ', + ], + [ false, - in_array($centerA, $ah->getReachableCenters($userA, - new Role('CHILL_ROLE_2'), $scopeC)), - 'center A should NOT be available for userA, with role 2, scopeA ' - ), - array( + in_array($centerA, $ah->getReachableCenters( + $userA, + 'CHILL_ROLE_2', + $scopeC + ), true), + 'center A should NOT be available for userA, with role 2, scopeA ', + ], + [ true, - in_array($centerB, $ah->getReachableCenters($userA, - new Role('CHILL_ROLE_2'), $scopeA)), - 'center B should be available for userA, with role 2, scopeA ' - ), - ); - - } - - /** - * - * @dataProvider dataProvider_getReachableScopes - * @param boolean $expectedResult - * @param Scope $testedScope - * @param User $user - * @param Role $role - * @param Center $center - * @param string $message - */ - public function testGetReachableScopes($expectedResult, Scope $testedScope, - User $user, Role $role, Center $center, $message) - { - $reachableScopes = $this->getAuthorizationHelper() - ->getReachableScopes($user, $role, $center); - - $this->assertEquals($expectedResult, in_array($testedScope, $reachableScopes), - $message); + in_array($centerB, $ah->getReachableCenters( + $userA, + 'CHILL_ROLE_2', + $scopeA + ), true), + 'center B should be available for userA, with role 2, scopeA ', + ], + ]; } public function dataProvider_getReachableScopes() @@ -489,52 +148,69 @@ class AuthorizationHelperTest extends KernelTestCase $scopeB = $this->prepareScope(2, 'scope B'); $scopeC = $this->prepareScope(3, 'scope C'); - $userA = $this->prepareUser(array( - array( + $userA = $this->prepareUser([ + [ 'center' => $centerA, - 'permissionsGroup' => array( + 'permissionsGroup' => [ ['scope' => $scopeB, 'role' => 'CHILL_ROLE_1'], - ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'] - ) - ), - array( - 'center' => $centerB, - 'permissionsGroup' => array( - ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'], - ['scope' => $scopeC, 'role' => 'CHILL_ROLE_2'], - ['scope' => $scopeB, 'role' => 'CHILL_ROLE_2'] - ) - ) + ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'], + ], + ], + [ + 'center' => $centerB, + 'permissionsGroup' => [ + ['scope' => $scopeA, 'role' => 'CHILL_ROLE_2'], + ['scope' => $scopeC, 'role' => 'CHILL_ROLE_2'], + ['scope' => $scopeB, 'role' => 'CHILL_ROLE_2'], + ], + ], + ]); - )); - - return array( - array( + return [ + [ true, $scopeA, $userA, - new Role('CHILL_ROLE_2'), + 'CHILL_ROLE_2', $centerA, - "Assert that a scope is found within accessible scopes" - ), - array( + 'Assert that a scope is found within accessible scopes', + ], + [ false, $scopeB, $userA, - new Role('CHILL_ROLE_2'), + 'CHILL_ROLE_2', $centerA, - "Assert that a scope not reachable is NOT found within accessible scopes" - ), - array( + 'Assert that a scope not reachable is NOT found within accessible scopes', + ], + [ false, $scopeB, $userA, - new Role('CHILL_ROLE_1'), + 'CHILL_ROLE_1', $centerB, - "Assert that a scope not reachable is not found within accessible scopes." - . " Trying on filter centering" - ) - ); + 'Assert that a scope not reachable is not found within accessible scopes.' + . ' Trying on filter centering', + ], + ]; + } + + public function testFindUsersReaching() + { + $centerA = self::$kernel->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository(Center::class) + ->findOneByName('Center A'); + + $users = $this->getAuthorizationHelper() + ->findUsersReaching( + 'CHILL_PERSON_SEE', + $centerA + ); + + $usernames = array_map(static function (User $u) { return $u->getUsername(); }, $users); + + $this->assertContains('center a_social', $usernames); } public function testGetParentRoles() @@ -542,24 +218,368 @@ class AuthorizationHelperTest extends KernelTestCase $parentRoles = $this->getAuthorizationHelper() ->getParentRoles('CHILL_INHERITED_ROLE_1'); - $this->assertContains('CHILL_MASTER_ROLE', $parentRoles, - "Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`"); + $this->assertContains( + 'CHILL_MASTER_ROLE', + $parentRoles, + 'Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`' + ); } - public function testFindUsersReaching() + /** + * @dataProvider dataProvider_getReachableCenters + * + * @param mixed $test + * @param mixed $result + * @param mixed $msg + */ + public function testGetReachableCenters($test, $result, $msg) { - $centerA = static::$kernel->getContainer() - ->get('doctrine.orm.entity_manager') - ->getRepository(Center::class) - ->findOneByName('Center A'); - - $users = $this->getAuthorizationHelper() - ->findUsersReaching(new Role('CHILL_PERSON_SEE'), - $centerA); - - $usernames = \array_map(function(User $u) { return $u->getUsername(); }, $users); - - $this->assertContains('center a_social', $usernames); + $this->assertEquals($test, $result, $msg); } + /** + * @dataProvider dataProvider_getReachableScopes + * + * @param bool $expectedResult + * @param string $message + */ + public function testGetReachableScopes( + $expectedResult, + Scope $testedScope, + User $user, + string $role, + Center $center, + $message + ) { + $reachableScopes = $this->getAuthorizationHelper() + ->getReachableScopes($user, $role, $center); + + $this->assertEquals( + $expectedResult, + in_array($testedScope, $reachableScopes, true), + $message + ); + } + + public function testtestUserHasAccessUserShouldHaveAccessEntityWithScope() + { + $center = $this->prepareCenter(1, 'center'); + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); + $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); + $entity->getCenter()->willReturn($center); + $entity->getScope()->willReturn($scope); + + $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + /** + * Test function userCanReach of helper. + * + * A user can not reachcenter =>W the function should return false + */ + public function testUserCanReachCenterUserShouldNotReach() + { + $centerA = $this->prepareCenter(1, 'center'); + $centerB = $this->prepareCenter(2, 'centerB'); + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $centerA, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'ANY_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + + $this->assertFalse($helper->userCanReachCenter($user, $centerB)); + } + + /** + * Test function userCanReach of helper. + * + * A user can reach center => the function should return true. + */ + public function testUserCanReachCenterUserShouldReach() + { + $center = $this->prepareCenter(1, 'center'); + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'ANY_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + + $this->assertTrue($helper->userCanReachCenter($user, $center)); + } + + public function testUserHasAccessEntityMultiScope() + { + $centerA = $this->prepareCenter(1, 'center'); + $centerB = $this->prepareCenter(1, 'centerB'); + $scopeA = $this->prepareScope(2, 'other'); //the user will be granted this scope + $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope + $user = $this->prepareUser([ + [ + 'center' => $centerA, 'permissionsGroup' => [ + ['scope' => $scopeA, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement(HasCentersInterface::class); + $entity->willImplement(HasScopesInterface::class); + $entity->getCenters()->willReturn([$centerA, $centerB]); + $entity->getScopes()->willReturn([$scopeA, $scopeB]); + + $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + public function testUserHasAccessMultiCenterEntityWithoutScope() + { + $center = $this->prepareCenter(1, 'center'); + $centerB = $this->prepareCenter(1, 'centerB'); + $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scopeB, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement(HasCentersInterface::class); + $entity->getCenters()->willReturn([$center, $centerB]); + + $this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + public function testUserHasAccessShouldHaveAccessEntityWithoutScope() + { + $center = $this->prepareCenter(1, 'center'); + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); + $entity->getCenter()->willReturn($center); + + $this->assertTrue($helper->userHasAccess( + $user, + $entity->reveal(), + 'CHILL_ROLE' + )); + } + + public function testUserHasAccessShouldHaveAccessWithInheritanceEntityWithoutScope() + { + $center = $this->prepareCenter(1, 'center'); + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'CHILL_MASTER_ROLE'], + ], + ], + ]); + + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); + $entity->getCenter()->willReturn($center); + + $this->assertTrue($helper->userHasAccess( + $user, + $entity->reveal(), + 'CHILL_INHERITED_ROLE_1' + )); + } + + public function testUserHasAccessUserHasNoCenterEntityWithScope() + { + $centerA = $this->prepareCenter(1, 'center'); //the user will have this center + $centerB = $this->prepareCenter(2, 'centerB'); //the entity will have another center + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $centerA, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); + $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); + $entity->getCenter()->willReturn($centerB); + $entity->getScope()->willReturn($scope); + + $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + public function testuserHasAccessUserHasNoRoleEntityWithoutScope() + { + $center = $this->prepareCenter(1, 'center'); + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'ANY_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); + $entity->getCenter()->willReturn($center); + + $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + public function testUserHasAccessUserHasNoRoleEntityWithScope() + { + $center = $this->prepareCenter(1, 'center'); + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); + $entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface'); + $entity->getCenter()->willReturn($center); + $entity->getScope()->willReturn($scope); + + $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'ANOTHER_ROLE')); + } + + /** + * test that a user has no access on a entity, but is granted on the same role + * on another center. + */ + public function testUserHasAccessUserHasNoRoleUserHasRoleOnAnotherCenterEntityWithoutScope() + { + $centerA = $this->prepareCenter(1, 'center'); + $centerB = $this->prepareCenter(2, 'centerB'); + $scope = $this->prepareScope(1, 'default'); + $user = $this->prepareUser([ + [ + 'center' => $centerA, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'ANY_ROLE'], + ], + [ + 'centerB' => $centerB, 'permissionsGroup' => [ + ['scope' => $scope, 'role' => 'ANY_ROLE'], + ['scope' => $scope, 'role' => 'CHILL_ROLE'], + ], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface'); + $entity->getCenter()->willReturn($centerA); + + $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + public function testUserHasAccessUserHasNoScopeEntityWithScope() + { + $center = $this->prepareCenter(1, 'center'); + $scopeA = $this->prepareScope(1, 'default'); //the entity will have this scope + $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scopeB, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement(HasCenterInterface::class); + $entity->willImplement(HasScopeInterface::class); + $entity->getCenter()->willReturn($center); + $entity->getScope()->willReturn($scopeA); + + $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + public function testUserHasNoAccessEntityMultiScope() + { + $centerA = $this->prepareCenter(1, 'center'); + $centerB = $this->prepareCenter(1, 'centerB'); + $scopeA = $this->prepareScope(2, 'other'); //the user will be granted this scope + $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope + $scopeC = $this->prepareScope(2, 'other'); //the user will be granted this scope + $user = $this->prepareUser([ + [ + 'center' => $centerA, 'permissionsGroup' => [ + ['scope' => $scopeA, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement(HasCentersInterface::class); + $entity->willImplement(HasScopesInterface::class); + $entity->getCenters()->willReturn([$centerA, $centerB]); + $entity->getScopes()->willReturn([$scopeB, $scopeC]); + + $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + public function testUserHasNoAccessMultiCenterEntityWithoutScope() + { + $center = $this->prepareCenter(1, 'center'); + $centerB = $this->prepareCenter(1, 'centerB'); + $centerC = $this->prepareCenter(1, 'centerC'); + $scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope + $user = $this->prepareUser([ + [ + 'center' => $center, 'permissionsGroup' => [ + ['scope' => $scopeB, 'role' => 'CHILL_ROLE'], + ], + ], + ]); + $helper = $this->getAuthorizationHelper(); + $entity = $this->getProphet()->prophesize(); + $entity->willImplement(HasCentersInterface::class); + $entity->getCenters()->willReturn([$centerB, $centerC]); + + $this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE')); + } + + /** + * @return \Chill\MainBundle\Security\Authorization\AuthorizationHelper + */ + private function getAuthorizationHelper() + { + return self::$container + ->get('chill.main.security.authorization.helper'); + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Security/PasswordRecover/TokenManagerTest.php b/src/Bundle/ChillMainBundle/Tests/Security/PasswordRecover/TokenManagerTest.php index abb36459b..4bb6c6743 100644 --- a/src/Bundle/ChillMainBundle/Tests/Security/PasswordRecover/TokenManagerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Security/PasswordRecover/TokenManagerTest.php @@ -1,58 +1,52 @@ - * - * 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\MainBundle\Tests\PasswordRecover; - -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Chill\MainBundle\Security\PasswordRecover\TokenManager; -use Chill\MainBundle\Entity\User; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ -class TokenManagerTest extends KernelTestCase + +declare(strict_types=1); + +namespace Chill\MainBundle\Tests\PasswordRecover; + +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Security\PasswordRecover\TokenManager; +use DateInterval; +use DateTimeImmutable; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + +/** + * @internal + * @coversNothing + */ +final class TokenManagerTest extends KernelTestCase { protected $tokenManager; - - public static function setUpBefore() - { - - } - + public function setUp() { self::bootKernel(); - + $logger = self::$container ->get('logger'); - + $this->tokenManager = new TokenManager('secret', $logger); } - public function testGenerate() + public static function setUpBefore() + { + } + + public function testGenerate() { $tokenManager = $this->tokenManager; $user = (new User())->setUsernameCanonical('test'); - $expiration = new \DateTimeImmutable('tomorrow'); - + $expiration = new DateTimeImmutable('tomorrow'); + $tokens = $tokenManager->generate($user, $expiration); - + $this->assertInternalType('array', $tokens); $this->assertArrayHasKey('h', $tokens); $this->assertArrayHasKey('t', $tokens); @@ -60,57 +54,57 @@ class TokenManagerTest extends KernelTestCase $this->assertNotEmpty($tokens['t']); $this->assertEquals($user->getUsernameCanonical(), $tokens['u']); } - + /** * @expectedException \UnexpectedValueException */ - public function testGenerateEmptyUsernameCanonical() + public function testGenerateEmptyUsernameCanonical() { $tokenManager = $this->tokenManager; // set a username, but not a username canonical $user = (new User())->setUsername('test'); - $expiration = new \DateTimeImmutable('tomorrow'); - + $expiration = new DateTimeImmutable('tomorrow'); + $tokenManager->generate($user, $expiration); } - + public function testVerify() { $tokenManager = $this->tokenManager; $user = (new User())->setUsernameCanonical('test'); - $expiration = new \DateTimeImmutable('tomorrow'); - + $expiration = new DateTimeImmutable('tomorrow'); + $tokens = $tokenManager->generate($user, $expiration); - + $hash = $tokens[TokenManager::HASH]; $token = $tokens[TokenManager::TOKEN]; $timestamp = $tokens[TokenManager::TIMESTAMP]; - + $verification = $tokenManager->verify($hash, $token, $user, $timestamp); - + $this->assertTrue($verification); - + // test with altering token - $this->assertFalse($tokenManager->verify($hash.'5', $token, $user, $timestamp)); - $this->assertFalse($tokenManager->verify($hash, $token.'25', $user, $timestamp)); + $this->assertFalse($tokenManager->verify($hash . '5', $token, $user, $timestamp)); + $this->assertFalse($tokenManager->verify($hash, $token . '25', $user, $timestamp)); $this->assertFalse($tokenManager->verify($hash, $token, $user->setUsernameCanonical('test2'), $timestamp)); - $this->assertFalse($tokenManager->verify($hash, $token, $user, $timestamp+1)); + $this->assertFalse($tokenManager->verify($hash, $token, $user, (string) ($timestamp + 1))); } - + public function testVerifyExpiredFails() { $tokenManager = $this->tokenManager; $user = (new User())->setUsernameCanonical('test'); - $expiration = (new \DateTimeImmutable('now'))->sub(new \DateInterval('PT1S')); - + $expiration = (new DateTimeImmutable('now'))->sub(new DateInterval('PT1S')); + $tokens = $tokenManager->generate($user, $expiration); - + $hash = $tokens[TokenManager::HASH]; $token = $tokens[TokenManager::TOKEN]; $timestamp = $tokens[TokenManager::TIMESTAMP]; - + $verification = $tokenManager->verify($hash, $token, $user, $timestamp); - + $this->assertFalse($verification); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Security/Resolver/CenterResolverDispatcherTest.php b/src/Bundle/ChillMainBundle/Tests/Security/Resolver/CenterResolverDispatcherTest.php index 851183e3d..80b01a170 100644 --- a/src/Bundle/ChillMainBundle/Tests/Security/Resolver/CenterResolverDispatcherTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Security/Resolver/CenterResolverDispatcherTest.php @@ -1,14 +1,28 @@ dispatcher->resolveCenter($center); + $resolved = $this->dispatcher->resolveCenter($center); - $this->assertSame($center, $resolved); + $this->assertSame($center, $resolved); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Security/Resolver/DefaultScopeResolverTest.php b/src/Bundle/ChillMainBundle/Tests/Security/Resolver/DefaultScopeResolverTest.php index de6d19b5a..32c07b04e 100644 --- a/src/Bundle/ChillMainBundle/Tests/Security/Resolver/DefaultScopeResolverTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Security/Resolver/DefaultScopeResolverTest.php @@ -1,5 +1,14 @@ scope = $scope; } @@ -30,7 +43,6 @@ class DefaultScopeResolverTest extends TestCase { return $this->scope; } - }; $this->assertTrue($this->scopeResolver->supports($entity)); @@ -41,8 +53,8 @@ class DefaultScopeResolverTest extends TestCase public function testHasScopesInterface() { $entity = new class($scopeA = new Scope(), $scopeB = new Scope()) implements HasScopesInterface { - - public function __construct(Scope $scopeA, Scope $scopeB) { + public function __construct(Scope $scopeA, Scope $scopeB) + { $this->scopes = [$scopeA, $scopeB]; } @@ -58,5 +70,4 @@ class DefaultScopeResolverTest extends TestCase $this->assertSame($scopeA, $this->scopeResolver->resolveScope($entity)[0]); $this->assertSame($scopeB, $this->scopeResolver->resolveScope($entity)[1]); } - } diff --git a/src/Bundle/ChillMainBundle/Tests/Security/Resolver/ScopeResolverDispatcherTest.php b/src/Bundle/ChillMainBundle/Tests/Security/Resolver/ScopeResolverDispatcherTest.php index 2a36fd365..320d2e326 100644 --- a/src/Bundle/ChillMainBundle/Tests/Security/Resolver/ScopeResolverDispatcherTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Security/Resolver/ScopeResolverDispatcherTest.php @@ -1,5 +1,14 @@ scope = $scope; } @@ -31,7 +44,6 @@ class DefaultScopeResolverDispatcherTest extends TestCase { return $this->scope; } - }; $this->assertTrue($this->scopeResolverDispatcher->isConcerned($entity)); @@ -41,8 +53,8 @@ class DefaultScopeResolverDispatcherTest extends TestCase public function testHasScopesInterface() { $entity = new class($scopeA = new Scope(), $scopeB = new Scope()) implements HasScopesInterface { - - public function __construct(Scope $scopeA, Scope $scopeB) { + public function __construct(Scope $scopeA, Scope $scopeB) + { $this->scopes = [$scopeA, $scopeB]; } diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php new file mode 100644 index 000000000..fc9612299 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php @@ -0,0 +1,112 @@ +prophet = new Prophet(); + } + + public function generateDataNormalize() + { + $datetime = DateTime::createFromFormat('Y-m-d H:i:sO', '2021-06-05 15:05:01+02:00'); + $date = DateTime::createFromFormat('Y-m-d H:i:sO', '2021-06-05 00:00:00+02:00'); + + yield [ + ['datetime' => '2021-06-05T15:05:01+0200'], + $datetime, 'json', null, 'simple normalization to json', + ]; + + yield [ + ['long' => '5 juin 2021', 'short' => '05/06/2021'], + $date, 'docgen', 'fr', 'normalization to docgen for a date, with current request', + ]; + + yield [ + ['long' => '5 juin 2021', 'short' => '05/06/2021'], + $date, 'docgen', null, 'normalization to docgen for a date, without current request', + ]; + + yield [ + ['long' => '5 juin 2021 à 15:05', 'short' => '05/06/2021 15:05'], + $datetime, 'docgen', null, 'normalization to docgen for a datetime, without current request', + ]; + + yield [ + ['long' => '', 'short' => ''], + null, 'docgen', null, 'normalization to docgen for a null datetime', + ]; + } + + /** + * @dataProvider generateDataNormalize + * + * @param mixed $expected + * @param mixed $date + * @param mixed $format + * @param mixed $locale + * @param mixed $msg + */ + public function testNormalize($expected, $date, $format, $locale, $msg) + { + $this->assertEquals($expected, $this->buildDateNormalizer($locale)->normalize($date, $format, []), $msg); + } + + public function testSupports() + { + $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new DateTime(), 'json')); + $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new DateTimeImmutable(), 'json')); + $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new DateTime(), 'docgen')); + $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new DateTimeImmutable(), 'docgen')); + $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => DateTimeImmutable::class])); + $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => DateTimeInterface::class])); + $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => DateTime::class])); + $this->assertFalse($this->buildDateNormalizer()->supportsNormalization(new stdClass(), 'docgen')); + $this->assertFalse($this->buildDateNormalizer()->supportsNormalization(new DateTime(), 'xml')); + } + + private function buildDateNormalizer(?string $locale = null): DateNormalizer + { + $requestStack = $this->prophet->prophesize(RequestStack::class); + $parameterBag = new ParameterBag(); + $parameterBag->set('kernel.default_locale', 'fr'); + + if (null === $locale) { + $requestStack->getCurrentRequest()->willReturn(null); + } else { + $request = $this->prophet->prophesize(Request::class); + $request->getLocale()->willReturn($locale); + $requestStack->getCurrentRequest()->willReturn($request->reveal()); + } + + return new DateNormalizer($requestStack->reveal(), $parameterBag); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php index c4f4bc13d..b45b2c21d 100644 --- a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php @@ -1,38 +1,39 @@ get(EntityManagerInterface::class); + $em = self::$container->get(EntityManagerInterface::class); $serializerFactory = self::$container->get(ClassMetadataFactoryInterface::class); $this->normalizer = new DoctrineExistingEntityNormalizer($em, $serializerFactory); } - /** - * @dataProvider dataProviderUserId - */ - public function testGetMappedClass($userId) - { - $data = [ 'type' => 'user', 'id' => $userId]; - $supports = $this->normalizer->supportsDenormalization($data, User::class); - - $this->assertTrue($supports); - } - public function dataProviderUserId() { self::bootKernel(); @@ -43,9 +44,21 @@ class DoctrineExistingEntityNormalizerTest extends KernelTestCase ->select('u.id') ->setMaxResults(1) ->getQuery() - ->getResult() - ; + ->getResult(); - yield [ $userIds[0]['id'] ]; + yield [$userIds[0]['id']]; + } + + /** + * @dataProvider dataProviderUserId + * + * @param mixed $userId + */ + public function testGetMappedClass($userId) + { + $data = ['type' => 'user', 'id' => $userId]; + $supports = $this->normalizer->supportsDenormalization($data, User::class); + + $this->assertTrue($supports); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Services/MenuComposerTest.php b/src/Bundle/ChillMainBundle/Tests/Services/MenuComposerTest.php index 98bf23386..bb98b074d 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/MenuComposerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/MenuComposerTest.php @@ -1,46 +1,53 @@ + * @internal + * @coversNothing */ -class MenuComposerTest extends KernelTestCase +final class MenuComposerTest extends KernelTestCase { - /** - * * @var \Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader; */ private $loader; - + /** - * * @var \Chill\MainBundle\DependencyInjection\Services\MenuComposer; */ private $menuComposer; - + public function setUp() { - self::bootKernel(array('environment' => 'test')); - $this->menuComposer = static::$container - ->get('chill.main.menu_composer'); + self::bootKernel(['environment' => 'test']); + $this->menuComposer = self::$container + ->get('chill.main.menu_composer'); } - + /** * @covers \Chill\MainBundle\Routing\MenuComposer */ public function testMenuComposer() { $collection = new RouteCollection(); - + $routes = $this->menuComposer->getRoutesFor('dummy0'); - + $this->assertInternalType('array', $routes); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Templating/ChillMarkdownRenderExtensionTest.php b/src/Bundle/ChillMainBundle/Tests/Templating/ChillMarkdownRenderExtensionTest.php index d95969b9d..89edc17d6 100644 --- a/src/Bundle/ChillMainBundle/Tests/Templating/ChillMarkdownRenderExtensionTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Templating/ChillMarkdownRenderExtensionTest.php @@ -1,76 +1,71 @@ - * - * 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\MainBundle\Tests\Templating; - -use PHPUnit\Framework\TestCase; -use Chill\MainBundle\Templating\ChillMarkdownRenderExtension; /** - * Test the service ChillMarkdownRenderExtension - * - * @internal we do not want to test the markdown transformation. We just want to - * test that the markdown is correctly transformed into html, and that the html - * is safe. + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ -class ChillMarkdownRenderExtensionTest extends TestCase -{ - - private const SIMPLE_MARKDOWN = <<test -

    Text.

    -HTML; - - private const UNAUTHORIZED_MARKDOWN = <<alert("ok"); -MD; - - private const UNAUTHORIZED_HTML = <<<script>alert("ok");</script>

    -HTML; - +declare(strict_types=1); + +namespace Chill\MainBundle\Tests\Templating; + +use Chill\MainBundle\Templating\ChillMarkdownRenderExtension; +use PHPUnit\Framework\TestCase; + +/** + * Test the service ChillMarkdownRenderExtension. + * + * @internal we do not want to test the markdown transformation. We just want to + * test that the markdown is correctly transformed into html, and that the html + * is safe. + * @coversNothing + */ +final class ChillMarkdownRenderExtensionTest extends TestCase +{ + private const SIMPLE_HTML = <<<'HTML' +

    test

    +

    Text.

    + HTML; + + private const SIMPLE_MARKDOWN = <<<'MD' + # test + + Text. + MD; + + private const UNAUTHORIZED_HTML = <<<'HTML' +

    <script>alert("ok");</script>

    + HTML; + + private const UNAUTHORIZED_MARKDOWN = <<<'MD' + + MD; + /** - * Test that the markdown input is transformed into html + * Test that the markdown input is transformed into html. */ public function testRendering() { $extension = new ChillMarkdownRenderExtension(); - - $this->assertEquals(self::SIMPLE_HTML, - $extension->renderMarkdownToHtml(self::SIMPLE_MARKDOWN)); + + $this->assertEquals( + self::SIMPLE_HTML, + $extension->renderMarkdownToHtml(self::SIMPLE_MARKDOWN) + ); } - + /** - * Test that the output of the markdown content is sanitized + * Test that the output of the markdown content is sanitized. */ public function testSecurity() { $extension = new ChillMarkdownRenderExtension(); - - $this->assertEquals(self::UNAUTHORIZED_HTML, - $extension->renderMarkdownToHtml(self::UNAUTHORIZED_MARKDOWN)); + + $this->assertEquals( + self::UNAUTHORIZED_HTML, + $extension->renderMarkdownToHtml(self::UNAUTHORIZED_MARKDOWN) + ); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Templating/Entity/AddressRenderTest.php b/src/Bundle/ChillMainBundle/Tests/Templating/Entity/AddressRenderTest.php index 79551d23c..c866224e3 100644 --- a/src/Bundle/ChillMainBundle/Tests/Templating/Entity/AddressRenderTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Templating/Entity/AddressRenderTest.php @@ -1,22 +1,53 @@ setName(['fr' => 'Pays']) + ->setCountryCode('BE'); + $postCode = new PostalCode(); + $postCode->setName('Locality') + ->setCode('012345') + ->setCountry($country); + + $addr->setStreet('Rue ABC') + ->setStreetNumber('5') + ->setPostcode($postCode); + + yield [$addr, 'Rue ABC, 5 - 012345 Locality']; + } + /** * @dataProvider addressDataProvider */ @@ -26,30 +57,8 @@ class AddressRenderTest extends KernelTestCase $renderer = new AddressRender($engine); $this->assertEquals($expectedString, $renderer->renderString($addr, [])); + return; $this->assertIsString($renderer->renderBox($addr, [])); - } - - - public function addressDataProvider(): \Iterator - { - $addr = new Address(); - $country = (new Country()) - ->setName([ "fr" => "Pays" ]) - ->setCountryCode("BE") - ; - $postCode = new PostalCode(); - $postCode->setName("Locality") - ->setCode("012345") - ->setCountry($country) - ; - - $addr->setStreet("Rue ABC") - ->setStreetNumber("5") - ->setPostcode($postCode) - ; - - yield[ $addr, "Rue ABC, 5 - 012345 Locality"]; } - } diff --git a/src/Bundle/ChillMainBundle/Tests/TestHelper.php b/src/Bundle/ChillMainBundle/Tests/TestHelper.php index a969eeeb3..205474685 100644 --- a/src/Bundle/ChillMainBundle/Tests/TestHelper.php +++ b/src/Bundle/ChillMainBundle/Tests/TestHelper.php @@ -1,47 +1,38 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Tests; use Symfony\Component\BrowserKit\Client; /** - * Provide useful methods for tests - * - * @author Julien Fastré - * @author Champs Libres + * Provide useful methods for tests. */ class TestHelper { /** - * create a client authenticated with an user - * - * @param WebTestCase $testCase + * create a client authenticated with an user. + * + * @param mixed $username + * @param mixed $password + * * @return \Symfony\Component\BrowserKit\Client authenticated client */ - public static function getAuthenticatedClientOptions($username = 'center a_social', - $password = 'password') - { - return array( - 'PHP_AUTH_USER' => $username, - 'PHP_AUTH_PW' => $password, - ); + public static function getAuthenticatedClientOptions( + $username = 'center a_social', + $password = 'password' + ) { + return [ + 'PHP_AUTH_USER' => $username, + 'PHP_AUTH_PW' => $password, + ]; } } diff --git a/src/Bundle/ChillMainBundle/Tests/Util/CountriesInfoTest.php b/src/Bundle/ChillMainBundle/Tests/Util/CountriesInfoTest.php index 955ec4382..6fd0facd4 100644 --- a/src/Bundle/ChillMainBundle/Tests/Util/CountriesInfoTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Util/CountriesInfoTest.php @@ -1,48 +1,60 @@ assertStringStartsWith("AS AF AFG 004 Afghanistan, Islamic Republic of", - $raw); + $continents = CountriesInfo::getContinentsCodes(); + + $this->assertContains('EU', $continents); } - - public function testGetArrayCountriesData() + + public function testGetArrayCountriesData() { $data = CountriesInfo::getArrayCountriesData(); - + $this->assertNotNull($data); - $this->assertContains(array( - "AS", "AF", "AFG", "004", "Afghanistan, Islamic Republic of" - ), $data); + $this->assertContains([ + 'AS', 'AF', 'AFG', '004', 'Afghanistan, Islamic Republic of', + ], $data); } - + public function testGetCountryCodeByContinents() { $countries = CountriesInfo::getCountriesCodeByContinent('EU'); - + $this->assertContains('BE', $countries); $this->assertContains('FR', $countries); $this->assertContains('GB', $countries); } - - public function getGetContinentsCodes() + + public function testGetCountryData() { - $continents = CountriesInfo::getContinentsCodes(); - - $this->assertContains('EU', $continents); + $raw = CountriesInfo::getCountriesData(); + + $this->assertStringStartsWith( + 'AS AF AFG 004 Afghanistan, Islamic Republic of', + $raw + ); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Util/DateRangeCoveringTest.php b/src/Bundle/ChillMainBundle/Tests/Util/DateRangeCoveringTest.php index c06b4a4f4..694677a8f 100644 --- a/src/Bundle/ChillMainBundle/Tests/Util/DateRangeCoveringTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Util/DateRangeCoveringTest.php @@ -1,35 +1,49 @@ add(new \DateTime('2010-01-01'), new \DateTime('2010-12-01'), 1) - ->add(new \DateTime('2010-06-01'), new \DateTime('2011-06-01'), 2) - ->add(new \DateTime('2019-06-01'), new \DateTime('2019-06-01'), 3) - ->compute() - ; + ->add(new DateTime('2010-01-01'), new DateTime('2010-12-01'), 1) + ->add(new DateTime('2010-06-01'), new DateTime('2011-06-01'), 2) + ->add(new DateTime('2019-06-01'), new DateTime('2019-06-01'), 3) + ->compute(); $this->assertTrue($cover->hasIntersections()); $this->assertIsArray($cover->getIntersections()); $this->assertCount(1, $cover->getIntersections()); $this->assertEquals( - new \DateTime('2010-06-01'), + new DateTime('2010-06-01'), $cover->getIntersections()[0][0], - "assert date start are the intersection" + 'assert date start are the intersection' ); $this->assertEquals( - new \DateTime('2010-12-01'), + new DateTime('2010-12-01'), $cover->getIntersections()[0][1], - "assert date end are the intersection" + 'assert date end are the intersection' ); $this->assertIsArray($cover->getIntersections()[0][2]); $this->assertContains(1, $cover->getIntersections()[0][2]); @@ -37,16 +51,26 @@ class DateRangeCoveringTest extends TestCase $this->assertNotContains(3, $cover->getIntersections()[0][2]); } + public function testCoveringWithMinCover1NoCoveringWithNullDates() + { + $cover = new DateRangeCovering(1, new DateTimeZone('Europe/Brussels')); + $cover + ->add(new DateTime('2021-10-05'), new DateTime('2021-10-18'), 521) + ->add(new DateTime('2021-10-26'), null, 663) + ->compute(); + + $this->assertFalse($cover->hasIntersections()); + } + public function testCoveringWithMinCover1WithTwoIntersections() { - $cover = new DateRangeCovering(1, new \DateTimeZone('Europe/Brussels')); + $cover = new DateRangeCovering(1, new DateTimeZone('Europe/Brussels')); $cover - ->add(new \DateTime('2010-01-01'), new \DateTime('2010-12-01'), 1) - ->add(new \DateTime('2010-06-01'), new \DateTime('2011-06-01'), 2) - ->add(new \DateTime('2019-01-01'), new \DateTime('2019-12-01'), 3) - ->add(new \DateTime('2019-06-01'), new \DateTime('2020-06-01'), 4) - ->compute() - ; + ->add(new DateTime('2010-01-01'), new DateTime('2010-12-01'), 1) + ->add(new DateTime('2010-06-01'), new DateTime('2011-06-01'), 2) + ->add(new DateTime('2019-01-01'), new DateTime('2019-12-01'), 3) + ->add(new DateTime('2019-06-01'), new DateTime('2020-06-01'), 4) + ->compute(); $this->assertTrue($cover->hasIntersections()); $this->assertIsArray($cover->getIntersections()); @@ -55,7 +79,7 @@ class DateRangeCoveringTest extends TestCase $intersections = $cover->getIntersections(); // sort the intersections to compare them in expected order - \usort($intersections, function($a, $b) { + usort($intersections, static function ($a, $b) { if ($a[0] === $b[0]) { return $a[1] <=> $b[1]; } @@ -65,14 +89,14 @@ class DateRangeCoveringTest extends TestCase // first intersection $this->assertEquals( - new \DateTime('2010-06-01'), + new DateTime('2010-06-01'), $intersections[0][0], - "assert date start are the intersection" + 'assert date start are the intersection' ); $this->assertEquals( - new \DateTime('2010-12-01'), + new DateTime('2010-12-01'), $intersections[0][1], - "assert date end are the intersection" + 'assert date end are the intersection' ); $this->assertIsArray($intersections[0][2]); $this->assertContains(1, $intersections[0][2]); @@ -82,14 +106,14 @@ class DateRangeCoveringTest extends TestCase // second intersection $this->assertEquals( - new \DateTime('2019-06-01'), + new DateTime('2019-06-01'), $intersections[1][0], - "assert date start are the intersection" + 'assert date start are the intersection' ); $this->assertEquals( - new \DateTime('2019-12-01'), + new DateTime('2019-12-01'), $intersections[1][1], - "assert date end are the intersection" + 'assert date end are the intersection' ); $this->assertIsArray($intersections[1][2]); $this->assertContains(3, $intersections[1][2]); @@ -100,28 +124,27 @@ class DateRangeCoveringTest extends TestCase public function testCoveringWithMinCover2() { - $cover = new DateRangeCovering(2, new \DateTimeZone('Europe/Brussels')); + $cover = new DateRangeCovering(2, new DateTimeZone('Europe/Brussels')); $cover - ->add(new \DateTime('2010-01-01'), new \DateTime('2010-10-01'), 1) - ->add(new \DateTime('2010-06-01'), new \DateTime('2010-09-01'), 2) - ->add(new \DateTime('2010-04-01'), new \DateTime('2010-12-01'), 3) - ->add(new \DateTime('2019-01-01'), new \DateTime('2019-10-01'), 4) - ->add(new \DateTime('2019-06-01'), new \DateTime('2019-09-01'), 5) - ->compute() - ; + ->add(new DateTime('2010-01-01'), new DateTime('2010-10-01'), 1) + ->add(new DateTime('2010-06-01'), new DateTime('2010-09-01'), 2) + ->add(new DateTime('2010-04-01'), new DateTime('2010-12-01'), 3) + ->add(new DateTime('2019-01-01'), new DateTime('2019-10-01'), 4) + ->add(new DateTime('2019-06-01'), new DateTime('2019-09-01'), 5) + ->compute(); $this->assertTrue($cover->hasIntersections()); $this->assertIsArray($cover->getIntersections()); $this->assertCount(1, $cover->getIntersections()); $this->assertEquals( - new \DateTime('2010-06-01'), + new DateTime('2010-06-01'), $cover->getIntersections()[0][0], - "assert date start are the intersection" + 'assert date start are the intersection' ); $this->assertEquals( - new \DateTime('2010-09-01'), + new DateTime('2010-09-01'), $cover->getIntersections()[0][1], - "assert date end are the intersection" + 'assert date end are the intersection' ); $this->assertIsArray($cover->getIntersections()[0][2]); $this->assertContains(1, $cover->getIntersections()[0][2]); @@ -133,30 +156,29 @@ class DateRangeCoveringTest extends TestCase public function testCoveringWithMinCover2AndThreePeriodsCovering() { - $cover = new DateRangeCovering(2, new \DateTimeZone('Europe/Brussels')); + $cover = new DateRangeCovering(2, new DateTimeZone('Europe/Brussels')); $cover - ->add(new \DateTime('2010-01-01'), new \DateTime('2010-10-01'), 1) - ->add(new \DateTime('2010-06-01'), new \DateTime('2010-09-01'), 2) - ->add(new \DateTime('2010-04-01'), new \DateTime('2010-12-01'), 3) - ->add(new \DateTime('2009-01-01'), new \DateTime('2010-09-15'), 4) - ->add(new \DateTime('2019-01-01'), new \DateTime('2019-10-01'), 5) - ->add(new \DateTime('2019-06-01'), new \DateTime('2019-09-01'), 6) - ->compute() - ; + ->add(new DateTime('2010-01-01'), new DateTime('2010-10-01'), 1) + ->add(new DateTime('2010-06-01'), new DateTime('2010-09-01'), 2) + ->add(new DateTime('2010-04-01'), new DateTime('2010-12-01'), 3) + ->add(new DateTime('2009-01-01'), new DateTime('2010-09-15'), 4) + ->add(new DateTime('2019-01-01'), new DateTime('2019-10-01'), 5) + ->add(new DateTime('2019-06-01'), new DateTime('2019-09-01'), 6) + ->compute(); $this->assertTrue($cover->hasIntersections()); $this->assertIsArray($cover->getIntersections()); $this->assertCount(1, $cover->getIntersections()); $this->assertEquals( - new \DateTime('2010-04-01'), + new DateTime('2010-04-01'), $cover->getIntersections()[0][0], - "assert date start are the intersection" + 'assert date start are the intersection' ); $this->assertEquals( - new \DateTime('2010-09-15'), + new DateTime('2010-09-15'), $cover->getIntersections()[0][1], - "assert date end are the intersection" + 'assert date end are the intersection' ); $this->assertIsArray($cover->getIntersections()[0][2]); $this->assertContains(1, $cover->getIntersections()[0][2]); @@ -169,46 +191,43 @@ class DateRangeCoveringTest extends TestCase public function testCoveringWithMinCover2AndThreePeriodsCoveringWithNullMetadata() { - $cover = new DateRangeCovering(2, new \DateTimeZone('Europe/Brussels')); + $cover = new DateRangeCovering(2, new DateTimeZone('Europe/Brussels')); $cover - ->add(new \DateTime('2010-01-01'), new \DateTime('2010-10-01'), null) - ->add(new \DateTime('2010-06-01'), new \DateTime('2010-09-01'), null) - ->add(new \DateTime('2010-04-01'), new \DateTime('2010-12-01'), null) - ->add(new \DateTime('2009-01-01'), new \DateTime('2010-09-15'), null) - ->add(new \DateTime('2019-01-01'), new \DateTime('2019-10-01'), null) - ->add(new \DateTime('2019-06-01'), new \DateTime('2019-09-01'), null) - ->compute() - ; + ->add(new DateTime('2010-01-01'), new DateTime('2010-10-01'), null) + ->add(new DateTime('2010-06-01'), new DateTime('2010-09-01'), null) + ->add(new DateTime('2010-04-01'), new DateTime('2010-12-01'), null) + ->add(new DateTime('2009-01-01'), new DateTime('2010-09-15'), null) + ->add(new DateTime('2019-01-01'), new DateTime('2019-10-01'), null) + ->add(new DateTime('2019-06-01'), new DateTime('2019-09-01'), null) + ->compute(); $this->assertTrue($cover->hasIntersections()); $this->assertIsArray($cover->getIntersections()); $this->assertCount(1, $cover->getIntersections()); $this->assertEquals( - new \DateTime('2010-04-01'), + new DateTime('2010-04-01'), $cover->getIntersections()[0][0], - "assert date start are the intersection" + 'assert date start are the intersection' ); $this->assertEquals( - new \DateTime('2010-09-15'), + new DateTime('2010-09-15'), $cover->getIntersections()[0][1], - "assert date end are the intersection" + 'assert date end are the intersection' ); $this->assertIsArray($cover->getIntersections()[0][2]); } - public function testCoveringWithMinCover3Absent() { - $cover = new DateRangeCovering(3, new \DateTimeZone('Europe/Brussels')); + $cover = new DateRangeCovering(3, new DateTimeZone('Europe/Brussels')); $cover - ->add(new \DateTime('2010-01-01'), new \DateTime('2010-10-01'), 1) - ->add(new \DateTime('2010-06-01'), new \DateTime('2010-09-01'), 2) - ->add(new \DateTime('2010-04-01'), new \DateTime('2010-12-01'), 3) - ->add(new \DateTime('2019-01-01'), new \DateTime('2019-10-01'), 4) - ->add(new \DateTime('2019-06-01'), new \DateTime('2019-09-01'), 5) - ->compute() - ; + ->add(new DateTime('2010-01-01'), new DateTime('2010-10-01'), 1) + ->add(new DateTime('2010-06-01'), new DateTime('2010-09-01'), 2) + ->add(new DateTime('2010-04-01'), new DateTime('2010-12-01'), 3) + ->add(new DateTime('2019-01-01'), new DateTime('2019-10-01'), 4) + ->add(new DateTime('2019-06-01'), new DateTime('2019-09-01'), 5) + ->compute(); $this->assertFalse($cover->hasIntersections()); $this->assertIsArray($cover->getIntersections()); $this->assertCount(0, $cover->getIntersections()); diff --git a/src/Bundle/ChillMainBundle/Tests/bootstrap.php b/src/Bundle/ChillMainBundle/Tests/bootstrap.php index 9211155e5..d4e1e46fa 100644 --- a/src/Bundle/ChillMainBundle/Tests/bootstrap.php +++ b/src/Bundle/ChillMainBundle/Tests/bootstrap.php @@ -1,8 +1,16 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Timeline; -use Doctrine\ORM\Query\ResultSetMapping; -use Doctrine\DBAL\Types\Type; +use DateTime; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Doctrine\ORM\Query; -use Doctrine\ORM\NativeQuery; +use Doctrine\ORM\Query\ResultSetMapping; +use LogicException; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use function array_key_exists; +use function is_string; /** - * Build timeline + * Build timeline. */ class TimelineBuilder implements ContainerAwareInterface { - use \Symfony\Component\DependencyInjection\ContainerAwareTrait; - + /** - * * @var \Doctrine\ORM\EntityManagerInterface */ private $em; - + /** - * Record provider - * + * Record provider. + * * This array has the structure `[ 'service id' => $service ]` * * @var TimelineProviderInterface[] */ private $providers = []; - + /** - * Record provider and their context - * + * Record provider and their context. + * * This array has the structure `[ 'context' => [ 'service id' ] ]` * * @var array */ private $providersByContext = []; - + public function __construct(EntityManagerInterface $em) { $this->em = $em; } - - /** - * return an HTML string with timeline - * - * This function must be called from controller - * - * @example https://redmine.champs-libres.coop/projects/chillperson/repository/revisions/bd2e1b1808f73e39532e9538413025df5487cad0/entry/Controller/TimelinePersonController.php#L47 the implementation in person bundle - * - * @param string $context - * @param array $args arguments defined by the bundle which create the context - * @param int $firstItem first item number - * @param int $number number of items by page - * @return string an HTML representation, must be included using `|raw` filter - */ - public function getTimelineHTML($context, array $args, $firstItem = 0, $number = 20) - { - list($union, $parameters) = $this->buildUnionQuery($context, $args); - - //add ORDER BY clause and LIMIT - $query = $union . sprintf(' ORDER BY date DESC LIMIT %d OFFSET %d', - $number, $firstItem); - - // run query and handle results - $fetched = $this->runUnionQuery($query, $parameters); - $entitiesByKey = $this->getEntities($fetched, $context); - - return $this->render($fetched, $entitiesByKey, $context, $args); - } - - /** - * Return the number of items for the given context and args - * - * @param unknown $context - * @param array $args - * @return mixed|\Doctrine\DBAL\Driver\Statement|NULL - */ - public function countItems($context, array $args) - { - $rsm = (new ResultSetMapping()) - ->addScalarResult('total', 'total', Type::INTEGER); - list($select, $parameters) = $this->buildUnionQuery($context, $args); - - // embed the union query inside a count query - $countQuery = sprintf('SELECT COUNT(sq.id) AS total FROM (%s) as sq', $select); - - $nq = $this->em->createNativeQuery($countQuery, $rsm); - $nq->setParameters($parameters); - - return $nq->getSingleScalarResult(); - } - /** - * add a provider id + * add a provider id. * * @internal This function is called by the TimelineCompilerClass * @@ -127,36 +69,142 @@ class TimelineBuilder implements ContainerAwareInterface $this->providersByContext[$context][] = $id; $this->providers[$id] = $provider; } - + /** - * Get providers by context + * Return the number of items for the given context and args. + * + * @param unknown $context + * + * @return \Doctrine\DBAL\Driver\Statement|mixed|null + */ + public function countItems($context, array $args) + { + $rsm = (new ResultSetMapping()) + ->addScalarResult('total', 'total', Types::INTEGER); + + [$select, $parameters] = $this->buildUnionQuery($context, $args); + + // embed the union query inside a count query + $countQuery = sprintf('SELECT COUNT(sq.id) AS total FROM (%s) as sq', $select); + + $nq = $this->em->createNativeQuery($countQuery, $rsm); + $nq->setParameters($parameters); + + return $nq->getSingleScalarResult(); + } + + /** + * Get providers by context. * * @param string $context + * * @return TimelineProviderInterface[] */ public function getProvidersByContext($context) { //throw an exception if no provider have been defined for this context if (!array_key_exists($context, $this->providersByContext)) { - throw new \LogicException(sprintf('No builders have been defined for "%s"' + throw new LogicException(sprintf('No builders have been defined for "%s"' . ' context', $context)); } - + $providers = []; - - foreach($this->providersByContext[$context] as $providerId) { + + foreach ($this->providersByContext[$context] as $providerId) { $providers[] = $this->providers[$providerId]; } - + return $providers; } - + /** - * build the UNION query with all providers + * return an HTML string with timeline. + * + * This function must be called from controller + * + * @example https://redmine.champs-libres.coop/projects/chillperson/repository/revisions/bd2e1b1808f73e39532e9538413025df5487cad0/entry/Controller/TimelinePersonController.php#L47 the implementation in person bundle + * + * @param string $context + * @param array $args arguments defined by the bundle which create the context + * @param int $firstItem first item number + * @param int $number number of items by page + * + * @return string an HTML representation, must be included using `|raw` filter + */ + public function getTimelineHTML($context, array $args, $firstItem = 0, $number = 20) + { + [$union, $parameters] = $this->buildUnionQuery($context, $args); + + //add ORDER BY clause and LIMIT + $query = $union . sprintf( + ' ORDER BY date DESC LIMIT %d OFFSET %d', + $number, + $firstItem + ); + + // run query and handle results + $fetched = $this->runUnionQuery($query, $parameters); + $entitiesByKey = $this->getEntities($fetched, $context); + + return $this->render($fetched, $entitiesByKey, $context, $args); + } + + /** + * Hack to replace the arbitrary "AS" statement in DQL + * into proper SQL query + * TODO remove + * private function replaceASInDQL(string $dql): string + * { + * $pattern = '/^(SELECT\s+[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s{0,},\s{0,}[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s{0,},\s{0,}[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s+FROM.*)/'; + * $replacements = '${1} AS id ${3} AS type ${5} AS date ${7}';. + * + * $s = \preg_replace($pattern, $replacements, $dql, 1); + * + * if (NULL === $s) { + * throw new \RuntimeException('Could not replace the "AS" statement produced by '. + * 'DQL with normal SQL AS: '.$dql); + * } + * + * return $s; + * } + * + * @param mixed $data + */ + + /** + * return the SQL SELECT query as a string,. + * + * @return array: first parameter is the sql string, second an array with parameters + */ + private function buildSelectQuery($data): array + { + return [$data->buildSql(), $data->getParameters()]; + // dead code + $parameters = []; + + $sql = sprintf( + 'SELECT %s AS id, ' + . '%s AS "date", ' + . "'%s' AS type " + . 'FROM %s ' + . 'WHERE %s', + $data['id'], + $data['date'], + $data['type'], + $data['FROM'], + is_string($data['WHERE']) ? $data['WHERE'] : $data['WHERE'][0] + ); + + return [$sql, $data['WHERE'][1]]; + } + + /** + * build the UNION query with all providers. * * @uses self::buildSelectQuery to build individual SELECT queries * - * @throws \LogicException if no builder have been defined for this context + * @throws LogicException if no builder have been defined for this context + * * @return array, where first element is the query, the second one an array with the parameters */ private function buildUnionQuery(string $context, array $args): array @@ -165,146 +213,48 @@ class TimelineBuilder implements ContainerAwareInterface $union = ''; $parameters = []; - foreach($this->getProvidersByContext($context) as $provider) { + foreach ($this->getProvidersByContext($context) as $provider) { $data = $provider->fetchQuery($context, $args); - list($select, $selectParameters) = $this->buildSelectQuery($data); - $append = empty($union) ? $select : ' UNION '.$select; + [$select, $selectParameters] = $this->buildSelectQuery($data); + $append = empty($union) ? $select : ' UNION ' . $select; $union .= $append; $parameters = array_merge($parameters, $selectParameters); } - + return [$union, $parameters]; } /** - * Hack to replace the arbitrary "AS" statement in DQL - * into proper SQL query - * TODO remove - private function replaceASInDQL(string $dql): string - { - $pattern = '/^(SELECT\s+[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s{0,},\s{0,}[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s{0,},\s{0,}[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s+FROM.*)/'; - $replacements = '${1} AS id ${3} AS type ${5} AS date ${7}'; - - $s = \preg_replace($pattern, $replacements, $dql, 1); - - if (NULL === $s) { - throw new \RuntimeException('Could not replace the "AS" statement produced by '. - 'DQL with normal SQL AS: '.$dql); - } - - return $s; - } - */ - - /** - * return the SQL SELECT query as a string, - * - * @return array: first parameter is the sql string, second an array with parameters - */ - private function buildSelectQuery($data): array - { - return [$data->buildSql(), $data->getParameters()]; - - // dead code - $parameters = []; - - $sql = sprintf( - 'SELECT %s AS id, ' - . '%s AS "date", ' - . "'%s' AS type " - . 'FROM %s ' - . 'WHERE %s', - $data['id'], - $data['date'], - $data['type'], - $data['FROM'], - is_string($data['WHERE']) ? $data['WHERE'] : $data['WHERE'][0] - ); - - return [$sql, $data['WHERE'][1]]; - - } - - /** - * run the UNION query and return result as an array - * - * @return array an array with the results - */ - private function runUnionQuery(string $query, array $parameters): array - { - $resultSetMapping = (new ResultSetMapping()) - ->addScalarResult('id', 'id') - ->addScalarResult('type', 'type') - ->addScalarResult('date', 'date'); - - return $this->em->createNativeQuery($query, $resultSetMapping) - ->setParameters($parameters) - ->getArrayResult(); - } - - /** - * - * @param array $queriedIds * @param string $context + * * @return array with the form array($type => [$entity, $entity, $entity]) */ private function getEntities(array $queriedIds, $context) { //gather entities by type to pass all id with same type to the TimelineProvider. - $idsByType = array(); - - foreach($queriedIds as $result) { + $idsByType = []; + + foreach ($queriedIds as $result) { $idsByType[$result['type']][] = $result['id']; } - + //fetch entities from providers - $entitiesByType = array(); + $entitiesByType = []; + foreach ($idsByType as $type => $ids) { //iterate providers for current context - foreach($this->getProvidersByContext($context) as $provider) { + foreach ($this->getProvidersByContext($context) as $provider) { if ($provider->supportsType($type)) { $entitiesByType[$type] = $provider->getEntities($ids); + break; //we assume that providers have unique keys => we break the loop } } } - + return $entitiesByType; } - - /** - * render the timeline as HTML - * - * @param array $fetched - * @param array $entitiesByType - * @param string $context - * @param mixed[] $args - * @return string the HTML representation of the timeline - */ - private function render(array $fetched, array $entitiesByType, $context, array $args) - { - //add results to a pretty array - $timelineEntries = array(); - foreach ($fetched as $result) { - $data = $this->getTemplateData( - $result['type'], - $entitiesByType[$result['type']][$result['id']], //the entity - $context, - $args); - $timelineEntry['date'] = new \DateTime($result['date']); - $timelineEntry['template'] = $data['template']; - $timelineEntry['template_data'] = $data['template_data']; - - $timelineEntries[] = $timelineEntry; - } - - return $this->container->get('templating') - ->render('@ChillMain/Timeline/chain_timelines.html.twig', array( - 'results' => $timelineEntries - )); - - } - + /** * get the template data from the provider for the given entity, by type. * @@ -312,14 +262,66 @@ class TimelineBuilder implements ContainerAwareInterface * @param mixed $entity * @param string $context * @param mixed[] $args + * * @return array the template data fetched from the provider */ private function getTemplateData($type, $entity, $context, array $args) { - foreach($this->getProvidersByContext($context) as $provider) { + foreach ($this->getProvidersByContext($context) as $provider) { if ($provider->supportsType($type)) { return $provider->getEntityTemplate($entity, $context, $args); } } } + + /** + * render the timeline as HTML. + * + * @param string $context + * @param mixed[] $args + * + * @return string the HTML representation of the timeline + */ + private function render(array $fetched, array $entitiesByType, $context, array $args) + { + //add results to a pretty array + $timelineEntries = []; + + foreach ($fetched as $result) { + $data = $this->getTemplateData( + $result['type'], + $entitiesByType[$result['type']][$result['id']], //the entity + $context, + $args + ); + + $timelineEntries[] = [ + 'date' => new DateTime($result['date']), + 'template' => $data['template'], + 'template_data' => $data['template_data'], + ]; + } + + return $this->container->get('templating') + ->render('@ChillMain/Timeline/chain_timelines.html.twig', [ + 'results' => $timelineEntries, + ]); + } + + /** + * run the UNION query and return result as an array. + * + * @return array an array with the results + */ + private function runUnionQuery(string $query, array $parameters): array + { + $resultSetMapping = (new ResultSetMapping()) + ->addScalarResult('id', 'id') + ->addScalarResult('type', 'type') + ->addScalarResult('date', 'date'); + + return $this->em->createNativeQuery($query, $resultSetMapping) + ->setParameters($parameters) + ->getArrayResult(); + } } diff --git a/src/Bundle/ChillMainBundle/Timeline/TimelineProviderInterface.php b/src/Bundle/ChillMainBundle/Timeline/TimelineProviderInterface.php index abe695e75..98c812ef2 100644 --- a/src/Bundle/ChillMainBundle/Timeline/TimelineProviderInterface.php +++ b/src/Bundle/ChillMainBundle/Timeline/TimelineProviderInterface.php @@ -1,29 +1,23 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Timeline; +use LogicException; + /** - * Interface for service providing info to timeline - * + * Interface for service providing info to timeline. + * * Services implementing those interface must be tagged like this : - * + * * ``` * services: * my_timeline: @@ -34,97 +28,96 @@ namespace Chill\MainBundle\Timeline; * # a second 'center' context : * - { name: timeline, context: center } * ``` - * + * * The bundle which will call the timeline will document available context and * the arguments provided by the context. - * - * - * @author Julien Fastré */ interface TimelineProviderInterface { - /** - * provide data to build a SQL SELECT query to fetch entities - * + * provide data to build a SQL SELECT query to fetch entities. + * * The TimeLineBuilder will create a full SELECT query and append * the query into an UNION of SELECT queries. This permit to fetch * all entities from different table in a single query. - * + * * The associative array MUST have the following key : * - `id` : the name of the id column * - `type`: a string to indicate the type * - `date`: the name of the datetime column, used to order entities by date * - `FROM` (in capital) : the FROM clause. May contains JOIN instructions - * + * * Those key are optional: - * - `WHERE` (in capital) : the WHERE clause. - * + * - `WHERE` (in capital) : the WHERE clause. + * * Where relevant, the data must be quoted to avoid SQL injection. - * + * * `$context` and `$args` are defined by the bundle which will call the timeline - * rendering. - * + * rendering. + * * @param string $context * @param mixed[] $args the argument to the context. + * * @return string[] * @throw \LogicException if the context is not supported */ public function fetchQuery($context, array $args); - - /** - * Indicate if the result type may be handled by the service - * - * @param string $type the key present in the SELECT query - * @return boolean - */ - public function supportsType($type); /** * fetch entities from db into an associative array. The keys **MUST BE** - * the id - * - * All ids returned by all SELECT queries + * the id. + * + * All ids returned by all SELECT queries * (@see TimeLineProviderInterface::fetchQuery) and with the type * supported by the provider (@see TimelineProviderInterface::supportsType) * will be passed as argument. - * + * * @param array $ids an array of id + * * @return mixed[] an associative array of entities, with id as key */ public function getEntities(array $ids); - + /** * return an associative array with argument to render the entity - * in an html template, which will be included in the timeline page - * + * in an html template, which will be included in the timeline page. + * * The result must have the following key : - * + * * - `template` : the template FQDN * - `template_data`: the data required by the template - * - * + * + * * Example: - * + * * ``` - * array( + * array( * 'template' => 'ChillMyBundle:timeline:template.html.twig', * 'template_data' => array( - * 'accompanyingPeriod' => $entity, - * 'person' => $args['person'] + * 'accompanyingPeriod' => $entity, + * 'person' => $args['person'] * ) * ); * ``` - * + * * `$context` and `$args` are defined by the bundle which will call the timeline - * rendering. - * + * rendering. + * * @param type $entity * @param type $context - * @param array $args + * + * @throws LogicException if the context is not supported + * * @return mixed[] - * @throws \LogicException if the context is not supported */ public function getEntityTemplate($entity, $context, array $args); - + + /** + * Indicate if the result type may be handled by the service. + * + * @param string $type the key present in the SELECT query + * + * @return bool + */ + public function supportsType($type); } diff --git a/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php b/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php index e7e456a80..23e60ef97 100644 --- a/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php +++ b/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php @@ -1,30 +1,40 @@ id = $id; @@ -35,6 +45,27 @@ class TimelineSingleQuery $this->parameters = $parameters; } + public function buildSql(): string + { + $parameters = []; + + return strtr( + 'SELECT {distinct} {id} AS id, ' + . '{date} AS "date", ' + . "'{key}' AS type " + . 'FROM {from} ' + . 'WHERE {where}', + [ + '{distinct}' => $this->distinct ? 'DISTINCT' : '', + '{id}' => $this->getId(), + '{date}' => $this->getDate(), + '{key}' => $this->getKey(), + '{from}' => $this->getFrom(), + '{where}' => $this->getWhere(), + ] + ); + } + public static function fromArray(array $a) { return new TimelineSingleQuery( @@ -43,78 +74,49 @@ class TimelineSingleQuery $a['type'] ?? $a['key'] ?? null, $a['FROM'] ?? $a['from'] ?? null, $a['WHERE'] ?? $a['where'] ?? null, - $a['parameters'] ?? null); - } - - public function getId(): string - { - return $this->id; - } - - public function setId(string $id): self - { - $this->id = $id; - - return $this; + $a['parameters'] ?? null + ); } public function getDate(): string { return $this->date; } - - public function setDate(string $date): self + + public function getFrom(): string { - $this->date = $date; - - return $this; + return $this->from; + } + + public function getId(): string + { + return $this->id; } public function getKey(): string { return $this->key; } - - public function setKey(string $key): self - { - $this->key = $key; - - return $this; - } - public function getFrom(): string + public function getParameters(): array { - return $this->from; - } - - public function setFrom(string $from): self - { - $this->from = $from; - - return $this; + return $this->parameters; } public function getWhere(): string { return $this->where; } - - public function setWhere(string $where): self + + public function isDistinct(): bool { - $this->where = $where; - - return $this; + return $this->distinct; } - public function getParameters(): array + public function setDate(string $date): self { - return $this->parameters; - } - - public function setParameters(array $parameters): self - { - $this->parameters = $parameters; - + $this->date = $date; + return $this; } @@ -125,31 +127,38 @@ class TimelineSingleQuery return $this; } - public function isDistinct(): bool + public function setFrom(string $from): self { - return $this->distinct; + $this->from = $from; + + return $this; } - public function buildSql(): string + public function setId(string $id): self { - $parameters = []; + $this->id = $id; - $sql = \strtr( - 'SELECT {distinct} {id} AS id, ' - . '{date} AS "date", ' - . "'{key}' AS type " - . 'FROM {from} ' - . 'WHERE {where}', - [ - '{distinct}' => $this->distinct ? 'DISTINCT' : '', - '{id}' => $this->getId(), - '{date}' => $this->getDate(), - '{key}' => $this->getKey(), - '{from}' => $this->getFrom(), - '{where}' => $this->getWhere(), - ] - ); + return $this; + } - return $sql; + public function setKey(string $key): self + { + $this->key = $key; + + return $this; + } + + public function setParameters(array $parameters): self + { + $this->parameters = $parameters; + + return $this; + } + + public function setWhere(string $where): self + { + $this->where = $where; + + return $this; } } diff --git a/src/Bundle/ChillMainBundle/Util/CountriesInfo.php b/src/Bundle/ChillMainBundle/Util/CountriesInfo.php index a6dfbee49..05089a04f 100644 --- a/src/Bundle/ChillMainBundle/Util/CountriesInfo.php +++ b/src/Bundle/ChillMainBundle/Util/CountriesInfo.php @@ -1,361 +1,370 @@ $minCover) { + throw new LogicException('argument minCover cannot be lower than 0'); } $this->minCover = $minCover; $this->tz = $tz; } - public function add(\DateTimeInterface $start, \DateTimeInterface $end = null, $metadata = null): self + public function add(DateTimeInterface $start, ?DateTimeInterface $end = null, $metadata = null): self { if ($this->computed) { - throw new \LogicException("You cannot add intervals to a computed instance"); + throw new LogicException('You cannot add intervals to a computed instance'); } $k = $this->uniqueKeyCounter++; @@ -72,29 +96,17 @@ class DateRangeCovering $this->addToSequence($start->getTimestamp(), $k, null); $this->addToSequence( - NULL === $end ? PHP_INT_MAX : $end->getTimestamp(), null, $k - ); + null === $end ? PHP_INT_MAX : $end->getTimestamp(), + null, + $k + ); return $this; } - private function addToSequence($timestamp, int $start = null, int $end = null) - { - if (!\array_key_exists($timestamp, $this->sequence)) { - $this->sequence[$timestamp] = [ 's' => [], 'e' => [] ]; - } - - if (NULL !== $start) { - $this->sequence[$timestamp]['s'][] = $start; - } - if (NULL !== $end) { - $this->sequence[$timestamp]['e'][] = $end; - } - } - public function compute(): self { - \ksort($this->sequence); + ksort($this->sequence); $currentPeriod = []; $currents = []; @@ -102,8 +114,8 @@ class DateRangeCovering $overs = []; foreach ($this->sequence as $ts => $moves) { - $currents = \array_merge($currents, $moves['s']); - $currents = \array_diff($currents, $moves['e']); + $currents = array_merge($currents, $moves['s']); + $currents = array_diff($currents, $moves['e']); if (count($currents) > $this->minCover && !$isOpen) { $currentPeriod[0] = $ts; @@ -115,23 +127,23 @@ class DateRangeCovering $currentPeriod = []; $isOpen = false; } elseif ($isOpen) { - $currentPeriod[2] = \array_merge($currentPeriod[2], $currents); + $currentPeriod[2] = array_merge($currentPeriod[2], $currents); } } // process metadata - foreach ($overs as list($start, $end, $metadata)) { + foreach ($overs as [$start, $end, $metadata]) { $this->intersections[] = [ - (new \DateTimeImmutable('@'.$start)) + (new DateTimeImmutable('@' . $start)) ->setTimezone($this->tz), - $end === PHP_INT_MAX ? null : (new \DateTimeImmutable('@'.$end)) + PHP_INT_MAX === $end ? null : (new DateTimeImmutable('@' . $end)) ->setTimezone($this->tz), - \array_values( - \array_intersect_key( + array_values( + array_intersect_key( $this->metadatas, - \array_flip(\array_unique($metadata)) + array_flip(array_unique($metadata)) ) - ) + ), ]; } @@ -140,85 +152,38 @@ class DateRangeCovering return $this; } - private function process(array $intersections): array - { - $result = []; - $starts = []; - $ends = []; - $metadatas = []; - - while (null !== ($current = \array_pop($intersections))) { - list($cStart, $cEnd, $cMetadata) = $current; - $n = count($cMetadata); - - foreach ($intersections as list($iStart, $iEnd, $iMetadata)) { - $start = max($cStart, $iStart); - $end = min($cEnd, $iEnd); - - if ($start <= $end) { - if (FALSE !== ($key = \array_search($start, $starts))) { - if ($ends[$key] === $end) { - $metadatas[$key] = \array_unique(\array_merge($metadatas[$key], $iMetadata)); - continue; - } - } - $starts[] = $start; - $ends[] = $end; - $metadatas[] = \array_unique(\array_merge($iMetadata, $cMetadata)); - } - } - } - - // recompose results - foreach ($starts as $k => $start) { - $result[] = [$start, $ends[$k], \array_unique($metadatas[$k])]; - } - - return $result; - } - - private function addToIntersections(array $intersections, array $intersection) - { - $foundExisting = false; - list($nStart, $nEnd, $nMetadata) = $intersection; - - \array_walk($intersections, - function(&$i, $key) use ($nStart, $nEnd, $nMetadata, $foundExisting) { - if ($foundExisting) { - return; - }; - if ($i[0] === $nStart && $i[1] === $nEnd) { - $foundExisting = true; - $i[2] = \array_merge($i[2], $nMetadata); - } - } - ); - - if (!$foundExisting) { - $intersections[] = $intersection; - } - - return $intersections; - } - - public function hasIntersections(): bool - { - if (!$this->computed) { - throw new \LogicException(sprintf("You cannot call the method %s before ". - "'process'", __METHOD)); - } - - return count($this->intersections) > 0; - } - public function getIntersections(): array { if (!$this->computed) { - throw new \LogicException(sprintf("You cannot call the method %s before ". - "'process'", __METHOD)); + throw new LogicException(sprintf('You cannot call the method %s before ' . + "'process'", __METHOD__)); } return $this->intersections; } + public function hasIntersections(): bool + { + if (!$this->computed) { + throw new LogicException(sprintf('You cannot call the method %s before ' . + "'process'", __METHOD__)); + } + + return count($this->intersections) > 0; + } + + private function addToSequence($timestamp, ?int $start = null, ?int $end = null) + { + if (!array_key_exists($timestamp, $this->sequence)) { + $this->sequence[$timestamp] = ['s' => [], 'e' => []]; + } + + if (null !== $start) { + $this->sequence[$timestamp]['s'][] = $start; + } + + if (null !== $end) { + $this->sequence[$timestamp]['e'][] = $end; + } + } } diff --git a/src/Bundle/ChillMainBundle/Validation/Constraint/PhonenumberConstraint.php b/src/Bundle/ChillMainBundle/Validation/Constraint/PhonenumberConstraint.php index cf4479456..27fbe2f50 100644 --- a/src/Bundle/ChillMainBundle/Validation/Constraint/PhonenumberConstraint.php +++ b/src/Bundle/ChillMainBundle/Validation/Constraint/PhonenumberConstraint.php @@ -1,43 +1,36 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Validation\Constraint; use Symfony\Component\Validator\Constraint; /** - * * @Annotation */ class PhonenumberConstraint extends Constraint { - public $notMobileMessage = "This is not a mobile phonenumber"; - - public $notLandlineMessage = "This is not a landline phonenumber"; - - public $notValidMessage = "This is not a valid phonenumber"; - + public $notLandlineMessage = 'This is not a landline phonenumber'; + + public $notMobileMessage = 'This is not a mobile phonenumber'; + + public $notValidMessage = 'This is not a valid phonenumber'; + /** - * The type of phone: landline (not able to receive sms) or mobile (can receive sms) + * The type of phone: landline (not able to receive sms) or mobile (can receive sms). * * @var string 'landline', 'mobile' or 'any' */ - public $type = null; - + public $type; + public function validatedBy() { return \Chill\MainBundle\Validation\Validator\ValidPhonenumber::class; diff --git a/src/Bundle/ChillMainBundle/Validation/Constraint/RoleScopeScopePresenceConstraint.php b/src/Bundle/ChillMainBundle/Validation/Constraint/RoleScopeScopePresenceConstraint.php index 9f9b6381b..fe61d4655 100644 --- a/src/Bundle/ChillMainBundle/Validation/Constraint/RoleScopeScopePresenceConstraint.php +++ b/src/Bundle/ChillMainBundle/Validation/Constraint/RoleScopeScopePresenceConstraint.php @@ -1,46 +1,35 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Validation\Constraint; use Symfony\Component\Validator\Constraint; /** - * Check that a role scope has a scope if required - * - * @author Julien Fastré + * Check that a role scope has a scope if required. */ class RoleScopeScopePresenceConstraint extends Constraint { - - public $messagePresenceRequired = "The role \"%role%\" require to be associated with " - . "a scope."; - public $messageNullRequired = "The role \"%role%\" should not be associated with a scope."; - - public function validatedBy() - { - return 'role_scope_scope_presence'; - } - + public $messageNullRequired = 'The role "%role%" should not be associated with a scope.'; + + public $messagePresenceRequired = 'The role "%role%" require to be associated with ' + . 'a scope.'; + public function getTargets() { return self::CLASS_CONSTRAINT; } - + + public function validatedBy() + { + return 'role_scope_scope_presence'; + } } diff --git a/src/Bundle/ChillMainBundle/Validation/Constraint/UserUniqueEmailAndUsernameConstraint.php b/src/Bundle/ChillMainBundle/Validation/Constraint/UserUniqueEmailAndUsernameConstraint.php index 1cd481dd7..4b2a5d5b4 100644 --- a/src/Bundle/ChillMainBundle/Validation/Constraint/UserUniqueEmailAndUsernameConstraint.php +++ b/src/Bundle/ChillMainBundle/Validation/Constraint/UserUniqueEmailAndUsernameConstraint.php @@ -1,42 +1,32 @@ - * - * 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\MainBundle\Validation\Constraint; - -use Symfony\Component\Validator\Constraint; -use Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Validation\Constraint; + +use Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername; +use Symfony\Component\Validator\Constraint; + class UserUniqueEmailAndUsernameConstraint extends Constraint { - public $messageDuplicateUsername = "A user with the same or a close username already exists"; - public $messageDuplicateEmail = "A user with the same or a close email already exists"; - + public $messageDuplicateEmail = 'A user with the same or a close email already exists'; + + public $messageDuplicateUsername = 'A user with the same or a close username already exists'; + + public function getTargets() + { + return [self::CLASS_CONSTRAINT]; + } + public function validatedBy() { return UserUniqueEmailAndUsername::class; } - - public function getTargets() - { - return [ self::CLASS_CONSTRAINT ]; - } } diff --git a/src/Bundle/ChillMainBundle/Validation/Validator/RoleScopeScopePresence.php b/src/Bundle/ChillMainBundle/Validation/Validator/RoleScopeScopePresence.php index e6681738a..20abb8907 100644 --- a/src/Bundle/ChillMainBundle/Validation/Validator/RoleScopeScopePresence.php +++ b/src/Bundle/ChillMainBundle/Validation/Validator/RoleScopeScopePresence.php @@ -1,60 +1,48 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\MainBundle\Validation\Validator; -use Chill\MainBundle\Security\RoleProvider; use Chill\MainBundle\Entity\RoleScope; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\ConstraintValidator; +use Chill\MainBundle\Security\RoleProvider; use Chill\MainBundle\Validation\Constraint\RoleScopeScopePresenceConstraint; use Psr\Log\LoggerInterface; +use RuntimeException; use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use function in_array; -/** - * - * - * @author Julien Fastré - */ class RoleScopeScopePresence extends ConstraintValidator { /** - * - * @var RoleProvider - */ - private $roleProvider; - - /** - * * @var LoggerInterface */ private $logger; - + + /** + * @var RoleProvider + */ + private $roleProvider; + /** - * * @var TranslatorInterface */ private $translator; - - public function __construct(RoleProvider $roleProvider, LoggerInterface $logger, - TranslatorInterface $translator) - { + + public function __construct( + RoleProvider $roleProvider, + LoggerInterface $logger, + TranslatorInterface $translator + ) { $this->roleProvider = $roleProvider; $this->logger = $logger; $this->translator = $translator; @@ -62,41 +50,37 @@ class RoleScopeScopePresence extends ConstraintValidator public function validate($value, Constraint $constraint) { - if (! $value instanceof RoleScope) { - throw new \RuntimeException('The validated object is not an instance of roleScope'); + if (!$value instanceof RoleScope) { + throw new RuntimeException('The validated object is not an instance of roleScope'); } - - if (! $constraint instanceof RoleScopeScopePresenceConstraint) { - throw new \RuntimeException('This validator should be used with RoleScopScopePresenceConstraint'); + + if (!$constraint instanceof RoleScopeScopePresenceConstraint) { + throw new RuntimeException('This validator should be used with RoleScopScopePresenceConstraint'); } - + $this->logger->debug('begin validation of a role scope instance'); - + //if the role scope should have a scope if ( - !in_array($value->getRole(), $this->roleProvider->getRolesWithoutScopes()) - && - $value->getScope() === NULL + !in_array($value->getRole(), $this->roleProvider->getRolesWithoutScopes(), true) + && $value->getScope() === null ) { $this->context->buildViolation($constraint->messagePresenceRequired) - ->setParameter('%role%', $this->translator->trans($value->getRole())) - ->addViolation(); + ->setParameter('%role%', $this->translator->trans($value->getRole())) + ->addViolation(); $this->logger->debug('the role scope should have a scope, but scope is null. Violation build.'); } elseif // if the scope should be null ( - in_array($value->getRole(), $this->roleProvider->getRolesWithoutScopes()) - && - ! is_null($value->getScope()) - ) - { + in_array($value->getRole(), $this->roleProvider->getRolesWithoutScopes(), true) + && null !== $value->getScope() + ) { $this->context->buildViolation($constraint->messageNullRequired) - ->setParameter('%role%', $this->translator->trans($value->getRole())) - ->addViolation(); + ->setParameter('%role%', $this->translator->trans($value->getRole())) + ->addViolation(); $this->logger->debug('the role scole should not have a scope, but scope is not null. Violation build.'); } // everything is fine ! else { $this->logger->debug('role scope is valid. Validation finished.'); } } - } diff --git a/src/Bundle/ChillMainBundle/Validation/Validator/UserUniqueEmailAndUsername.php b/src/Bundle/ChillMainBundle/Validation/Validator/UserUniqueEmailAndUsername.php index 0dafa0b98..4ece73852 100644 --- a/src/Bundle/ChillMainBundle/Validation/Validator/UserUniqueEmailAndUsername.php +++ b/src/Bundle/ChillMainBundle/Validation/Validator/UserUniqueEmailAndUsername.php @@ -1,117 +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\MainBundle\Validation\Validator; - -use Symfony\Component\Validator\ConstraintValidator; -use Symfony\Component\Validator\Constraint; -use Chill\MainBundle\Entity\User; -use Doctrine\ORM\EntityManagerInterface; /** - * + * Chill is a software for social workers * - * @author Julien Fastré + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Validation\Validator; + +use Chill\MainBundle\Entity\User; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use UnexpectedValueException; + class UserUniqueEmailAndUsername extends ConstraintValidator { /** - * * @var EntityManagerInterface */ protected $em; - + public function __construct(EntityManagerInterface $em) { $this->em = $em; } - public function validate($value, Constraint $constraint) { if (!$value instanceof User) { - throw new \UnexpectedValueException("This validation should happens " - . "only on class ".User::class); + throw new UnexpectedValueException('This validation should happens ' + . 'only on class ' . User::class); } - + if ($value->getId() !== null) { $countUsersByUsername = $this->em->createQuery( sprintf( - "SELECT COUNT(u) FROM %s u " - . "WHERE u.usernameCanonical = LOWER(UNACCENT(:username)) " - . "AND u != :user", - User::class) + 'SELECT COUNT(u) FROM %s u ' + . 'WHERE u.usernameCanonical = LOWER(UNACCENT(:username)) ' + . 'AND u != :user', + User::class ) + ) ->setParameter('username', $value->getUsername()) ->setParameter('user', $value) ->getSingleScalarResult(); } else { $countUsersByUsername = $this->em->createQuery( sprintf( - "SELECT COUNT(u) FROM %s u " - . "WHERE u.usernameCanonical = LOWER(UNACCENT(:username)) ", - User::class) + 'SELECT COUNT(u) FROM %s u ' + . 'WHERE u.usernameCanonical = LOWER(UNACCENT(:username)) ', + User::class ) + ) ->setParameter('username', $value->getUsername()) ->getSingleScalarResult(); } - - if ($countUsersByUsername > 0) { + + if (0 < $countUsersByUsername) { $this->context ->buildViolation($constraint->messageDuplicateUsername) ->setParameters([ - '%username%' => $value->getUsername() + '%username%' => $value->getUsername(), ]) ->atPath('username') - ->addViolation() - ; + ->addViolation(); } - + if ($value->getId() !== null) { $countUsersByEmail = $this->em->createQuery( sprintf( - "SELECT COUNT(u) FROM %s u " - . "WHERE u.emailCanonical = LOWER(UNACCENT(:email)) " - . "AND u != :user", - User::class) + 'SELECT COUNT(u) FROM %s u ' + . 'WHERE u.emailCanonical = LOWER(UNACCENT(:email)) ' + . 'AND u != :user', + User::class ) + ) ->setParameter('email', $value->getEmail()) ->setParameter('user', $value) ->getSingleScalarResult(); } else { $countUsersByEmail = $this->em->createQuery( sprintf( - "SELECT COUNT(u) FROM %s u " - . "WHERE u.emailCanonical = LOWER(UNACCENT(:email))", - User::class) + 'SELECT COUNT(u) FROM %s u ' + . 'WHERE u.emailCanonical = LOWER(UNACCENT(:email))', + User::class ) + ) ->setParameter('email', $value->getEmail()) ->getSingleScalarResult(); } - - if ($countUsersByEmail > 0) { + + if (0 < $countUsersByEmail) { $this->context ->buildViolation($constraint->messageDuplicateEmail) ->setParameters([ - '%email%' => $value->getEmail() + '%email%' => $value->getEmail(), ]) ->atPath('email') - ->addViolation() - ; + ->addViolation(); } } } diff --git a/src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php b/src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php index 127bfad33..5f2a5bf14 100644 --- a/src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php +++ b/src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php @@ -1,41 +1,31 @@ - * - * 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\MainBundle\Validation\Validator; - -use Symfony\Component\Validator\ConstraintValidator; -use Symfony\Component\Validator\Constraint; -use Chill\MainBundle\Phonenumber\PhonenumberHelper; -use Psr\Log\LoggerInterface; /** - * + * Chill is a software for social workers * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\MainBundle\Validation\Validator; + +use Chill\MainBundle\Phonenumber\PhonenumberHelper; +use LogicException; +use Psr\Log\LoggerInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + class ValidPhonenumber extends ConstraintValidator { + protected $logger; + /** - * * @var PhonenumberHelper */ protected $phonenumberHelper; - - protected $logger; - + public function __construct( LoggerInterface $logger, PhonenumberHelper $phonenumberHelper @@ -43,45 +33,49 @@ class ValidPhonenumber extends ConstraintValidator $this->phonenumberHelper = $phonenumberHelper; $this->logger = $logger; } - + /** - * * @param string $value * @param \Chill\MainBundle\Validation\Constraint\PhonenumberConstraint $constraint */ public function validate($value, Constraint $constraint) { - if (FALSE === $this->phonenumberHelper->isPhonenumberValidationConfigured()) { + if (false === $this->phonenumberHelper->isPhonenumberValidationConfigured()) { $this->logger->debug('[phonenumber] skipping validation due to not configured helper'); - + return; } - + if (empty($value)) { return; } - - switch($constraint->type) { + + switch ($constraint->type) { case 'landline': $isValid = $this->phonenumberHelper->isValidPhonenumberLandOrVoip($value); $message = $constraint->notLandlineMessage; + break; + case 'mobile': $isValid = $this->phonenumberHelper->isValidPhonenumberMobile($value); $message = $constraint->notMobileMessage; + break; + case 'any': $isValid = $this->phonenumberHelper->isValidPhonenumberAny($value); $message = $constraint->notValidMessage; + break; - + default: - throw new \LogicException(sprintf("This type '%s' is not implemented. " + throw new LogicException(sprintf("This type '%s' is not implemented. " . "Possible values are 'mobile', 'landline' or 'any'", $constraint->type)); } - - if (FALSE === $isValid) { - $this->context->addViolation($message, [ '%phonenumber%' => $value ]); + + if (false === $isValid) { + $this->context->addViolation($message, ['%phonenumber%' => $value]); } } } diff --git a/src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistency.php b/src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistency.php index 238aa1a7f..8b405db6a 100644 --- a/src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistency.php +++ b/src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistency.php @@ -1,52 +1,43 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Validator\Constraints\Entity; use Symfony\Component\Validator\Constraint; /** - * - * * @Annotation */ class UserCircleConsistency extends Constraint { - public $message = "{{ username }} is not allowed to see entities published in this circle"; - - public $role; - public $getUserFunction = 'getUser'; - + + public $message = '{{ username }} is not allowed to see entities published in this circle'; + public $path = 'circle'; - + + public $role; + public function getDefaultOption() { return 'role'; } - + public function getRequiredOptions() { - return [ 'role' ]; + return ['role']; } - + public function getTargets() { return self::CLASS_CONSTRAINT; } - } diff --git a/src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistencyValidator.php b/src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistencyValidator.php index c8ec72b50..5cc283d95 100644 --- a/src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistencyValidator.php +++ b/src/Bundle/ChillMainBundle/Validator/Constraints/Entity/UserCircleConsistencyValidator.php @@ -1,66 +1,52 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Validator\Constraints\Entity; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; -use Chill\MainBundle\Entity\HasScopeInterface; +use function call_user_func; -/** - * - * - */ class UserCircleConsistencyValidator extends ConstraintValidator { /** - * * @var AuthorizationHelper */ protected $autorizationHelper; - - function __construct(AuthorizationHelper $autorizationHelper) + + public function __construct(AuthorizationHelper $autorizationHelper) { $this->autorizationHelper = $autorizationHelper; } - /** - * * @param object $value * @param UserCircleConsistency $constraint */ public function validate($value, Constraint $constraint) { - /* @var $user \Chill\MainBundle\Entity\User */ - $user = \call_user_func([$value, $constraint->getUserFunction ]); - - if ($user === null) { + /** @var \Chill\MainBundle\Entity\User $user */ + $user = call_user_func([$value, $constraint->getUserFunction]); + + if (null === $user) { return; } - - if (FALSE === $this->autorizationHelper->userHasAccess($user, $value, $constraint->role)) { + + if (false === $this->autorizationHelper->userHasAccess($user, $value, $constraint->role)) { $this->context ->buildViolation($constraint->message) ->setParameter('{{ username }}', $user->getUsername()) ->atPath($constraint->path) - ->addViolation() - ; + ->addViolation(); } } } diff --git a/src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraint.php b/src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraint.php index d00962e39..88378e9ce 100644 --- a/src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraint.php +++ b/src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraint.php @@ -1,47 +1,39 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Validator\Constraints\Export; use Symfony\Component\Validator\Constraint; /** - * Constraint which will check the ExportElement. + * Constraint which will check the ExportElement. * * @see ExportElementConstraintValidator - * @author Julien Fastré */ class ExportElementConstraint extends Constraint { public $element; + public function getRequiredOptions() + { + return ['element']; + } + + public function getTargets() + { + return self::PROPERTY_CONSTRAINT; + } public function validatedBy() { return ExportElementConstraintValidator::class; } - - public function getRequiredOptions() - { - return array('element'); - } - - public function getTargets() - { - return self::PROPERTY_CONSTRAINT; - } } diff --git a/src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraintValidator.php b/src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraintValidator.php index b6a61a0b4..f395008d7 100644 --- a/src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraintValidator.php +++ b/src/Bundle/ChillMainBundle/Validator/Constraints/Export/ExportElementConstraintValidator.php @@ -1,40 +1,32 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\MainBundle\Validator\Constraints\Export; +use Chill\MainBundle\Export\ExportElementValidatedInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; -use Chill\MainBundle\Export\ExportElementValidatedInterface; /** * This validator validate the _export element_ if this element implements - * {@link ExportElementValidatedInterface}. - * - * @author Julien Fastré + * {@link ExportElementValidatedInterface}. */ class ExportElementConstraintValidator extends ConstraintValidator { public function validate($value, Constraint $constraint) - { + { if ($constraint->element instanceof ExportElementValidatedInterface) { - if ($value["enabled"] === true) { - $constraint->element->validateForm($value["form"], $this->context); - } + if (true === $value['enabled']) { + $constraint->element->validateForm($value['form'], $this->context); + } } } } diff --git a/src/Bundle/ChillMainBundle/chill.api.specs.yaml b/src/Bundle/ChillMainBundle/chill.api.specs.yaml index 8252502ba..fba3cfc19 100644 --- a/src/Bundle/ChillMainBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillMainBundle/chill.api.specs.yaml @@ -1,625 +1,728 @@ --- openapi: "3.0.0" info: - version: "1.0.0" - title: "Chill api" - description: "Api documentation for chill. Currently, work in progress" + version: "1.0.0" + title: "Chill api" + description: "Api documentation for chill. Currently, work in progress" servers: - - url: "/api" - description: "Your current dev server" + - url: "/api" + description: "Your current dev server" components: - schemas: - User: - type: object - properties: - id: - type: integer - type: - type: string - enum: - - user - username: - type: string - text: - type: string - Center: - type: object - properties: - id: - type: integer - name: - type: string - Address: - type: object - properties: - address_id: - type: integer - text: - type: string - postcode: - type: object - properties: - name: - type: string - - Country: - type: object - properties: - id: - type: integer - name: - type: object - countryCode: - type: string - - PostalCode: - type: object - properties: - id: - type: integer - name: - type: string - code: - type: string - country: - type: object - properties: - id: - type: integer - name: - type: object - countryCode: - type: string - - AddressReference: - type: object - properties: - id: - type: integer - refId: - type: string - street: - type: string - streetNumber: - type: string - postcode: - type: object - properties: - id: - type: integer - name: - type: string - code: - type: string - country: - type: object - properties: + schemas: + User: + type: object + properties: id: - type: integer + type: integer + type: + type: string + enum: + - user + username: + type: string + text: + type: string + Center: + type: object + properties: + id: + type: integer name: - type: object + type: string + Address: + type: object + properties: + address_id: + type: integer + text: + type: string + postcode: + type: object + properties: + name: + type: string + + Country: + type: object + properties: + id: + type: integer + name: + type: object countryCode: - type: string - municipalityCode: - type: string - source: - type: string - point: - type: object - properties: - type: - type: string - coordinates: - type: array - items: - type: number - minItems: 2 - maxItems: 2 + type: string + + PostalCode: + type: object + properties: + id: + type: integer + name: + type: string + code: + type: string + country: + type: object + properties: + id: + type: integer + name: + type: object + countryCode: + type: string + + AddressReference: + type: object + properties: + id: + type: integer + refId: + type: string + street: + type: string + streetNumber: + type: string + postcode: + type: object + properties: + id: + type: integer + name: + type: string + code: + type: string + country: + type: object + properties: + id: + type: integer + name: + type: object + countryCode: + type: string + municipalityCode: + type: string + source: + type: string + point: + type: object + properties: + type: + type: string + coordinates: + type: array + items: + type: number + minItems: 2 + maxItems: 2 paths: - /1.0/search.json: - get: - summary: perform a search across multiple entities - tags: - - search - - person - - thirdparty - description: > - The search is performed across multiple entities. The entities must be listed into - `type` parameters. - - The results are ordered by relevance, from the most to the lowest relevant. - - parameters: - - name: q - in: query - required: true - description: the pattern to search - schema: - type: string - - name: type[] - in: query - required: true - description: the type entities amongst the search is performed - schema: - type: array - items: - type: string - enum: + /1.0/search.json: + get: + summary: perform a search across multiple entities + tags: + - search - person - thirdparty + description: > + The search is performed across multiple entities. The entities must be listed into + `type` parameters. + + The results are ordered by relevance, from the most to the lowest relevant. + + parameters: + - name: q + in: query + required: true + description: the pattern to search + schema: + type: string + - name: type[] + in: query + required: true + description: the type entities amongst the search is performed + schema: + type: array + items: + type: string + enum: + - person + - thirdparty + - user + responses: + 200: + description: "OK" + /1.0/main/address.json: + get: + tags: + - address + summary: Return a list of all Chill addresses + responses: + 200: + description: "ok" + post: + tags: + - address + summary: create a new address + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + buildingName: + type: string + corridor: + type: string + distribution: + type: string + extra: + type: string + flat: + type: string + floor: + type: string + isNoAddress: + type: boolean + point: + type: array + items: + type: number + minItems: 2 + maxItems: 2 + postcode: + $ref: '#/components/schemas/PostalCode' + steps: + type: string + street: + type: string + streetNumber: + type: string + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "Unprocessable entity (validation errors)" + 400: + description: "transition cannot be applyed" + patch: + tags: + - address + summary: patch an address + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + buildingName: + type: string + corridor: + type: string + distribution: + type: string + extra: + type: string + flat: + type: string + floor: + type: string + isNoAddress: + type: boolean + point: + type: array + items: + type: number + minItems: 2 + maxItems: 2 + postcode: + $ref: '#/components/schemas/PostalCode' + steps: + type: string + street: + type: string + streetNumber: + type: string + validFrom: + type: string + validTo: + type: string + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "Unprocessable entity (validation errors)" + 400: + description: "transition cannot be applyed" + + /1.0/main/address/{id}.json: + get: + tags: + - address + summary: Return an address by id + parameters: + - name: id + in: path + required: true + description: The address id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Address' + 404: + description: "not found" + 401: + description: "Unauthorized" + + /1.0/main/address/{id}/duplicate.json: + post: + tags: + - address + summary: Duplicate an existing address + parameters: + - name: id + in: path + required: true + description: The address id that will be duplicated + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Address' + 404: + description: "not found" + 401: + description: "Unauthorized" + + /1.0/main/address-reference.json: + get: + tags: + - address + summary: Return a list of all reference addresses + parameters: + - in: query + name: postal_code + required: false + schema: + type: integer + description: The id of a postal code to filter the reference addresses + responses: + 200: + description: "ok" + /1.0/main/address-reference/{id}.json: + get: + tags: + - address + summary: Return a reference address by id + parameters: + - name: id + in: path + required: true + description: The reference address id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/AddressReference' + 404: + description: "not found" + 401: + description: "Unauthorized" + + /1.0/main/address-reference/by-postal-code/{id}/search.json: + get: + tags: + - address + - search + summary: Return a reference address by id + parameters: + - name: id + in: path + required: true + description: The reference address id + schema: + type: integer + format: integer + minimum: 1 + - name: q + in: query + required: true + description: The search pattern + schema: + type: string + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/AddressReference' + 404: + description: "not found" + 401: + description: "Unauthorized" + 400: + description: "Bad request" + /1.0/main/postal-code.json: + get: + tags: + - address + summary: Return a list of all postal-code + parameters: + - in: query + name: country + required: false + schema: + type: integer + description: The id of a country to filter the postal code + responses: + 200: + description: "ok" + post: + tags: + - address + summary: create a new PostalCode + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + code: + type: string + country: + $ref: '#/components/schemas/Country' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "Unprocessable entity (validation errors)" + 400: + description: "transition cannot be applyed" + + /1.0/main/postal-code/{id}.json: + get: + tags: + - address + summary: Return a postal code by id + parameters: + - name: id + in: path + required: true + description: The postal code id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/PostalCode' + 404: + description: "not found" + 401: + description: "Unauthorized" + + /1.0/main/postal-code/search.json: + get: + tags: + - address + - search + summary: Search a postal code + parameters: + - name: q + in: query + required: true + description: The search pattern + schema: + type: string + - name: country + in: query + required: false + description: The country id + schema: + type: integer + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/PostalCode' + 404: + description: "not found" + 400: + description: "Bad Request" + + /1.0/main/country.json: + get: + tags: + - address + summary: Return a list of all countries + responses: + 200: + description: "ok" + /1.0/main/country/{id}.json: + get: + tags: + - address + summary: Return a country by id + parameters: + - name: id + in: path + required: true + description: The country id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Country' + 404: + description: "not found" + 401: + description: "Unauthorized" + + + /1.0/main/user.json: + get: + tags: - user - responses: - 200: - description: "OK" - /1.0/main/address.json: - get: - tags: - - address - summary: Return a list of all Chill addresses - responses: - 200: - description: "ok" - post: - tags: - - address - summary: create a new address - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - buildingName: - type: string - corridor: - type: string - distribution: - type: string - extra: - type: string - flat: - type: string - floor: - type: string - isNoAddress: - type: boolean - point: - type: array - items: - type: number - minItems: 2 - maxItems: 2 - postcode: - $ref: '#/components/schemas/PostalCode' - steps: - type: string - street: - type: string - streetNumber: - type: string - responses: - 401: - description: "Unauthorized" - 404: - description: "Not found" - 200: - description: "OK" - 422: - description: "Unprocessable entity (validation errors)" - 400: - description: "transition cannot be applyed" - patch: - tags: - - address - summary: patch an address - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - buildingName: - type: string - corridor: - type: string - distribution: - type: string - extra: - type: string - flat: - type: string - floor: - type: string - isNoAddress: - type: boolean - point: - type: array - items: - type: number - minItems: 2 - maxItems: 2 - postcode: - $ref: '#/components/schemas/PostalCode' - steps: - type: string - street: - type: string - streetNumber: - type: string - validFrom: - type: string - validTo: - type: string - responses: - 401: - description: "Unauthorized" - 404: - description: "Not found" - 200: - description: "OK" - 422: - description: "Unprocessable entity (validation errors)" - 400: - description: "transition cannot be applyed" + summary: Return a list of all user + responses: + 200: + description: "ok" + /1.0/main/whoami.json: + get: + tags: + - user + summary: Return the currently authenticated user + responses: + 200: + description: "ok" + /1.0/main/user/{id}.json: + get: + tags: + - user + summary: Return a user by id + parameters: + - name: id + in: path + required: true + description: The user id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/User' + 404: + description: "not found" + 401: + description: "Unauthorized" - /1.0/main/address/{id}.json: - get: - tags: - - address - summary: Return an address by id - parameters: - - name: id - in: path - required: true - description: The address id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - content: - application/json: - schema: - $ref: '#/components/schemas/Address' - 404: - description: "not found" - 401: - description: "Unauthorized" + /1.0/main/scope.json: + get: + tags: + - scope + summary: return a list of scopes + responses: + 200: + description: "ok" + 401: + description: "Unauthorized" - /1.0/main/address/{id}/duplicate.json: - post: - tags: - - address - summary: Duplicate an existing address - parameters: - - name: id - in: path - required: true - description: The address id that will be duplicated - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - content: - application/json: - schema: - $ref: '#/components/schemas/Address' - 404: - description: "not found" - 401: - description: "Unauthorized" + /1.0/main/scope/{id}.json: + get: + tags: + - scope + summary: return a list of scopes + parameters: + - name: id + in: path + required: true + description: The scope id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + 401: + description: "Unauthorized" - /1.0/main/address-reference.json: - get: - tags: - - address - summary: Return a list of all reference addresses - parameters: - - in: query - name: postal_code - required: false - schema: - type: integer - description: The id of a postal code to filter the reference addresses - responses: - 200: - description: "ok" - /1.0/main/address-reference/{id}.json: - get: - tags: - - address - summary: Return a reference address by id - parameters: - - name: id - in: path - required: true - description: The reference address id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - content: - application/json: - schema: - $ref: '#/components/schemas/AddressReference' - 404: - description: "not found" - 401: - description: "Unauthorized" + /1.0/main/location.json: + get: + tags: + - location + summary: Return a list of locations + responses: + 200: + description: "ok" + 401: + description: "Unauthorized" + post: + tags: + - location + summary: create a new location + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + type: + type: string + name: + type: string + phonenumber1: + type: string + phonenumber2: + type: string + email: + type: string + address: + type: object + properties: + id: + type: integer + locationType: + type: object + properties: + id: + type: integer + type: + type: string + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "Unprocessable entity (validation errors)" + 400: + description: "transition cannot be applyed" - /1.0/main/postal-code.json: - get: - tags: - - address - summary: Return a list of all postal-code - parameters: - - in: query - name: country - required: false - schema: - type: integer - description: The id of a country to filter the postal code - responses: - 200: - description: "ok" - post: - tags: - - address - summary: create a new PostalCode - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - code: - type: string - country: - $ref: '#/components/schemas/Country' - responses: - 401: - description: "Unauthorized" - 404: - description: "Not found" - 200: - description: "OK" - 422: - description: "Unprocessable entity (validation errors)" - 400: - description: "transition cannot be applyed" + /1.0/main/location/{id}.json: + get: + tags: + - location + summary: Return the given location + parameters: + - name: id + in: path + required: true + description: The location id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + 401: + description: "Unauthorized" - /1.0/main/postal-code/{id}.json: - get: - tags: - - address - summary: Return a postal code by id - parameters: - - name: id - in: path - required: true - description: The postal code id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - content: - application/json: - schema: - $ref: '#/components/schemas/PostalCode' - 404: - description: "not found" - 401: - description: "Unauthorized" + /1.0/main/location-type.json: + get: + tags: + - location + summary: Return a list of location types + responses: + 200: + description: "ok" + 401: + description: "Unauthorized" - /1.0/main/country.json: - get: - tags: - - address - summary: Return a list of all countries - responses: - 200: - description: "ok" - /1.0/main/country/{id}.json: - get: - tags: - - address - summary: Return a country by id - parameters: - - name: id - in: path - required: true - description: The country id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - content: - application/json: - schema: - $ref: '#/components/schemas/Country' - 404: - description: "not found" - 401: - description: "Unauthorized" + /1.0/main/permissions/info.json: + post: + tags: + - permissions + summary: Return info about permissions on entity + responses: + 200: + description: "ok" + 401: + description: "Unauthorized" + 400: + description: "Bad request" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + object: + type: object + class: + type: string + roles: + type: array + items: + type: string + examples: + an-accompanying-period: + value: + object: + type: accompanying_period + id: 1 + class: 'Chill\PersonBundle\Entity\AccompanyingPeriod' + roles: + - 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE' - - /1.0/main/user.json: - get: - tags: - - user - summary: Return a list of all user - responses: - 200: - description: "ok" - /1.0/main/whoami.json: - get: - tags: - - user - summary: Return the currently authenticated user - responses: - 200: - description: "ok" - /1.0/main/user/{id}.json: - get: - tags: - - user - summary: Return a user by id - parameters: - - name: id - in: path - required: true - description: The user id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - content: - application/json: - schema: - $ref: '#/components/schemas/User' - 404: - description: "not found" - 401: - description: "Unauthorized" - - /1.0/main/scope.json: - get: - tags: - - scope - summary: return a list of scopes - responses: - 200: - description: "ok" - 401: - description: "Unauthorized" - - /1.0/main/scope/{id}.json: - get: - tags: - - scope - summary: return a list of scopes - parameters: - - name: id - in: path - required: true - description: The scope id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - 401: - description: "Unauthorized" - - /1.0/main/location.json: - get: - tags: - - location - summary: Return a list of locations - responses: - 200: - description: "ok" - 401: - description: "Unauthorized" - post: - tags: - - location - summary: create a new location - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - type: - type: string - name: - type: string - phonenumber1: - type: string - phonenumber2: - type: string - email: - type: string - address: - type: object - properties: - id: - type: integer - locationType: - type: object - properties: - id: - type: integer - type: - type: string - responses: - 401: - description: "Unauthorized" - 404: - description: "Not found" - 200: - description: "OK" - 422: - description: "Unprocessable entity (validation errors)" - 400: - description: "transition cannot be applyed" - - /1.0/main/location/{id}.json: - get: - tags: - - location - summary: Return the given location - parameters: - - name: id - in: path - required: true - description: The location id - schema: - type: integer - format: integer - minimum: 1 - responses: - 200: - description: "ok" - 401: - description: "Unauthorized" - - /1.0/main/location-type.json: - get: - tags: - - location - summary: Return a list of location types - responses: - 200: - description: "ok" - 401: - description: "Unauthorized" diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index 6540069fa..8ee24d7b7 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -8,36 +8,29 @@ services: Chill\MainBundle\Repository\: resource: '../Repository/' - autowire: true - autoconfigure: true + + Chill\MainBundle\Repository\UserACLAwareRepositoryInterface: '@Chill\MainBundle\Repository\UserACLAwareRepository' Chill\MainBundle\Serializer\Normalizer\: resource: '../Serializer/Normalizer' - autoconfigure: true - autowire: true tags: - { name: 'serializer.normalizer', priority: 64 } Chill\MainBundle\Form\Type\: resource: '../Form/Type' - autoconfigure: true - autowire: true tags: - { name: form.type } Chill\MainBundle\Doctrine\Event\: resource: '../Doctrine/Event/' - autowire: true tags: - { name: 'doctrine.event_subscriber' } chill.main.helper.translatable_string: class: Chill\MainBundle\Templating\TranslatableStringHelper - arguments: - - "@request_stack" - - "@translator.default" Chill\MainBundle\Templating\TranslatableStringHelper: '@chill.main.helper.translatable_string' + Chill\MainBundle\Templating\TranslatableStringHelperInterface: '@Chill\MainBundle\Templating\TranslatableStringHelper' chill.main.twig.translatable_string: class: Chill\MainBundle\Templating\TranslatableStringTwig @@ -77,3 +70,4 @@ services: - "@chill.main.security.authorization.helper" - "@security.token_storage" + Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverDispatcher' diff --git a/src/Bundle/ChillMainBundle/config/services/cache.yaml b/src/Bundle/ChillMainBundle/config/services/cache.yaml index de362ea30..ce2bb3e08 100644 --- a/src/Bundle/ChillMainBundle/config/services/cache.yaml +++ b/src/Bundle/ChillMainBundle/config/services/cache.yaml @@ -1,4 +1,8 @@ services: + _defaults: + autowire: true + autoconfigure: true + chill_main.tag_aware_cache: class: Symfony\Component\Cache\Adapter\TagAwareAdapter arguments: diff --git a/src/Bundle/ChillMainBundle/config/services/command.yaml b/src/Bundle/ChillMainBundle/config/services/command.yaml index 5df80c2ad..87220bc1f 100644 --- a/src/Bundle/ChillMainBundle/config/services/command.yaml +++ b/src/Bundle/ChillMainBundle/config/services/command.yaml @@ -1,10 +1,9 @@ services: + _defaults: + autowire: true + autoconfigure: true + Chill\MainBundle\Command\ChillImportUsersCommand: - arguments: - $em: '@Doctrine\ORM\EntityManagerInterface' - $logger: '@Psr\Log\LoggerInterface' - $passwordEncoder: '@Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface' - $validator: '@Symfony\Component\Validator\Validator\ValidatorInterface' tags: - { name: console.command } diff --git a/src/Bundle/ChillMainBundle/config/services/controller.yaml b/src/Bundle/ChillMainBundle/config/services/controller.yaml index a74755ffd..28abc94e8 100644 --- a/src/Bundle/ChillMainBundle/config/services/controller.yaml +++ b/src/Bundle/ChillMainBundle/config/services/controller.yaml @@ -1,12 +1,13 @@ services: + _defaults: + autowire: true + autoconfigure: true Chill\MainBundle\Controller\: - autowire: true resource: '../../Controller' tags: ['controller.service_arguments'] Chill\MainBundle\Controller\PasswordController: - autowire: true arguments: $chillLogger: '@monolog.logger.chill' tags: ['controller.service_arguments'] @@ -28,10 +29,6 @@ services: $validator: '@Symfony\Component\Validator\Validator\ValidatorInterface' tags: ['controller.service_arguments'] - Chill\MainBundle\Controller\UserController: - autowire: true - autoconfigure: true - Chill\MainBundle\Controller\NotificationController: arguments: $security: '@Symfony\Component\Security\Core\Security' diff --git a/src/Bundle/ChillMainBundle/config/services/crud.yaml b/src/Bundle/ChillMainBundle/config/services/crud.yaml index c5c05f344..02533f433 100644 --- a/src/Bundle/ChillMainBundle/config/services/crud.yaml +++ b/src/Bundle/ChillMainBundle/config/services/crud.yaml @@ -1,15 +1,19 @@ services: + _defaults: + autowire: true + autoconfigure: true + Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader: arguments: $crudConfig: '%chill_main_crud_route_loader_config%' - $apiConfig: '%chill_main_api_route_loader_config%' + $apiCrudConfig: '%chill_main_api_route_loader_config%' tags: [ routing.loader ] Chill\MainBundle\CRUD\Resolver\Resolver: arguments: $em: '@Doctrine\ORM\EntityManagerInterface' $crudConfig: '%chill_main_crud_route_loader_config%' - + Chill\MainBundle\CRUD\Templating\TwigCRUDResolver: arguments: $resolver: '@Chill\MainBundle\CRUD\Resolver\Resolver' diff --git a/src/Bundle/ChillMainBundle/config/services/doctrine.yaml b/src/Bundle/ChillMainBundle/config/services/doctrine.yaml index 09b68f03b..e59088fc4 100644 --- a/src/Bundle/ChillMainBundle/config/services/doctrine.yaml +++ b/src/Bundle/ChillMainBundle/config/services/doctrine.yaml @@ -1,3 +1,7 @@ --- services: - 'Chill\MainBundle\Doctrine\Migrations\VersionComparator': ~ + _defaults: + autowire: true + autoconfigure: true + + Chill\MainBundle\Doctrine\Migrations\VersionComparator: ~ diff --git a/src/Bundle/ChillMainBundle/config/services/export.yaml b/src/Bundle/ChillMainBundle/config/services/export.yaml index ac49199af..ce361677b 100644 --- a/src/Bundle/ChillMainBundle/config/services/export.yaml +++ b/src/Bundle/ChillMainBundle/config/services/export.yaml @@ -1,9 +1,13 @@ services: + _defaults: + autowire: true + autoconfigure: true + chill.main.export_element_validator: class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator tags: - { name: validator.constraint_validator } - + # deprecated in favor of spreadsheet_formatter # chill.main.export.csv_formatter: # class: Chill\MainBundle\Export\Formatter\CSVFormatter @@ -11,7 +15,7 @@ services: # - "@translator" # tags: # - { name: chill.export_formatter, alias: 'csv' } - + chill.main.export.spreadsheet_formatter: class: Chill\MainBundle\Export\Formatter\SpreadSheetFormatter arguments: @@ -19,7 +23,7 @@ services: $exportManager: '@Chill\MainBundle\Export\ExportManager' tags: - { name: chill.export_formatter, alias: 'spreadsheet' } - + chill.main.export.list_formatter: class: Chill\MainBundle\Export\Formatter\CSVListFormatter arguments: @@ -27,7 +31,7 @@ services: $exportManager: '@Chill\MainBundle\Export\ExportManager' tags: - { name: chill.export_formatter, alias: 'csvlist' } - + chill.main.export.list_spreadsheet_formatter: class: Chill\MainBundle\Export\Formatter\SpreadsheetListFormatter arguments: @@ -35,7 +39,7 @@ services: $exportManager: '@Chill\MainBundle\Export\ExportManager' tags: - { name: chill.export_formatter, alias: 'spreadlist' } - + chill.main.export.pivoted_list_formatter: class: Chill\MainBundle\Export\Formatter\CSVPivotedListFormatter arguments: @@ -43,4 +47,3 @@ services: $exportManager: '@Chill\MainBundle\Export\ExportManager' tags: - { name: chill.export_formatter, alias: 'csv_pivoted_list' } - \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/config/services/fixtures.yaml b/src/Bundle/ChillMainBundle/config/services/fixtures.yaml index ccae65867..29aabb25f 100644 --- a/src/Bundle/ChillMainBundle/config/services/fixtures.yaml +++ b/src/Bundle/ChillMainBundle/config/services/fixtures.yaml @@ -1,4 +1,8 @@ services: + _defaults: + autowire: true + autoconfigure: true + Chill\MainBundle\DataFixtures\ORM\: resource: ../../DataFixtures/ORM tags: [ 'doctrine.fixture.orm' ] diff --git a/src/Bundle/ChillMainBundle/config/services/form.yaml b/src/Bundle/ChillMainBundle/config/services/form.yaml index b3eb0ca98..0d0a17201 100644 --- a/src/Bundle/ChillMainBundle/config/services/form.yaml +++ b/src/Bundle/ChillMainBundle/config/services/form.yaml @@ -1,4 +1,7 @@ services: + _defaults: + autowire: true + autoconfigure: true chill.main.form.type.translatable.string: class: Chill\MainBundle\Form\Type\TranslatableStringFormType @@ -25,6 +28,7 @@ services: - "@request_stack" - "@doctrine.orm.entity_manager" - "@chill.main.helper.translatable_string" + - '@Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface' tags: - { name: form.type, alias: select2_chill_country } @@ -34,12 +38,11 @@ services: - "@request_stack" - "@doctrine.orm.entity_manager" - "@chill.main.helper.translatable_string" + - '@Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface' tags: - { name: form.type, alias: select2_chill_language } - Chill\MainBundle\Form\Type\PickCenterType: - autowire: true - autoconfigure: true + Chill\MainBundle\Form\Type\PickCenterType: ~ chill.main.form.type.composed_role_scope: class: Chill\MainBundle\Form\Type\ComposedRoleScopeType @@ -60,9 +63,7 @@ services: tags: - { name: form.type } - Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader: - autowire: true - autoconfigure: true + Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader: ~ chill.main.form.type.export: class: Chill\MainBundle\Form\Type\Export\ExportType @@ -94,14 +95,10 @@ services: arguments: - '@Chill\MainBundle\Export\ExportManager' - Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer: - autowire: true - autoconfigure: true + Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer: ~ chill.main.form.advanced_search_type: class: Chill\MainBundle\Form\AdvancedSearchType - autowire: true - autoconfigure: true arguments: - "@chill_main.search_provider" tags: @@ -114,9 +111,7 @@ services: tags: - { name: form.type } - Chill\MainBundle\Form\UserType: - autowire: true - autoconfigure: true + Chill\MainBundle\Form\UserType: ~ Chill\MainBundle\Form\PermissionsGroupType: tags: @@ -129,15 +124,20 @@ services: tags: - { name: form.type } + Chill\MainBundle\Form\Type\PickAddressType: ~ - Chill\MainBundle\Form\Type\PickAddressType: - autoconfigure: true - autowire: true + Chill\MainBundle\Form\DataTransform\AddressToIdDataTransformer: ~ Chill\MainBundle\Form\DataTransform\AddressToIdDataTransformer: autoconfigure: true autowire: true - Chill\MainBundle\Form\Type\LocationFormType: + Chill\MainBundle\Form\LocationFormType: autowire: true - autoconfigure: true \ No newline at end of file + autoconfigure: true + + Chill\MainBundle\Form\UserCurrentLocationType: + autowire: true + autoconfigure: true + + Chill\MainBundle\Form\Type\LocationFormType: ~ diff --git a/src/Bundle/ChillMainBundle/config/services/logger.yaml b/src/Bundle/ChillMainBundle/config/services/logger.yaml index 2da5a4a4c..a8c8dce58 100644 --- a/src/Bundle/ChillMainBundle/config/services/logger.yaml +++ b/src/Bundle/ChillMainBundle/config/services/logger.yaml @@ -1,4 +1,8 @@ services: + _defaults: + autowire: true + autoconfigure: true + chill.main.logger: # a logger to log events from the app (deletion, remove, etc.) alias: monolog.logger.chill diff --git a/src/Bundle/ChillMainBundle/config/services/menu.yaml b/src/Bundle/ChillMainBundle/config/services/menu.yaml index cf31dccf1..97a1d5e89 100644 --- a/src/Bundle/ChillMainBundle/config/services/menu.yaml +++ b/src/Bundle/ChillMainBundle/config/services/menu.yaml @@ -1,12 +1,14 @@ services: - Chill\MainBundle\Routing\MenuBuilder\: - resource: '../../Routing/MenuBuilder' + _defaults: autowire: true autoconfigure: true + Chill\MainBundle\Routing\MenuBuilder\: + resource: '../../Routing/MenuBuilder' + Chill\MainBundle\Routing\MenuBuilder\UserMenuBuilder: - arguments: - $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + autowire: true + autoconfigure: true tags: - { name: 'chill.menu_builder' } diff --git a/src/Bundle/ChillMainBundle/config/services/notification.yaml b/src/Bundle/ChillMainBundle/config/services/notification.yaml index c8d970c5d..efb25c5b5 100644 --- a/src/Bundle/ChillMainBundle/config/services/notification.yaml +++ b/src/Bundle/ChillMainBundle/config/services/notification.yaml @@ -1,4 +1,8 @@ services: + _defaults: + autowire: true + autoconfigure: true + Chill\MainBundle\Notification\Mailer: arguments: $logger: '@Psr\Log\LoggerInterface' @@ -9,6 +13,4 @@ services: $translator: '@Symfony\Component\Translation\TranslatorInterface' $routeParameters: '%chill_main.notifications%' - Chill\MainBundle\Notification\NotificationRenderer: - autoconfigure: true - autowire: true + Chill\MainBundle\Notification\NotificationRenderer: ~ diff --git a/src/Bundle/ChillMainBundle/config/services/pagination.yaml b/src/Bundle/ChillMainBundle/config/services/pagination.yaml index cb0855a3d..f94bc31be 100644 --- a/src/Bundle/ChillMainBundle/config/services/pagination.yaml +++ b/src/Bundle/ChillMainBundle/config/services/pagination.yaml @@ -1,9 +1,11 @@ services: + _defaults: + autowire: true + autoconfigure: true + chill_main.paginator_factory: class: Chill\MainBundle\Pagination\PaginatorFactory public: true - autowire: true - autoconfigure: true arguments: - "@request_stack" - "@router" diff --git a/src/Bundle/ChillMainBundle/config/services/phonenumber.yaml b/src/Bundle/ChillMainBundle/config/services/phonenumber.yaml index f297c03e0..46fa853ee 100644 --- a/src/Bundle/ChillMainBundle/config/services/phonenumber.yaml +++ b/src/Bundle/ChillMainBundle/config/services/phonenumber.yaml @@ -1,19 +1,21 @@ services: + _defaults: + autowire: true + autoconfigure: true + Chill\MainBundle\Phonenumber\PhonenumberHelper: arguments: - $logger: '@Psr\Log\LoggerInterface' $config: '%chill_main.phone_helper%' $cachePool: '@cache.user_data' - + Chill\MainBundle\Phonenumber\Templating: arguments: $phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper' tags: - { name: twig.extension } - + Chill\MainBundle\Validation\Validator\ValidPhonenumber: arguments: - $logger: '@Psr\Log\LoggerInterface' $phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper' tags: - { name: validator.constraint_validator } diff --git a/src/Bundle/ChillMainBundle/config/services/redis.yaml b/src/Bundle/ChillMainBundle/config/services/redis.yaml index c8d5c2879..6ccbfee7f 100644 --- a/src/Bundle/ChillMainBundle/config/services/redis.yaml +++ b/src/Bundle/ChillMainBundle/config/services/redis.yaml @@ -1,10 +1,13 @@ services: + _defaults: + autowire: true + autoconfigure: true + Chill\MainBundle\Redis\RedisConnectionFactory: arguments: $parameters: "%chill_main.redis%" tags: - { name: kernel.event_subcriber } - + Chill\MainBundle\Redis\ChillRedis: factory: [ '@Chill\MainBundle\Redis\RedisConnectionFactory', 'create' ] - \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/config/services/routing.yaml b/src/Bundle/ChillMainBundle/config/services/routing.yaml index c935f1a4d..fa8a56696 100644 --- a/src/Bundle/ChillMainBundle/config/services/routing.yaml +++ b/src/Bundle/ChillMainBundle/config/services/routing.yaml @@ -1,4 +1,8 @@ services: + _defaults: + autowire: true + autoconfigure: true + chill.main.menu_composer: class: Chill\MainBundle\Routing\MenuComposer arguments: @@ -6,7 +10,7 @@ services: - '@Knp\Menu\FactoryInterface' - '@Symfony\Component\Translation\TranslatorInterface' Chill\MainBundle\Routing\MenuComposer: '@chill.main.menu_composer' - + chill.main.routes_loader: class: Chill\MainBundle\Routing\Loader\ChillRoutesLoader arguments: diff --git a/src/Bundle/ChillMainBundle/config/services/search.yaml b/src/Bundle/ChillMainBundle/config/services/search.yaml index 38e421eaf..91cfb120b 100644 --- a/src/Bundle/ChillMainBundle/config/services/search.yaml +++ b/src/Bundle/ChillMainBundle/config/services/search.yaml @@ -1,4 +1,8 @@ services: + _defaults: + autowire: true + autoconfigure: true + chill_main.search_provider: class: Chill\MainBundle\Search\SearchProvider @@ -7,8 +11,13 @@ services: Chill\MainBundle\Search\SearchApi: autowire: true autoconfigure: true + arguments: + $providers: !tagged_iterator chill.search_api_provider Chill\MainBundle\Search\Entity\: + resource: '../../Search/Entity' + + Chill\MainBundle\Search\Utils\: autowire: true autoconfigure: true - resource: '../../Search/Entity' + resource: './../Search/Utils/' diff --git a/src/Bundle/ChillMainBundle/config/services/security.yaml b/src/Bundle/ChillMainBundle/config/services/security.yaml index 15d6f7da5..163b86d8e 100644 --- a/src/Bundle/ChillMainBundle/config/services/security.yaml +++ b/src/Bundle/ChillMainBundle/config/services/security.yaml @@ -3,43 +3,36 @@ services: autowire: true autoconfigure: true - # do not autowire the directory Security/Resolver Chill\MainBundle\Security\Resolver\CenterResolverDispatcher: arguments: - !tagged_iterator chill_main.center_resolver + Chill\MainBundle\Security\Resolver\CenterResolverManager: + arguments: + - !tagged_iterator chill_main.center_resolver + Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverManager' + Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher: arguments: - !tagged_iterator chill_main.scope_resolver - # do not autowire the directory Security/Resolver - Chill\MainBundle\Security\Resolver\DefaultCenterResolver: - autoconfigure: true - autowire: true + Chill\MainBundle\Security\Resolver\DefaultCenterResolver: ~ - Chill\MainBundle\Security\Resolver\DefaultScopeResolver: - autoconfigure: true - autowire: true + Chill\MainBundle\Security\Resolver\DefaultScopeResolver: ~ - # do not autowire the directory Security/Resolver - Chill\MainBundle\Security\Resolver\ResolverTwigExtension: - autoconfigure: true - autowire: true + Chill\MainBundle\Security\Resolver\ResolverTwigExtension: ~ - # do not autowire the directory Security/Resolver - Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory: - autowire: true + Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory: ~ - # do not autowire the directory Security/Resolver Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory' chill.main.security.authorization.helper: class: Chill\MainBundle\Security\Authorization\AuthorizationHelper - autowire: true - autoconfigure: true Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper' Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface: '@chill.main.security.authorization.helper' + Chill\MainBundle\Security\ParentRoleHelper: ~ + chill.main.role_provider: class: Chill\MainBundle\Security\RoleProvider Chill\MainBundle\Security\RoleProvider: '@chill.main.role_provider' @@ -50,20 +43,16 @@ services: Symfony\Component\Security\Core\User\UserProviderInterface: "@chill.main.user_provider" Chill\MainBundle\Security\Authorization\ChillExportVoter: - arguments: - $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' tags: - { name: security.voter } Chill\MainBundle\Security\PasswordRecover\TokenManager: arguments: $secret: '%kernel.secret%' - $logger: '@Psr\Log\LoggerInterface' Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper: arguments: $tokenManager: '@Chill\MainBundle\Security\PasswordRecover\TokenManager' - $urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface' $mailer: '@Chill\MainBundle\Notification\Mailer' $routeParameters: "%chill_main.notifications%" @@ -76,11 +65,9 @@ services: Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker: arguments: $chillRedis: '@Chill\MainBundle\Redis\ChillRedis' - $logger: '@Psr\Log\LoggerInterface' Chill\MainBundle\Security\PasswordRecover\PasswordRecoverVoter: arguments: $locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker' - $requestStack: '@Symfony\Component\HttpFoundation\RequestStack' tags: - { name: security.voter } diff --git a/src/Bundle/ChillMainBundle/config/services/serializer.yaml b/src/Bundle/ChillMainBundle/config/services/serializer.yaml index efe98996e..4d8aa9954 100644 --- a/src/Bundle/ChillMainBundle/config/services/serializer.yaml +++ b/src/Bundle/ChillMainBundle/config/services/serializer.yaml @@ -1,11 +1,13 @@ --- services: + _defaults: + autowire: true + autoconfigure: true # note: the autowiring for serializers and normalizers is declared # into ../services.yaml Chill\MainBundle\Serializer\Normalizer\DoctrineExistingEntityNormalizer: - autowire: true tags: - { name: 'serializer.normalizer', priority: 8 } diff --git a/src/Bundle/ChillMainBundle/config/services/templating.yaml b/src/Bundle/ChillMainBundle/config/services/templating.yaml index 02d806db4..ade26b946 100644 --- a/src/Bundle/ChillMainBundle/config/services/templating.yaml +++ b/src/Bundle/ChillMainBundle/config/services/templating.yaml @@ -1,4 +1,8 @@ services: + _defaults: + autowire: true + autoconfigure: true + # twig_intl: # class: Twig_Extensions_Extension_Intl # tags: @@ -32,8 +36,6 @@ services: - { name: twig.extension } Chill\MainBundle\Templating\Entity\CommentRender: - autoconfigure: true - autowire: true tags: - { name: 'chill.render_entity' } @@ -41,17 +43,11 @@ services: tags: - { name: twig.extension } - Chill\MainBundle\Templating\Entity\AddressRender: - autoconfigure: true - autowire: true + Chill\MainBundle\Templating\Entity\AddressRender: ~ - Chill\MainBundle\Templating\Entity\UserRender: - autoconfigure: true - autowire: true + Chill\MainBundle\Templating\Entity\UserRender: ~ Chill\MainBundle\Templating\Listing\: resource: './../../Templating/Listing' - autoconfigure: true - autowire: true Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface: '@Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory' diff --git a/src/Bundle/ChillMainBundle/config/services/timeline.yaml b/src/Bundle/ChillMainBundle/config/services/timeline.yaml index fe830c7ab..7b7987f5a 100644 --- a/src/Bundle/ChillMainBundle/config/services/timeline.yaml +++ b/src/Bundle/ChillMainBundle/config/services/timeline.yaml @@ -1,4 +1,8 @@ services: + _defaults: + autowire: true + autoconfigure: true + chill_main.timeline_builder: class: Chill\MainBundle\Timeline\TimelineBuilder arguments: diff --git a/src/Bundle/ChillMainBundle/config/services/validator.yaml b/src/Bundle/ChillMainBundle/config/services/validator.yaml index c15b2181e..b3b60b9d6 100644 --- a/src/Bundle/ChillMainBundle/config/services/validator.yaml +++ b/src/Bundle/ChillMainBundle/config/services/validator.yaml @@ -1,11 +1,15 @@ services: + _defaults: + autowire: true + autoconfigure: true + chill_main.validator_user_circle_consistency: class: Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistencyValidator arguments: - "@chill.main.security.authorization.helper" tags: - { name: "validator.constraint_validator" } - + Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername: arguments: $em: '@Doctrine\ORM\EntityManagerInterface' diff --git a/src/Bundle/ChillMainBundle/config/services/widget.yaml b/src/Bundle/ChillMainBundle/config/services/widget.yaml index 53f29da5b..1f4bca2e3 100644 --- a/src/Bundle/ChillMainBundle/config/services/widget.yaml +++ b/src/Bundle/ChillMainBundle/config/services/widget.yaml @@ -1,2 +1,6 @@ services: + _defaults: + autowire: true + autoconfigure: true + Chill\MainBundle\Templating\UI\CountNotificationUser: ~ diff --git a/src/Bundle/ChillMainBundle/migrations/Version20100000000000.php b/src/Bundle/ChillMainBundle/migrations/Version20100000000000.php new file mode 100644 index 000000000..89454c356 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20100000000000.php @@ -0,0 +1,28 @@ +addSql('CREATE EXTENSION IF NOT EXISTS unaccent'); + $this->addSql('CREATE EXTENSION IF NOT EXISTS pg_trgm'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20141128194409.php b/src/Bundle/ChillMainBundle/migrations/Version20141128194409.php index 2f75b9566..37de9c102 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20141128194409.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20141128194409.php @@ -1,56 +1,64 @@ addSql("CREATE TABLE Country (id INT NOT NULL, name JSON NOT NULL, countryCode VARCHAR(3) NOT NULL, PRIMARY KEY(id));"); - $this->addSql("CREATE TABLE centers (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id));"); - $this->addSql("CREATE TABLE Language (id VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(id));"); - $this->addSql("CREATE TABLE group_centers (id INT NOT NULL, center_id INT DEFAULT NULL, PRIMARY KEY(id));"); - $this->addSql("CREATE INDEX IDX_A14D8F3D5932F377 ON group_centers (center_id);"); - $this->addSql("CREATE TABLE groupcenter_permissionsgroup (groupcenter_id INT NOT NULL, permissionsgroup_id INT NOT NULL, PRIMARY KEY(groupcenter_id, permissionsgroup_id));"); - $this->addSql("CREATE INDEX IDX_55DFEC607EC2FA68 ON groupcenter_permissionsgroup (groupcenter_id);"); - $this->addSql("CREATE INDEX IDX_55DFEC606FA97D46 ON groupcenter_permissionsgroup (permissionsgroup_id);"); - $this->addSql("CREATE TABLE role_scopes (id INT NOT NULL, scope_id INT DEFAULT NULL, role VARCHAR(255) NOT NULL, PRIMARY KEY(id));"); - $this->addSql("CREATE INDEX IDX_AFF20281682B5931 ON role_scopes (scope_id);"); - $this->addSql("CREATE TABLE permission_groups (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id));"); - $this->addSql("CREATE TABLE permissionsgroup_rolescope (permissionsgroup_id INT NOT NULL, rolescope_id INT NOT NULL, PRIMARY KEY(permissionsgroup_id, rolescope_id));"); - $this->addSql("CREATE INDEX IDX_B22441DC6FA97D46 ON permissionsgroup_rolescope (permissionsgroup_id);"); - $this->addSql("CREATE INDEX IDX_B22441DCA0AE1DB7 ON permissionsgroup_rolescope (rolescope_id);"); - $this->addSql("CREATE TABLE users (id INT NOT NULL, username VARCHAR(80) NOT NULL, password VARCHAR(255) NOT NULL, salt VARCHAR(255) DEFAULT NULL, enabled BOOLEAN NOT NULL, locked BOOLEAN NOT NULL, PRIMARY KEY(id));"); - $this->addSql("CREATE TABLE user_groupcenter (user_id INT NOT NULL, groupcenter_id INT NOT NULL, PRIMARY KEY(user_id, groupcenter_id));"); - $this->addSql("CREATE INDEX IDX_33FFE54AA76ED395 ON user_groupcenter (user_id);"); - $this->addSql("CREATE INDEX IDX_33FFE54A7EC2FA68 ON user_groupcenter (groupcenter_id);"); - $this->addSql("CREATE TABLE scopes (id INT NOT NULL, name JSON NOT NULL, PRIMARY KEY(id));"); - $this->addSql("CREATE SEQUENCE Country_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); - $this->addSql("CREATE SEQUENCE centers_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); - $this->addSql("CREATE SEQUENCE group_centers_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); - $this->addSql("CREATE SEQUENCE role_scopes_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); - $this->addSql("CREATE SEQUENCE permission_groups_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); - $this->addSql("CREATE SEQUENCE users_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); - $this->addSql("CREATE SEQUENCE scopes_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); - $this->addSql("ALTER TABLE group_centers ADD CONSTRAINT FK_A14D8F3D5932F377 FOREIGN KEY (center_id) REFERENCES centers (id) NOT DEFERRABLE INITIALLY IMMEDIATE;"); - $this->addSql("ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT FK_55DFEC607EC2FA68 FOREIGN KEY (groupcenter_id) REFERENCES group_centers (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); - $this->addSql("ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT FK_55DFEC606FA97D46 FOREIGN KEY (permissionsgroup_id) REFERENCES permission_groups (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); - $this->addSql("ALTER TABLE role_scopes ADD CONSTRAINT FK_AFF20281682B5931 FOREIGN KEY (scope_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE;"); - $this->addSql("ALTER TABLE permissionsgroup_rolescope ADD CONSTRAINT FK_B22441DC6FA97D46 FOREIGN KEY (permissionsgroup_id) REFERENCES permission_groups (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); - $this->addSql("ALTER TABLE permissionsgroup_rolescope ADD CONSTRAINT FK_B22441DCA0AE1DB7 FOREIGN KEY (rolescope_id) REFERENCES role_scopes (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); - $this->addSql("ALTER TABLE user_groupcenter ADD CONSTRAINT FK_33FFE54AA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); - $this->addSql("ALTER TABLE user_groupcenter ADD CONSTRAINT FK_33FFE54A7EC2FA68 FOREIGN KEY (groupcenter_id) REFERENCES group_centers (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); - } - public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs + } + public function up(Schema $schema): void + { + $this->addSql('CREATE TABLE Country (id INT NOT NULL, name JSON NOT NULL, countryCode VARCHAR(3) NOT NULL, PRIMARY KEY(id));'); + $this->addSql('CREATE TABLE centers (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id));'); + $this->addSql('CREATE TABLE Language (id VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(id));'); + $this->addSql('CREATE TABLE group_centers (id INT NOT NULL, center_id INT DEFAULT NULL, PRIMARY KEY(id));'); + $this->addSql('CREATE INDEX IDX_A14D8F3D5932F377 ON group_centers (center_id);'); + $this->addSql('CREATE TABLE groupcenter_permissionsgroup (groupcenter_id INT NOT NULL, permissionsgroup_id INT NOT NULL, PRIMARY KEY(groupcenter_id, permissionsgroup_id));'); + $this->addSql('CREATE INDEX IDX_55DFEC607EC2FA68 ON groupcenter_permissionsgroup (groupcenter_id);'); + $this->addSql('CREATE INDEX IDX_55DFEC606FA97D46 ON groupcenter_permissionsgroup (permissionsgroup_id);'); + $this->addSql('CREATE TABLE role_scopes (id INT NOT NULL, scope_id INT DEFAULT NULL, role VARCHAR(255) NOT NULL, PRIMARY KEY(id));'); + $this->addSql('CREATE INDEX IDX_AFF20281682B5931 ON role_scopes (scope_id);'); + $this->addSql('CREATE TABLE permission_groups (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id));'); + $this->addSql('CREATE TABLE permissionsgroup_rolescope (permissionsgroup_id INT NOT NULL, rolescope_id INT NOT NULL, PRIMARY KEY(permissionsgroup_id, rolescope_id));'); + $this->addSql('CREATE INDEX IDX_B22441DC6FA97D46 ON permissionsgroup_rolescope (permissionsgroup_id);'); + $this->addSql('CREATE INDEX IDX_B22441DCA0AE1DB7 ON permissionsgroup_rolescope (rolescope_id);'); + $this->addSql('CREATE TABLE users (id INT NOT NULL, username VARCHAR(80) NOT NULL, password VARCHAR(255) NOT NULL, salt VARCHAR(255) DEFAULT NULL, enabled BOOLEAN NOT NULL, locked BOOLEAN NOT NULL, PRIMARY KEY(id));'); + $this->addSql('CREATE TABLE user_groupcenter (user_id INT NOT NULL, groupcenter_id INT NOT NULL, PRIMARY KEY(user_id, groupcenter_id));'); + $this->addSql('CREATE INDEX IDX_33FFE54AA76ED395 ON user_groupcenter (user_id);'); + $this->addSql('CREATE INDEX IDX_33FFE54A7EC2FA68 ON user_groupcenter (groupcenter_id);'); + $this->addSql('CREATE TABLE scopes (id INT NOT NULL, name JSON NOT NULL, PRIMARY KEY(id));'); + $this->addSql('CREATE SEQUENCE Country_id_seq INCREMENT BY 1 MINVALUE 1 START 1;'); + $this->addSql('CREATE SEQUENCE centers_id_seq INCREMENT BY 1 MINVALUE 1 START 1;'); + $this->addSql('CREATE SEQUENCE group_centers_id_seq INCREMENT BY 1 MINVALUE 1 START 1;'); + $this->addSql('CREATE SEQUENCE role_scopes_id_seq INCREMENT BY 1 MINVALUE 1 START 1;'); + $this->addSql('CREATE SEQUENCE permission_groups_id_seq INCREMENT BY 1 MINVALUE 1 START 1;'); + $this->addSql('CREATE SEQUENCE users_id_seq INCREMENT BY 1 MINVALUE 1 START 1;'); + $this->addSql('CREATE SEQUENCE scopes_id_seq INCREMENT BY 1 MINVALUE 1 START 1;'); + $this->addSql('ALTER TABLE group_centers ADD CONSTRAINT FK_A14D8F3D5932F377 FOREIGN KEY (center_id) REFERENCES centers (id) NOT DEFERRABLE INITIALLY IMMEDIATE;'); + $this->addSql('ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT FK_55DFEC607EC2FA68 FOREIGN KEY (groupcenter_id) REFERENCES group_centers (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;'); + $this->addSql('ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT FK_55DFEC606FA97D46 FOREIGN KEY (permissionsgroup_id) REFERENCES permission_groups (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;'); + $this->addSql('ALTER TABLE role_scopes ADD CONSTRAINT FK_AFF20281682B5931 FOREIGN KEY (scope_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE;'); + $this->addSql('ALTER TABLE permissionsgroup_rolescope ADD CONSTRAINT FK_B22441DC6FA97D46 FOREIGN KEY (permissionsgroup_id) REFERENCES permission_groups (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;'); + $this->addSql('ALTER TABLE permissionsgroup_rolescope ADD CONSTRAINT FK_B22441DCA0AE1DB7 FOREIGN KEY (rolescope_id) REFERENCES role_scopes (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;'); + $this->addSql('ALTER TABLE user_groupcenter ADD CONSTRAINT FK_33FFE54AA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;'); + $this->addSql('ALTER TABLE user_groupcenter ADD CONSTRAINT FK_33FFE54A7EC2FA68 FOREIGN KEY (groupcenter_id) REFERENCES group_centers (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;'); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20150821105642.php b/src/Bundle/ChillMainBundle/migrations/Version20150821105642.php index ffc3055ad..910a6b028 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20150821105642.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20150821105642.php @@ -1,145 +1,146 @@ GroupCenter * to - * ManyToOne : a GroupCenter can have only one PermissionGroup - * - * @link https://redmine.champs-libres.coop/issues/578 The issue describing the move + * ManyToOne : a GroupCenter can have only one PermissionGroup. + * + * @see https://redmine.champs-libres.coop/issues/578 The issue describing the move */ -class Version20150821105642 extends AbstractMigration implements - \Symfony\Component\DependencyInjection\ContainerAwareInterface +class Version20150821105642 extends AbstractMigration implements \Symfony\Component\DependencyInjection\ContainerAwareInterface { /** - * * @var ContainerInterface */ private $container; - - /** - * @param Schema $schema - */ - public function up(Schema $schema): void + public function down(Schema $schema): void { - // this up() migration is auto-generated, please modify it to your needs - $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); - - $this->addSql('ALTER TABLE group_centers ADD permissionsGroup_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE group_centers ADD CONSTRAINT FK_A14D8F3D447BBB3B FOREIGN KEY (permissionsGroup_id) REFERENCES permission_groups (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('CREATE INDEX IDX_A14D8F3D447BBB3B ON group_centers (permissionsGroup_id)'); + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + $this->addSql('ALTER TABLE group_centers DROP CONSTRAINT FK_A14D8F3D447BBB3B'); + $this->addSql('DROP INDEX IDX_A14D8F3D447BBB3B'); + $this->addSql('ALTER TABLE group_centers DROP permissionGroup_id'); } - + public function postUp(Schema $schema): void { /* * Before the upgrade to symfony version 4, this code worked. - * + * * But it doesn't work any more after the migration and currently this * code should note be necessary. - * + * * This code is kept for reference, and may be re-activated if needed, but * the probability that this will happens is near 0 */ return; - - //transform data from groupcenter_permissionsgroup table $em = $this->container->get('doctrine.orm.entity_manager'); - + //get all existing associations $rsm = new ResultSetMapping(); $rsm->addScalarResult('groupcenter_id', 'groupcenter_id'); $rsm->addScalarResult('permissionsgroup_id', 'permissionsgroup_id'); - - $groupPermissionsAssociations = $em->createNativeQuery( - "SELECT groupcenter_id, permissionsgroup_id " - . "FROM groupcenter_permissionsgroup", - $rsm - ) - ->getScalarResult(); - //update + $groupPermissionsAssociations = $em->createNativeQuery( + 'SELECT groupcenter_id, permissionsgroup_id ' + . 'FROM groupcenter_permissionsgroup', + $rsm + ) + ->getScalarResult(); + + //update foreach ($groupPermissionsAssociations as $groupPermissionAssociation) { //get the corresponding groupCenter $rsmGroupCenter = new ResultSetMapping(); $rsmGroupCenter->addScalarResult('id', 'id'); $rsmGroupCenter->addScalarResult('permissionsGroup_id', 'permissionsGroup_id'); $rsmGroupCenter->addScalarResult('center_id', 'center_id'); - - $groupCenters = $em->createNativeQuery("SELECT id, permissionsGroup_id, center_id " - . "FROM group_centers " - . "WHERE id = :groupcenter_id AND permissionsGroup_id IS NULL", - $rsmGroupCenter) - ->setParameter('groupcenter_id', $groupPermissionAssociation['groupcenter_id']) - ->getResult(); - + + $groupCenters = $em->createNativeQuery( + 'SELECT id, permissionsGroup_id, center_id ' + . 'FROM group_centers ' + . 'WHERE id = :groupcenter_id AND permissionsGroup_id IS NULL', + $rsmGroupCenter + ) + ->setParameter('groupcenter_id', $groupPermissionAssociation['groupcenter_id']) + ->getResult(); + if (count($groupCenters) === 1) { // we have to update this group with the current association - $em->getConnection()->executeUpdate("UPDATE group_centers " - . "SET permissionsGroup_id = ? " - . "WHERE id = ?", array( - $groupPermissionAssociation['permissionsgroup_id'], - $groupPermissionAssociation['groupcenter_id']) - ); + $em->getConnection()->executeUpdate( + 'UPDATE group_centers ' + . 'SET permissionsGroup_id = ? ' + . 'WHERE id = ?', + [ + $groupPermissionAssociation['permissionsgroup_id'], + $groupPermissionAssociation['groupcenter_id'], ] + ); } elseif (count($groupCenters) === 0) { // the association was multiple. We have to create a new group_center $rsmNewId = new ResultSetMapping(); $rsmNewId->addScalarResult('new_id', 'new_id'); - $newId = $em->createNativeQuery("select nextval('group_centers_id_seq') as new_id", - $rsmNewId) - ->getSingleScalarResult(); - - $em->getConnection()->insert("group_centers", array( - 'id' => $newId, - 'center_id' => $group_center['center_id'], - 'permissionsGroup_id' => $groupPermissionAssociation['permissionsgroup_id'] - )); - + $newId = $em->createNativeQuery( + "select nextval('group_centers_id_seq') as new_id", + $rsmNewId + ) + ->getSingleScalarResult(); + + $em->getConnection()->insert('group_centers', [ + 'id' => $newId, + 'center_id' => $group_center['center_id'], + 'permissionsGroup_id' => $groupPermissionAssociation['permissionsgroup_id'], + ]); + // we have to link existing users to new created groupcenter $em->getConnection()->executeQuery('INSERT INTO user_groupcenter ' - . '(user_id, groupcenter_id) SELECT user_id, '.$newId.' ' + . '(user_id, groupcenter_id) SELECT user_id, ' . $newId . ' ' . 'FROM user_groupcenter WHERE groupcenter_id = ' - .$groupPermissionAssociation['groupcenter_id']); + . $groupPermissionAssociation['groupcenter_id']); } else { - throw new \RuntimeException("Error in the data : we should not have two groupCenter " - . "with the same id !"); + throw new RuntimeException('Error in the data : we should not have two groupCenter ' + . 'with the same id !'); } } - - } - /** - * @param Schema $schema - */ - public function down(Schema $schema): void + public function setContainer(?ContainerInterface $container = null) { - $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); - - $this->addSql('ALTER TABLE group_centers DROP CONSTRAINT FK_A14D8F3D447BBB3B'); - $this->addSql('DROP INDEX IDX_A14D8F3D447BBB3B'); - $this->addSql('ALTER TABLE group_centers DROP permissionGroup_id'); - - } - - public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null) - { - if ($container === NULL) { - throw new \RuntimeException('Container is not provided. This migration ' + if (null === $container) { + throw new RuntimeException('Container is not provided. This migration ' . 'need container to set a default center'); } - + $this->container = $container; } + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE group_centers ADD permissionsGroup_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE group_centers ADD CONSTRAINT FK_A14D8F3D447BBB3B FOREIGN KEY (permissionsGroup_id) REFERENCES permission_groups (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_A14D8F3D447BBB3B ON group_centers (permissionsGroup_id)'); + } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20150821122935.php b/src/Bundle/ChillMainBundle/migrations/Version20150821122935.php index f20b9d927..500ac9de4 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20150821122935.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20150821122935.php @@ -1,39 +1,39 @@ addSql('DROP TABLE groupcenter_permissionsgroup'); - $this->addSql('ALTER TABLE group_centers ALTER permissionsGroup_id SET NOT NULL'); - - } - - /** - * @param Schema $schema - */ public function down(Schema $schema): void { $this->addSql('ALTER TABLE group_centers ALTER permissionsGroup_id SET DEFAULT NULL'); $this->addSql('CREATE TABLE groupcenter_permissionsgroup (groupcenter_id INT NOT NULL, permissionsgroup_id INT NOT NULL, PRIMARY KEY(groupcenter_id, permissionsgroup_id))'); $this->addSql('CREATE INDEX idx_55dfec607ec2fa68 ON groupcenter_permissionsgroup (groupcenter_id)'); $this->addSql('CREATE INDEX idx_55dfec606fa97d46 ON groupcenter_permissionsgroup (permissionsgroup_id)'); - + $this->addSql('ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT fk_55dfec607ec2fa68 FOREIGN KEY (groupcenter_id) REFERENCES group_centers (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT fk_55dfec606fa97d46 FOREIGN KEY (permissionsgroup_id) REFERENCES permission_groups (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + public function up(Schema $schema): void + { + $this->addSql('DROP TABLE groupcenter_permissionsgroup'); + $this->addSql('ALTER TABLE group_centers ALTER permissionsGroup_id SET NOT NULL'); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20160310122322.php b/src/Bundle/ChillMainBundle/migrations/Version20160310122322.php index 25220555b..d1a8a31d1 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20160310122322.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20160310122322.php @@ -1,21 +1,39 @@ abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE chill_main_address ' + . 'DROP CONSTRAINT FK_165051F6EECBFDF1'); + $this->addSql('DROP SEQUENCE chill_main_address_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE chill_main_postal_code_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_address'); + $this->addSql('DROP TABLE chill_main_postal_code'); + } + public function up(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('CREATE SEQUENCE chill_main_address_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_main_postal_code_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); @@ -46,21 +64,5 @@ class Version20160310122322 extends AbstractMigration . 'FOREIGN KEY (country_id) ' . 'REFERENCES Country (id) ' . 'NOT DEFERRABLE INITIALLY IMMEDIATE'); - - } - - /** - * @param Schema $schema - */ - public function down(Schema $schema): void - { - $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); - - $this->addSql('ALTER TABLE chill_main_address ' - . 'DROP CONSTRAINT FK_165051F6EECBFDF1'); - $this->addSql('DROP SEQUENCE chill_main_address_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE chill_main_postal_code_id_seq CASCADE'); - $this->addSql('DROP TABLE chill_main_address'); - $this->addSql('DROP TABLE chill_main_postal_code'); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20180703191509.php b/src/Bundle/ChillMainBundle/migrations/Version20180703191509.php index 2546018c0..a15ee7c8c 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20180703191509.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20180703191509.php @@ -1,15 +1,32 @@ -abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP INDEX search_name_code'); + } + public function up(Schema $schema): void { $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); @@ -17,17 +34,8 @@ final class Version20180703191509 extends AbstractMigration try { $this->addSql('CREATE EXTENSION IF NOT EXISTS pg_trgm'); $this->addSql('CREATE INDEX search_name_code ON chill_main_postal_code USING GIN (LOWER(code) gin_trgm_ops, LOWER(label) gin_trgm_ops)'); - } catch (\Exception $e) { - $this->skipIf(true, "Could not create extension pg_trgm"); + } catch (Exception $e) { + $this->skipIf(true, 'Could not create extension pg_trgm'); } - - } - - public function down(Schema $schema): void - { - $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); - - $this->addSql('DROP INDEX search_name_code'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20180709181423.php b/src/Bundle/ChillMainBundle/migrations/Version20180709181423.php index 3a23657f9..7771756a7 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20180709181423.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20180709181423.php @@ -1,16 +1,40 @@ -abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP INDEX UNIQ_1483A5E9F5A5DC32'); + $this->addSql('DROP INDEX UNIQ_1483A5E9885281E'); + $this->addSql('ALTER TABLE users DROP usernameCanonical'); + $this->addSql('ALTER TABLE users DROP email'); + $this->addSql('ALTER TABLE users DROP emailCanonical'); + $this->addSql('DROP TRIGGER canonicalize_user_on_insert ON users'); + $this->addSql('DROP FUNCTION canonicalize_user_on_insert()'); + $this->addSql('DROP TRIGGER canonicalize_user_on_update ON users'); + $this->addSql('DROP FUNCTION canonicalize_user_on_update()'); + } + public function up(Schema $schema): void { $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); @@ -23,65 +47,53 @@ final class Version20180709181423 extends AbstractMigration $this->addSql('ALTER TABLE users ADD emailCanonical VARCHAR(150) DEFAULT NULL'); $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9F5A5DC32 ON users (usernameCanonical)'); $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9885281E ON users (emailCanonical)'); - - $this->addSql(<<<'SQL' - CREATE OR REPLACE FUNCTION canonicalize_user_on_update() RETURNS TRIGGER AS - $BODY$ - BEGIN - IF NEW.username <> OLD.username OR NEW.email <> OLD.email OR OLD.emailcanonical IS NULL OR OLD.usernamecanonical IS NULL THEN - UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id; - END IF; - RETURN NEW; - END; - $BODY$ LANGUAGE PLPGSQL -SQL + $this->addSql( + <<<'SQL' + CREATE OR REPLACE FUNCTION canonicalize_user_on_update() RETURNS TRIGGER AS + $BODY$ + BEGIN + IF NEW.username <> OLD.username OR NEW.email <> OLD.email OR OLD.emailcanonical IS NULL OR OLD.usernamecanonical IS NULL THEN + UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id; + END IF; + + RETURN NEW; + END; + $BODY$ LANGUAGE PLPGSQL + SQL ); - $this->addSql(<<addSql( + <<<'SQL' + CREATE TRIGGER canonicalize_user_on_update + AFTER UPDATE + ON users + FOR EACH ROW + EXECUTE PROCEDURE canonicalize_user_on_update(); + SQL ); - $this->addSql(<<<'SQL' - CREATE OR REPLACE FUNCTION canonicalize_user_on_insert() RETURNS TRIGGER AS - $BODY$ - BEGIN - UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id; + $this->addSql( + <<<'SQL' + CREATE OR REPLACE FUNCTION canonicalize_user_on_insert() RETURNS TRIGGER AS + $BODY$ + BEGIN + UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id; - RETURN NEW; - END; - $BODY$ LANGUAGE PLPGSQL; -SQL + RETURN NEW; + END; + $BODY$ LANGUAGE PLPGSQL; + SQL ); - $this->addSql(<<abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); - - $this->addSql('DROP INDEX UNIQ_1483A5E9F5A5DC32'); - $this->addSql('DROP INDEX UNIQ_1483A5E9885281E'); - $this->addSql('ALTER TABLE users DROP usernameCanonical'); - $this->addSql('ALTER TABLE users DROP email'); - $this->addSql('ALTER TABLE users DROP emailCanonical'); - $this->addSql('DROP TRIGGER canonicalize_user_on_insert ON users'); - $this->addSql('DROP FUNCTION canonicalize_user_on_insert()'); - $this->addSql('DROP TRIGGER canonicalize_user_on_update ON users'); - $this->addSql('DROP FUNCTION canonicalize_user_on_update()'); - + $this->addSql( + <<<'SQL' + CREATE TRIGGER canonicalize_user_on_insert + AFTER INSERT + ON users + FOR EACH ROW + EXECUTE PROCEDURE canonicalize_user_on_insert(); + SQL + ); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20180905101426.php b/src/Bundle/ChillMainBundle/migrations/Version20180905101426.php index 841a68750..e56936aed 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20180905101426.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20180905101426.php @@ -1,4 +1,13 @@ -addSql('ALTER TABLE permission_groups DROP COLUMN flags'); + $this->addSql('ALTER TABLE group_centers ALTER permissionsgroup_id SET DEFAULT NULL'); + } + public function up(Schema $schema): void { $this->addSql('ALTER TABLE permission_groups ADD flags JSONB DEFAULT \'[]\' NOT NULL'); $this->addSql('ALTER TABLE group_centers ALTER permissionsgroup_id DROP NOT NULL'); - - } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE permission_groups DROP COLUMN flags'); - $this->addSql('ALTER TABLE group_centers ALTER permissionsgroup_id SET DEFAULT NULL'); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20180911093642.php b/src/Bundle/ChillMainBundle/migrations/Version20180911093642.php index 6ff24bc5a..86e5585e3 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20180911093642.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20180911093642.php @@ -1,4 +1,13 @@ -addSql("DROP TRIGGER canonicalize_user_on_update ON users"); - - $this->addSql(<<addSql("DROP TRIGGER canonicalize_user_on_update ON users"); - - $this->addSql(<<addSql('DROP TRIGGER canonicalize_user_on_update ON users'); + $this->addSql( + <<<'SQL' + CREATE TRIGGER canonicalize_user_on_update + AFTER UPDATE + ON users + FOR EACH ROW + EXECUTE PROCEDURE canonicalize_user_on_update(); + SQL + ); + } + + public function up(Schema $schema): void + { + $this->addSql('DROP TRIGGER canonicalize_user_on_update ON users'); + + $this->addSql( + <<<'SQL' + CREATE TRIGGER canonicalize_user_on_update + AFTER UPDATE + ON users + FOR EACH ROW + WHEN (pg_trigger_depth() = 0) + EXECUTE PROCEDURE canonicalize_user_on_update(); + SQL + ); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20200422122715.php b/src/Bundle/ChillMainBundle/migrations/Version20200422122715.php index 3449e5f28..95a9abe51 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20200422122715.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20200422122715.php @@ -1,4 +1,13 @@ -addSql('ALTER TABLE chill_main_address ADD isNoAddress BOOLEAN NOT NULL DEFAULT FALSE'); - } - public function down(Schema $schema): void { $this->addSql('ALTER TABLE chill_main_address DROP isNoAddress'); } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_address ADD isNoAddress BOOLEAN NOT NULL DEFAULT FALSE'); + } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210304085819.php b/src/Bundle/ChillMainBundle/migrations/Version20210304085819.php index 5f1fb6869..5f15147e4 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210304085819.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210304085819.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE users DROP attributes'); + } + + public function getDescription(): string { return ''; } - public function up(Schema $schema) : void + public function up(Schema $schema): void { $this->addSql('ALTER TABLE users ADD attributes JSONB DEFAULT NULL'); - $this->addSql('COMMENT ON COLUMN users.attributes IS \'(DC2Type:json_array)\''); - } - - public function down(Schema $schema) : void - { - $this->addSql('ALTER TABLE users DROP attributes'); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210308111926.php b/src/Bundle/ChillMainBundle/migrations/Version20210308111926.php index fa4f001bb..3e5d17a4a 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210308111926.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210308111926.php @@ -1,4 +1,13 @@ -abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); - - $this->addSql('ALTER TABLE chill_main_address ADD customs JSONB DEFAULT \'[]\''); - - } - - public function down(Schema $schema) : void + public function down(Schema $schema): void { $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('ALTER TABLE chill_main_address DROP customs'); } - + public function getDescription(): string { - return "Add custom data in addresses"; + return 'Add custom data in addresses'; + } + + public function up(Schema $schema): void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE chill_main_address ADD customs JSONB DEFAULT \'[]\''); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210414091001.php b/src/Bundle/ChillMainBundle/migrations/Version20210414091001.php index db207c594..3eaa4630d 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210414091001.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210414091001.php @@ -1,4 +1,13 @@ -abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); - - $this->addSql('CREATE EXTENSION IF NOT EXISTS postgis;'); - - } - - public function down(Schema $schema) : void + public function down(Schema $schema): void { $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); @@ -27,6 +28,13 @@ final class Version20210414091001 extends AbstractMigration public function getDescription(): string { - return "Enable the postgis extension in public schema"; + return 'Enable the postgis extension in public schema'; + } + + public function up(Schema $schema): void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE EXTENSION IF NOT EXISTS postgis;'); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210420115006.php b/src/Bundle/ChillMainBundle/migrations/Version20210420115006.php index 79dea4853..b0b281d06 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210420115006.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210420115006.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_address RENAME COLUMN streetaddress1 TO street;'); - $this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetaddress2 TO streetNumber;'); - $this->addSql('ALTER TABLE chill_main_address ADD floor VARCHAR(16) DEFAULT NULL'); - $this->addSql('ALTER TABLE chill_main_address ADD corridor VARCHAR(16) DEFAULT NULL'); - $this->addSql('ALTER TABLE chill_main_address ADD steps VARCHAR(16) DEFAULT NULL'); - $this->addSql('ALTER TABLE chill_main_address ADD buildingName VARCHAR(255) DEFAULT NULL'); - $this->addSql('ALTER TABLE chill_main_address ADD flat VARCHAR(16) DEFAULT NULL'); - $this->addSql('ALTER TABLE chill_main_address ADD distribution VARCHAR(255) DEFAULT NULL'); - $this->addSql('ALTER TABLE chill_main_address ADD extra VARCHAR(255) DEFAULT NULL'); - $this->addSql('ALTER TABLE chill_main_address ADD validTo DATE DEFAULT NULL'); - $this->addSql('ALTER TABLE chill_main_address ADD point geometry(POINT,4326) DEFAULT NULL'); - } - - - public function down(Schema $schema) : void + public function down(Schema $schema): void { $this->addSql('ALTER TABLE chill_main_address RENAME COLUMN street TO streetaddress1;'); $this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetNumber TO streetaddress2;'); @@ -47,4 +33,24 @@ final class Version20210420115006 extends AbstractMigration $this->addSql('ALTER TABLE chill_main_address DROP validTo'); $this->addSql('ALTER TABLE chill_main_address DROP point'); } + + public function getDescription(): string + { + return 'Add a Point data type and modify the Address entity'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetaddress1 TO street;'); + $this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetaddress2 TO streetNumber;'); + $this->addSql('ALTER TABLE chill_main_address ADD floor VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD corridor VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD steps VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD buildingName VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD flat VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD distribution VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD extra VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD validTo DATE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD point geometry(POINT,4326) DEFAULT NULL'); + } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210503085107.php b/src/Bundle/ChillMainBundle/migrations/Version20210503085107.php index f693777a0..0f664e2b2 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210503085107.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210503085107.php @@ -1,5 +1,12 @@ addSql('DROP SEQUENCE chill_main_address_reference_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_address_reference'); + } + public function getDescription(): string { return 'Add a AddressReference table for storing authoritative address data'; @@ -24,10 +37,4 @@ final class Version20210503085107 extends AbstractMigration $this->addSql('CREATE INDEX IDX_CA6C1BD7EECBFDF1 ON chill_main_address_reference (postcode_id)'); $this->addSql('ALTER TABLE chill_main_address_reference ADD CONSTRAINT FK_CA6C1BD7EECBFDF1 FOREIGN KEY (postcode_id) REFERENCES chill_main_postal_code (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); } - - public function down(Schema $schema): void - { - $this->addSql('DROP SEQUENCE chill_main_address_reference_id_seq CASCADE'); - $this->addSql('DROP TABLE chill_main_address_reference'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210505153727.php b/src/Bundle/ChillMainBundle/migrations/Version20210505153727.php index 42161ba84..34c3c10b9 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210505153727.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210505153727.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_address DROP CONSTRAINT FK_165051F6114B8DD9'); + $this->addSql('DROP INDEX IDX_165051F6114B8DD9'); + $this->addSql('ALTER TABLE chill_main_address DROP linkedToThirdParty_id'); + $this->addSql('DROP TABLE IF EXISTS chill_main_address_legacy'); + $this->addSql(' + UPDATE chill_main_address + SET validto = null; + '); + } + public function getDescription(): string { return 'Add linkedToThirdParty field to Address'; @@ -43,16 +62,4 @@ final class Version20210505153727 extends AbstractMigration ); '); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_address DROP CONSTRAINT FK_165051F6114B8DD9'); - $this->addSql('DROP INDEX IDX_165051F6114B8DD9'); - $this->addSql('ALTER TABLE chill_main_address DROP linkedToThirdParty_id'); - $this->addSql('DROP TABLE IF EXISTS chill_main_address_legacy'); - $this->addSql(' - UPDATE chill_main_address - SET validto = null; - '); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210525144016.php b/src/Bundle/ChillMainBundle/migrations/Version20210525144016.php index 3be8d9ea1..f917a5bf1 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210525144016.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210525144016.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_address DROP CONSTRAINT fk_165051f6114b8dd9'); + $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT fk_165051f6114b8dd9 FOREIGN KEY (linkedtothirdparty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + public function getDescription(): string { return 'Specify ON DELETE behaviour to handle deletion of parents in associated tables'; @@ -22,10 +35,4 @@ final class Version20210525144016 extends AbstractMigration $this->addSql('ALTER TABLE chill_main_address DROP CONSTRAINT FK_165051F6114B8DD9'); $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT FK_165051F6114B8DD9 FOREIGN KEY (linkedToThirdParty_id) REFERENCES chill_3party.third_party (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_address DROP CONSTRAINT fk_165051f6114b8dd9'); - $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT fk_165051f6114b8dd9 FOREIGN KEY (linkedtothirdparty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php b/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php index d2a33230f..09a0a52fd 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php @@ -1,5 +1,12 @@ addSql('DROP EXTENSION btree_gist'); + } + public function getDescription(): string { return 'add extension btree_gist'; @@ -21,9 +33,4 @@ final class Version20210528090000 extends AbstractMigration { $this->addSql('CREATE EXTENSION IF NOT EXISTS btree_gist'); } - - public function down(Schema $schema): void - { - $this->addSql('DROP EXTENSION btree_gist'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210610140248.php b/src/Bundle/ChillMainBundle/migrations/Version20210610140248.php index d54ff4634..54160273e 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210610140248.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210610140248.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_notification_addresses_user DROP CONSTRAINT FK_E52C5D2BEF1A9D84'); + $this->addSql('DROP SEQUENCE chill_main_notification_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_notification'); + $this->addSql('DROP TABLE chill_main_notification_addresses_user'); + } + public function getDescription(): string { return 'Add table for ChillMain/Notification'; @@ -31,12 +46,4 @@ final class Version20210610140248 extends AbstractMigration $this->addSql('ALTER TABLE chill_main_notification_addresses_user ADD CONSTRAINT FK_E52C5D2BEF1A9D84 FOREIGN KEY (notification_id) REFERENCES chill_main_notification (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_main_notification_addresses_user ADD CONSTRAINT FK_E52C5D2BA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_notification_addresses_user DROP CONSTRAINT FK_E52C5D2BEF1A9D84'); - $this->addSql('DROP SEQUENCE chill_main_notification_id_seq CASCADE'); - $this->addSql('DROP TABLE chill_main_notification'); - $this->addSql('DROP TABLE chill_main_notification_addresses_user'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210616134328.php b/src/Bundle/ChillMainBundle/migrations/Version20210616134328.php index c54f0a183..ed4603b17 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210616134328.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210616134328.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_postal_code DROP origin'); + } + public function getDescription(): string { return ''; @@ -21,9 +33,4 @@ final class Version20210616134328 extends AbstractMigration { $this->addSql('ALTER TABLE chill_main_postal_code ADD origin INT DEFAULT NULL'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_postal_code DROP origin'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210903144853.php b/src/Bundle/ChillMainBundle/migrations/Version20210903144853.php index 7fe6b8e72..cfd449dd0 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210903144853.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210903144853.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E964B65C5B'); + $this->addSql('DROP SEQUENCE chill_main_user_job_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_user_job'); + $this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E92C2125C1'); + $this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E9115E73F3'); + $this->addSql('ALTER TABLE users DROP label'); + $this->addSql('ALTER TABLE users DROP mainCenter_id'); + $this->addSql('ALTER TABLE users DROP mainScope_id'); + $this->addSql('ALTER TABLE users DROP userJob_id'); + $this->addSql('ALTER TABLE users ALTER usernameCanonical DROP NOT NULL'); + } + public function getDescription(): string { return 'Add metadata on users'; @@ -35,18 +56,4 @@ final class Version20210903144853 extends AbstractMigration $this->addSql('CREATE INDEX IDX_1483A5E9115E73F3 ON users (mainScope_id)'); $this->addSql('CREATE INDEX IDX_1483A5E964B65C5B ON users (userJob_id)'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E964B65C5B'); - $this->addSql('DROP SEQUENCE chill_main_user_job_id_seq CASCADE'); - $this->addSql('DROP TABLE chill_main_user_job'); - $this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E92C2125C1'); - $this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E9115E73F3'); - $this->addSql('ALTER TABLE users DROP label'); - $this->addSql('ALTER TABLE users DROP mainCenter_id'); - $this->addSql('ALTER TABLE users DROP mainScope_id'); - $this->addSql('ALTER TABLE users DROP userJob_id'); - $this->addSql('ALTER TABLE users ALTER usernameCanonical DROP NOT NULL'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210929192242.php b/src/Bundle/ChillMainBundle/migrations/Version20210929192242.php index a2fbefa69..5e22201c3 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210929192242.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210929192242.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_address DROP addressReference_id'); + } + public function getDescription(): string { return 'Add a link between address and address reference'; @@ -23,9 +35,4 @@ final class Version20210929192242 extends AbstractMigration $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT FK_165051F647069464 FOREIGN KEY (addressReference_id) REFERENCES chill_main_address_reference (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('CREATE INDEX IDX_165051F647069464 ON chill_main_address (addressReference_id)'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_address DROP addressReference_id'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211006151653.php b/src/Bundle/ChillMainBundle/migrations/Version20211006151653.php index 9f4508ba8..c564c30eb 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20211006151653.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20211006151653.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_postal_code DROP refPostalCodeId'); + $this->addSql('ALTER TABLE chill_main_postal_code DROP postalCodeSource'); + $this->addSql('ALTER TABLE chill_main_postal_code DROP center'); + } + public function getDescription(): string { return 'Add some fields to PostalCode'; @@ -23,11 +37,4 @@ final class Version20211006151653 extends AbstractMigration $this->addSql('ALTER TABLE chill_main_postal_code ADD postalCodeSource VARCHAR(255) DEFAULT NULL'); $this->addSql('ALTER TABLE chill_main_postal_code ADD center geometry(POINT,4326) DEFAULT NULL'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_postal_code DROP refPostalCodeId'); - $this->addSql('ALTER TABLE chill_main_postal_code DROP postalCodeSource'); - $this->addSql('ALTER TABLE chill_main_postal_code DROP center'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php b/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php index b81c7f74e..33cb2fd42 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php @@ -1,5 +1,12 @@ addSql('DROP SEQUENCE chill_main_civility_id_seq'); + $this->addSql('DROP TABLE chill_main_civility'); + } + public function getDescription(): string { return 'create civility table'; @@ -22,10 +35,4 @@ final class Version20211007150019 extends AbstractMigration $this->addSql('CREATE SEQUENCE chill_main_civility_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE TABLE chill_main_civility (id INT NOT NULL, name JSON NOT NULL, abbreviation JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))'); } - - public function down(Schema $schema): void - { - $this->addSql('DROP SEQUENCE chill_main_civility_id_seq'); - $this->addSql('DROP TABLE chill_main_civility'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211012141336.php b/src/Bundle/ChillMainBundle/migrations/Version20211012141336.php index 4ed8da1dc..4d1050b94 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20211012141336.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20211012141336.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE activity DROP CONSTRAINT FK_AC74095A64D218E'); + $this->addSql('ALTER TABLE activity DROP location_id'); + + $this->addSql('ALTER TABLE chill_calendar.calendar DROP CONSTRAINT FK_712315AC64D218E'); + $this->addSql('ALTER TABLE chill_calendar.calendar DROP location_id'); + + $this->addSql('ALTER TABLE chill_main_location DROP CONSTRAINT FK_90E4736AB8B0DA8E'); + + $this->addSql('DROP SEQUENCE chill_main_location_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE chill_main_location_type_id_seq CASCADE'); + + $this->addSql('DROP TABLE chill_main_location'); + $this->addSql('DROP TABLE chill_main_location_type'); + } + public function getDescription(): string { return 'Add Location and LocationType Entities (for activity and calendar)'; @@ -45,22 +69,4 @@ final class Version20211012141336 extends AbstractMigration $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315AC64D218E FOREIGN KEY (location_id) REFERENCES chill_main_location (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('CREATE INDEX IDX_712315AC64D218E ON chill_calendar.calendar (location_id)'); } - - public function down(Schema $schema): void - { - - $this->addSql('ALTER TABLE activity DROP CONSTRAINT FK_AC74095A64D218E'); - $this->addSql('ALTER TABLE activity DROP location_id'); - - $this->addSql('ALTER TABLE chill_calendar.calendar DROP CONSTRAINT FK_712315AC64D218E'); - $this->addSql('ALTER TABLE chill_calendar.calendar DROP location_id'); - - $this->addSql('ALTER TABLE chill_main_location DROP CONSTRAINT FK_90E4736AB8B0DA8E'); - - $this->addSql('DROP SEQUENCE chill_main_location_id_seq CASCADE'); - $this->addSql('DROP SEQUENCE chill_main_location_type_id_seq CASCADE'); - - $this->addSql('DROP TABLE chill_main_location'); - $this->addSql('DROP TABLE chill_main_location_type'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211013124455.php b/src/Bundle/ChillMainBundle/migrations/Version20211013124455.php index 0d30cece5..c40446834 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20211013124455.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20211013124455.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE activitytype DROP locationVisible'); + $this->addSql('ALTER TABLE activitytype DROP locationLabel'); + + // fix old migration !? + $this->addSql('ALTER TABLE activitytype ALTER category_id SET DEFAULT 1'); + } + public function getDescription(): string { return 'add location visible admin option'; @@ -21,16 +37,5 @@ final class Version20211013124455 extends AbstractMigration // fix old migration !? $this->addSql('ALTER TABLE activitytype ALTER category_id DROP DEFAULT'); - - } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE activitytype DROP locationVisible'); - $this->addSql('ALTER TABLE activitytype DROP locationLabel'); - - // fix old migration !? - $this->addSql('ALTER TABLE activitytype ALTER category_id SET DEFAULT 1'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211015084653.php b/src/Bundle/ChillMainBundle/migrations/Version20211015084653.php index 12b230f10..52cf53aac 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20211015084653.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20211015084653.php @@ -1,5 +1,12 @@ addSql('DROP INDEX IDX_90E4736AF5B7AF75'); + $this->addSql('CREATE UNIQUE INDEX uniq_90e4736af5b7af75 ON chill_main_location (address_id)'); + } + public function getDescription(): string { return 'Location entity: change Address to ManyToOne'; @@ -16,13 +29,7 @@ final class Version20211015084653 extends AbstractMigration public function up(Schema $schema): void { - $this->addSql('DROP INDEX uniq_90e4736af5b7af75'); - $this->addSql('CREATE INDEX IDX_90E4736AF5B7AF75 ON chill_main_location (address_id)'); - } - - public function down(Schema $schema): void - { - $this->addSql('DROP INDEX IDX_90E4736AF5B7AF75'); - $this->addSql('CREATE UNIQUE INDEX uniq_90e4736af5b7af75 ON chill_main_location (address_id)'); + $this->addSql('DROP INDEX uniq_90e4736af5b7af75'); + $this->addSql('CREATE INDEX IDX_90E4736AF5B7AF75 ON chill_main_location (address_id)'); } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211022094429.php b/src/Bundle/ChillMainBundle/migrations/Version20211022094429.php index 1bc4f68d0..f11dbf42b 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20211022094429.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20211022094429.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_location_type DROP active'); + $this->addSql('ALTER TABLE chill_main_location DROP active'); + } + public function getDescription(): string { return 'Add active on Location and LocationType'; @@ -22,10 +35,4 @@ final class Version20211022094429 extends AbstractMigration $this->addSql('ALTER TABLE chill_main_location ADD active BOOLEAN DEFAULT TRUE;'); $this->addSql('ALTER TABLE chill_main_location_type ADD active BOOLEAN DEFAULT TRUE;'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_location_type DROP active'); - $this->addSql('ALTER TABLE chill_main_location DROP active'); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211116162847.php b/src/Bundle/ChillMainBundle/migrations/Version20211116162847.php new file mode 100644 index 000000000..d9011049f --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211116162847.php @@ -0,0 +1,40 @@ +addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E93C219753'); + $this->addSql('DROP INDEX IDX_1483A5E93C219753'); + $this->addSql('ALTER TABLE users DROP currentLocation_id'); + } + + public function getDescription(): string + { + return 'Add current location to User entity'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE users ADD currentLocation_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E93C219753 FOREIGN KEY (currentLocation_id) REFERENCES chill_main_location (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_1483A5E93C219753 ON users (currentLocation_id)'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211119173554.php b/src/Bundle/ChillMainBundle/migrations/Version20211119173554.php new file mode 100644 index 000000000..9494337fd --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211119173554.php @@ -0,0 +1,42 @@ +throwIrreversibleMigrationException(); + } + + public function getDescription(): string + { + return 'remove comment on deprecated json_array type'; + } + + public function up(Schema $schema): void + { + $columns = [ + 'users.attributes', + ]; + + foreach ($columns as $col) { + $this->addSql("COMMENT ON COLUMN {$col} IS NULL"); + } + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211123093355.php b/src/Bundle/ChillMainBundle/migrations/Version20211123093355.php new file mode 100644 index 000000000..b6618d25e --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211123093355.php @@ -0,0 +1,38 @@ +addSql('DROP INDEX UNIQ_A459B5CADD3E4105'); + $this->addSql('ALTER TABLE chill_main_location_type DROP defaultFor'); + } + + public function getDescription(): string + { + return 'Add defaultFor to LocationType'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_location_type ADD defaultFor VARCHAR(32) DEFAULT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_A459B5CADD3E4105 ON chill_main_location_type (defaultFor)'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211125142016.php b/src/Bundle/ChillMainBundle/migrations/Version20211125142016.php new file mode 100644 index 000000000..08f3815de --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211125142016.php @@ -0,0 +1,85 @@ +addSql('DROP TRIGGER canonicalize_address_reference_on_insert ON chill_main_address_reference'); + $this->addSql('DROP TRIGGER canonicalize_address_reference_on_update ON chill_main_address_reference'); + $this->addSql('DROP FUNCTION canonicalize_address_reference()'); + $this->addSql('ALTER TABLE chill_main_address_reference DROP COLUMN addressCanonical'); + } + + public function getDescription(): string + { + return 'Add a column "canonicalized" on chill_main_address_reference and add trigger and indexed on it'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_address_reference ADD addressCanonical TEXT DEFAULT \'\' NOT NULL'); + + $this->addSql('UPDATE chill_main_address_reference + SET addresscanonical = + TRIM( + UNACCENT( + LOWER( + street || + \' \' || + streetnumber + ) + ) + + )'); + + $this->addSql('CREATE OR REPLACE FUNCTION public.canonicalize_address_reference() RETURNS TRIGGER + LANGUAGE plpgsql + AS + $$ + BEGIN + NEW.addresscanonical = + TRIM( + UNACCENT( + LOWER( + NEW.street || + \' \' || + NEW.streetnumber + ) + ) + + ) + ; + + return NEW; + END + $$'); + + $this->addSql('CREATE TRIGGER canonicalize_address_reference_on_insert + BEFORE INSERT + ON chill_main_address_reference + FOR EACH ROW + EXECUTE procedure canonicalize_address_reference()'); + + $this->addSql('CREATE TRIGGER canonicalize_address_reference_on_update + BEFORE UPDATE + ON chill_main_address_reference + FOR EACH ROW + EXECUTE procedure canonicalize_address_reference()'); + + $this->addSql('CREATE INDEX chill_internal_address_reference_canonicalized ON chill_main_address_reference USING GIST (postcode_id, addressCanonical gist_trgm_ops)'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211125142017.php b/src/Bundle/ChillMainBundle/migrations/Version20211125142017.php new file mode 100644 index 000000000..7f938b241 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211125142017.php @@ -0,0 +1,85 @@ +addSql('DROP TRIGGER canonicalize_postal_code_on_insert ON chill_main_postal_code'); + $this->addSql('DROP TRIGGER canonicalize_postal_code_on_update ON chill_main_postal_code'); + $this->addSql('DROP FUNCTION canonicalize_postal_code()'); + $this->addSql('ALTER TABLE chill_main_postal_code DROP COLUMN canonical'); + } + + public function getDescription(): string + { + return 'Add a column "canonicalized" on postal code'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_postal_code ADD canonical TEXT DEFAULT \'\' NOT NULL'); + + $this->addSql('UPDATE chill_main_postal_code + SET canonical = + TRIM( + UNACCENT( + LOWER( + code || + \' \' || + label + ) + ) + + )'); + + $this->addSql('CREATE OR REPLACE FUNCTION public.canonicalize_postal_code() RETURNS TRIGGER + LANGUAGE plpgsql + AS + $$ + BEGIN + NEW.canonical = + TRIM( + UNACCENT( + LOWER( + NEW.code || + \' \' || + NEW.label + ) + ) + + ) + ; + + return NEW; + END + $$'); + + $this->addSql('CREATE TRIGGER canonicalize_postal_code_on_insert + BEFORE INSERT + ON chill_main_postal_code + FOR EACH ROW + EXECUTE procedure canonicalize_postal_code()'); + + $this->addSql('CREATE TRIGGER canonicalize_postal_code_on_update + BEFORE UPDATE + ON chill_main_postal_code + FOR EACH ROW + EXECUTE procedure canonicalize_postal_code()'); + + $this->addSql('CREATE INDEX chill_internal_postal_code_canonicalized ON chill_main_postal_code USING GIST (canonical gist_trgm_ops) WHERE origin = 0'); + } +} diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 7737e1d04..78363d080 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -37,7 +37,7 @@ Choose an user: Choisir un utilisateur No value: Aucune information Last updated by: Dernière mise à jour par Last updated on: Dernière mise à jour le -on: le +on: "le " Edit: Modifier Update: Mettre à jour @@ -74,7 +74,7 @@ Choose a postal code: Choisir un code postal address: address_homeless: L'adresse est-elle celle d'un domicile fixe ? real address: Adresse d'un domicile - consider homeless: N'est pas l'adresse d'un domicile (SDF) + consider homeless: Cette adresse est incomplète address more: floor: ét corridor: coul @@ -86,6 +86,10 @@ address more: Create a new address: Créer une nouvelle adresse Create an address: Créer une adresse Update address: Modifier l'adresse +City or postal code: Ville ou code postal + +# contact +Part of the phonenumber: Partie du numéro de téléphone #serach Your search is empty. Please provide search terms.: La recherche est vide. Merci de fournir des termes de recherche. @@ -176,6 +180,13 @@ Flags: Drapeaux # admin section for users jobs User jobs: Métiers +# user page for current location +Current location: Localisation actuelle +Edit my current location: Éditer ma localisation actuelle +Change current location: Changer ma localisation actuelle +Set a location: Indiquer une localisation +Current location successfully updated: Localisation actuelle mise à jour +Pick a location: Choisir un lieu #admin section for circles (old: scopes) List circles: Cercles @@ -190,7 +201,7 @@ Location: Localisation Location type list: Liste des types de localisation Create a new location type: Créer un nouveau type de localisation Available for users: Disponible aux utilisateurs -Address required: Adresse requise? +Address required: Adresse requise? Contact data: Données de contact? optional: optionnel required: requis @@ -200,11 +211,15 @@ Location list: Liste des localisations Location type: Type de localisation Phonenumber1: Numéro de téléphone Phonenumber2: Autre numéro de téléphone -Configure location: Configuration des localisations -Configure location type: Configuration des types de localisations +Configure location and location type: Configuration des localisations +Default for: Type de localisation par défaut pour +none: aucun +person: usager +thirdparty: tiers # circles / scopes Choose the circle: Choisir le cercle +Scopes: Services #export diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/SocialIssueConsistency/AccompanyingPeriodLinkedWithSocialIssuesEntityInterface.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/SocialIssueConsistency/AccompanyingPeriodLinkedWithSocialIssuesEntityInterface.php index 0f1922435..566e05440 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/SocialIssueConsistency/AccompanyingPeriodLinkedWithSocialIssuesEntityInterface.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/SocialIssueConsistency/AccompanyingPeriodLinkedWithSocialIssuesEntityInterface.php @@ -1,13 +1,22 @@ ensureConsistencyEntity($entity); } - public function preUpdate(AccompanyingPeriodLinkedWithSocialIssuesEntityInterface $entity, LifecycleEventArgs $eventArgs) - { - $this->ensureConsistencyEntity($entity); - } - public function prePersistAccompanyingPeriod(AccompanyingPeriod $period, LifecycleEventArgs $eventArgs) { $this->ensureConsistencyAccompanyingPeriod($period); } - public function preUpdateAccompanyingPeriod(AccompanyingPeriod $period, LifecycleEventArgs $eventArgs) + public function preUpdate(AccompanyingPeriodLinkedWithSocialIssuesEntityInterface $entity, LifecycleEventArgs $eventArgs) { - $this->ensureConsistencyAccompanyingPeriod($period); + $this->ensureConsistencyEntity($entity); } - private function ensureConsistencyEntity(AccompanyingPeriodLinkedWithSocialIssuesEntityInterface $entity): void + public function preUpdateAccompanyingPeriod(AccompanyingPeriod $period, LifecycleEventArgs $eventArgs) { - if (NULL === $period = $entity->getAccompanyingPeriod()) { - return; - } - // remove issues parents on the entity itself - $ancestors = SocialIssue::findAncestorSocialIssues($entity->getSocialIssues()); - foreach ($ancestors as $ancestor) { - $entity->removeSocialIssue($ancestor); - } - - foreach ($entity->getSocialIssues() as $issue) { - // the entity itself test if the social issue is already associated, or not - $period->addSocialIssue($issue); - } - $this->ensureConsistencyAccompanyingPeriod($period); } @@ -64,4 +54,24 @@ final class AccompanyingPeriodSocialIssueConsistencyEntityListener $period->removeSocialIssue($ancestor); } } + + private function ensureConsistencyEntity(AccompanyingPeriodLinkedWithSocialIssuesEntityInterface $entity): void + { + if (null === $period = $entity->getAccompanyingPeriod()) { + return; + } + // remove issues parents on the entity itself + $ancestors = SocialIssue::findAncestorSocialIssues($entity->getSocialIssues()); + + foreach ($ancestors as $ancestor) { + $entity->removeSocialIssue($ancestor); + } + + foreach ($entity->getSocialIssues() as $issue) { + // the entity itself test if the social issue is already associated, or not + $period->addSocialIssue($issue); + } + + $this->ensureConsistencyAccompanyingPeriod($period); + } } diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Suggestion/ReferralsSuggestion.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Suggestion/ReferralsSuggestion.php index b28dcce79..552a7a879 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Suggestion/ReferralsSuggestion.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Suggestion/ReferralsSuggestion.php @@ -1,12 +1,21 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Actions; use Symfony\Component\EventDispatcher\Event; /** * Event triggered when an entity attached to a person is removed. - * - * */ class ActionEvent extends Event { - const DELETE = 'CHILL_PERSON.DELETE_ASSOCIATED_ENTITY'; - const MOVE = 'CHILL_PERSON.MOVE_ASSOCIATED_ENTITY'; - + public const DELETE = 'CHILL_PERSON.DELETE_ASSOCIATED_ENTITY'; + + public const MOVE = 'CHILL_PERSON.MOVE_ASSOCIATED_ENTITY'; + /** - * - * @var int - */ - protected $personId; - - /** - * the FQDN class name as recorded in doctrine + * the FQDN class name as recorded in doctrine. * * @var string */ protected $entity; - + /** - * an array of key value data to describe the movement + * an array of key value data to describe the movement. * * @var array */ protected $metadata; - + /** - * the sql statement - * - * @var string + * @var int */ - protected $sqlStatement; - + protected $personId; + /** - * - * @var string[] - */ - protected $preSql = []; - - /** - * * @var string[] */ protected $postSql = []; - + + /** + * @var string[] + */ + protected $preSql = []; + + /** + * the sql statement. + * + * @var string + */ + protected $sqlStatement; + public function __construct($personId, $entity, $sqlStatement, $metadata = []) { $this->personId = $personId; @@ -77,21 +67,17 @@ class ActionEvent extends Event } /** - * - * @return string[] + * Add Sql which will be executed **after** the delete statement. + * + * @param type $postSql + * + * @return $this */ - public function getPreSql(): array + public function addPostSql(string $postSql) { - return $this->preSql; - } + $this->postSql[] = $postSql; - /** - * - * @return string[] - */ - public function getPostSql(): array - { - return $this->postSql; + return $this; } /* @@ -100,19 +86,21 @@ class ActionEvent extends Event public function addPreSql(string $preSql) { $this->preSql[] = $preSql; + return $this; } /** - * Add Sql which will be executed **after** the delete statement - * - * @param type $postSql - * @return $this + * get the entity name, as recorded in doctrine. */ - public function addPostSql(string $postSql) + public function getEntity(): string { - $this->postSql[] = $postSql; - return $this; + return $this->entity; + } + + public function getMetadata() + { + return $this->metadata; } public function getPersonId(): int @@ -121,23 +109,23 @@ class ActionEvent extends Event } /** - * get the entity name, as recorded in doctrine - * - * @return string + * @return string[] */ - public function getEntity(): string + public function getPostSql(): array { - return $this->entity; + return $this->postSql; } - + + /** + * @return string[] + */ + public function getPreSql(): array + { + return $this->preSql; + } + public function getSqlStatement() { return $this->sqlStatement; } - - public function getMetadata() - { - return $this->metadata; - } - } diff --git a/src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php b/src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php index bb5d9bdac..930b96e07 100644 --- a/src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php +++ b/src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php @@ -1,54 +1,47 @@ - * - * 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\Actions\Remove; - -use Doctrine\ORM\EntityManagerInterface; -use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Doctrine\ORM\Mapping\ClassMetadata; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Chill\PersonBundle\Actions\ActionEvent; /** - * Move or delete entities associated to a person to a new one, and delete the - * old person. The data associated to a person (birthdate, name, ...) are left - * untouched on the "new one". - * - * See `getSql` for details. - * + * Chill is a software for social workers * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\Actions\Remove; + +use Chill\PersonBundle\Actions\ActionEvent; +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use function array_merge; +use function implode; +use function in_array; + +/** + * Move or delete entities associated to a person to a new one, and delete the + * old person. The data associated to a person (birthdate, name, ...) are left + * untouched on the "new one". + * + * See `getSql` for details. */ class PersonMove { /** - * * @var EntityManagerInterface */ protected $em; - + /** - * * @var EventDispatcherInterface */ protected $eventDispatcher; - + public function __construct( - EntityManagerInterface $em, + EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher ) { $this->em = $em; @@ -56,128 +49,136 @@ class PersonMove } /** - * Return the sql used to move or delete entities associated to a person to - * a new one, and delete the old person. The data associated to a person + * Return the sql used to move or delete entities associated to a person to + * a new one, and delete the old person. The data associated to a person * (birthdate, name, ...) are left untouched on the "new one". - * - * The accompanying periods associated to a person are always removed. The other + * + * The accompanying periods associated to a person are always removed. The other * associated entity are updated: the new person id is associated to the entity. - * + * * Optionnaly, you can ask for removing entity by passing them in $deleteEntities - * parameters. - * + * parameters. + * * The following events are triggered: * - `'CHILL_PERSON.DELETE_ASSOCIATED_ENTITY'` is triggered when an entity * will be removed ; * - `'CHILL_PERSON.MOVE_ASSOCIATED_ENTITY'` is triggered when an entity * will be moved ; - * + * * Those events have the following metadata: - * + * * - 'original_action' : always 'move' ; * - 'to': the person id to move ; - * - * @param Person $from - * @param Person $to - * @param array $deleteEntities + * * @return type */ public function getSQL(Person $from, Person $to, array $deleteEntities = []) { $sqls = []; - $toDelete = \array_merge($deleteEntities, $this->getDeleteEntities()); - + $toDelete = array_merge($deleteEntities, $this->getDeleteEntities()); + foreach ($this->em->getMetadataFactory()->getAllMetadata() as $metadata) { if ($metadata->isMappedSuperclass) { continue; } - + foreach ($metadata->getAssociationMappings() as $field => $mapping) { - if ($mapping['targetEntity'] === Person::class) { - if (\in_array($metadata->getName(), $toDelete)) { + if (Person::class === $mapping['targetEntity']) { + if (in_array($metadata->getName(), $toDelete, true)) { $sql = $this->createDeleteSQL($metadata, $from, $field); - $event = new ActionEvent($from->getId(), $metadata->getName(), $sql, - ['to' => $to->getId(), 'original_action' => 'move']); + $event = new ActionEvent( + $from->getId(), + $metadata->getName(), + $sql, + ['to' => $to->getId(), 'original_action' => 'move'] + ); $this->eventDispatcher->dispatch(ActionEvent::DELETE, $event); - } else { $sql = $this->createMoveSQL($metadata, $from, $to, $field); - $event = new ActionEvent($from->getId(), $metadata->getName(), $sql, - ['to' => $to->getId(), 'original_action' => 'move']); + $event = new ActionEvent( + $from->getId(), + $metadata->getName(), + $sql, + ['to' => $to->getId(), 'original_action' => 'move'] + ); $this->eventDispatcher->dispatch(ActionEvent::MOVE, $event); } - $sqls = \array_merge($sqls, $event->getPreSql(), [$event->getSqlStatement()], $event->getPostSql()); + $sqls = array_merge($sqls, $event->getPreSql(), [$event->getSqlStatement()], $event->getPostSql()); } } } - + $personMetadata = $this->em->getClassMetadata(Person::class); - $sqls[] = sprintf("DELETE FROM %s WHERE id = %d", + $sqls[] = sprintf( + 'DELETE FROM %s WHERE id = %d', $this->getTableName($personMetadata), - $from->getId()); - + $from->getId() + ); + return $sqls ?? []; } - - protected function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string - { - $mapping = $metadata->getAssociationMapping($field); - - // Set part of the query, aka in "UPDATE table SET " - $sets = []; - foreach ($mapping["joinColumns"] as $columns) { - $sets[] = sprintf("%s = %d", $columns["name"], $to->getId()); - } - - $conditions = []; - foreach ($mapping["joinColumns"] as $columns) { - $conditions[] = sprintf("%s = %d", $columns["name"], $from->getId()); - } - - return \sprintf("UPDATE %s SET %s WHERE %s", - $this->getTableName($metadata), - \implode(" ", $sets), - \implode(" AND ", $conditions) - ); - } - + protected function createDeleteSQL(ClassMetadata $metadata, Person $from, $field): string { $mapping = $metadata->getAssociationMapping($field); - + $conditions = []; - foreach ($mapping["joinColumns"] as $columns) { - $conditions[] = sprintf("%s = %d", $columns["name"], $from->getId()); + + foreach ($mapping['joinColumns'] as $columns) { + $conditions[] = sprintf('%s = %d', $columns['name'], $from->getId()); } - - return \sprintf("DELETE FROM %s WHERE %s", + + return sprintf( + 'DELETE FROM %s WHERE %s', $this->getTableName($metadata), - \implode(" AND ", $conditions) - ); + implode(' AND ', $conditions) + ); } - + + protected function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string + { + $mapping = $metadata->getAssociationMapping($field); + + // Set part of the query, aka in "UPDATE table SET " + $sets = []; + + foreach ($mapping['joinColumns'] as $columns) { + $sets[] = sprintf('%s = %d', $columns['name'], $to->getId()); + } + + $conditions = []; + + foreach ($mapping['joinColumns'] as $columns) { + $conditions[] = sprintf('%s = %d', $columns['name'], $from->getId()); + } + + return sprintf( + 'UPDATE %s SET %s WHERE %s', + $this->getTableName($metadata), + implode(' ', $sets), + implode(' AND ', $conditions) + ); + } + /** - * return an array of classes where entities should be deleted - * instead of moved - * - * @return array + * return an array of classes where entities should be deleted + * instead of moved. */ protected function getDeleteEntities(): array { return [ - AccompanyingPeriod::class + AccompanyingPeriod::class, ]; } - + /** - * get the full table name with schema if it does exists + * get the full table name with schema if it does exists. */ private function getTableName(ClassMetadata $metadata): string { - return empty($metadata->getSchemaName()) ? - $metadata->getTableName() : - $metadata->getSchemaName().".".$metadata->getTableName(); + return empty($metadata->getSchemaName()) ? + $metadata->getTableName() : + $metadata->getSchemaName() . '.' . $metadata->getTableName(); } - } diff --git a/src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php b/src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php index 549792e51..d3de0bd3d 100644 --- a/src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php +++ b/src/Bundle/ChillPersonBundle/CRUD/Controller/EntityPersonCRUDController.php @@ -1,113 +1,127 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\CRUD\Controller; use Chill\MainBundle\CRUD\Controller\CRUDController; -use Symfony\Component\HttpFoundation\Request; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\QueryBuilder; +use Exception; +use Symfony\Component\HttpFoundation\Request; +use function array_merge; /** * Class EntityPersonCRUDController - * CRUD Controller for entities attached to a Person - * - * @package Chill\PersonBundle\CRUD\Controller + * CRUD Controller for entities attached to a Person. */ class EntityPersonCRUDController extends CRUDController { /** - * Extract the person from the request - * - * the person parameter will be `person_id` and must be + * Override the base method to add a filtering step to a person. + * + * @return QueryBuilder + */ + protected function buildQueryEntities(string $action, Request $request) + { + $qb = parent::buildQueryEntities($action, $request); + + return $this->filterQueryEntitiesByPerson($action, $qb, $request); + } + + /** + * @param \Chill\MainBundle\CRUD\Controller\string|string $action + */ + protected function createEntity($action, Request $request): object + { + $entity = parent::createEntity($action, $request); + + $person = $this->getPerson($request); + + $entity->setPerson($person); + + return $entity; + } + + /** + * Add a where clause to the buildQuery. + * + * @param \Chill\PersonBundle\CRUD\Controller\QueryBuilder $qb + * + * @return \Chill\PersonBundle\CRUD\Controller\QueryBuilder + */ + protected function filterQueryEntitiesByPerson(string $action, QueryBuilder $qb, Request $request): QueryBuilder + { + $qb->andWhere($qb->expr()->eq('e.person', ':person')); + $qb->setParameter('person', $this->getPerson($request)); + + return $qb; + } + + /** + * @param mixed $entity + * + * @throws Exception + */ + protected function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = []): array + { + $person = $this->getPerson($request); + + if (null === $person) { + throw new Exception('the `person_id` parameter is not set in the query. ' + . 'You should set it or override the current method to allow another ' + . 'behaviour: ' . __METHOD__); + } + + return parent::generateTemplateParameter( + $action, + $entity, + $request, + array_merge(['person' => $person], $defaultTemplateParameters) + ); + } + + /** + * Extract the person from the request. + * + * the person parameter will be `person_id` and must be * present in the query - * + * * If the parameter is not set, this method will return null. - * - * If the person id does not exists, the method will throw a + * + * If the person id does not exists, the method will throw a * Symfony\Component\HttpKernel\Exception\NotFoundHttpException - * - * @param Request $request + * * @throws Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the person with given id is not found */ protected function getPerson(Request $request): ?Person { - if (FALSE === $request->query->has('person_id')) { + if (false === $request->query->has('person_id')) { return null; } - + $person = $this->getDoctrine() ->getRepository(Person::class) - ->find($request->query->getInt('person_id')) - ; - - if (NULL === $person) { + ->find($request->query->getInt('person_id')); + + if (null === $person) { throw $this->createNotFoundException('the person with this id is not found'); } - + return $person; } - - /** - * @param \Chill\MainBundle\CRUD\Controller\string|string $action - * @param Request $request - * @return object - */ - protected function createEntity($action, Request $request): object - { - $entity = parent::createEntity($action, $request); - - $person = $this->getPerson($request); - - $entity->setPerson($person); - - return $entity; - } - + /** * @param string $action * @param mixed $entity - * @param Request $request - * @param array $defaultTemplateParameters - * @return array - * @throws \Exception - */ - protected function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = array()): array - { - $person = $this->getPerson($request); - - if (NULL === $person) { - throw new \Exception("the `person_id` parameter is not set in the query. " - . "You should set it or override the current method to allow another " - . "behaviour: ".__METHOD__); - } - - return parent::generateTemplateParameter( - $action, - $entity, - $request, - \array_merge([ 'person' => $person ], $defaultTemplateParameters) - ); - } - - /** - * @param string $action - * @param mixed $entity - * @param Request $request + * * @return string */ protected function getTemplateFor($action, $entity, Request $request) @@ -115,83 +129,59 @@ class EntityPersonCRUDController extends CRUDController if ($this->hasCustomTemplate($action, $entity, $request)) { return $this->getActionConfig($action)['template']; } - + switch ($action) { case 'new': return '@ChillPerson/CRUD/new.html.twig'; - case 'edit': + + case 'edit': return '@ChillPerson/CRUD/edit.html.twig'; + case 'view': return '@ChillPerson/CRUD/view.html.twig'; + case 'delete': return '@ChillPerson/CRUD/delete.html.twig'; + case 'index': return '@ChillPerson/CRUD/index.html.twig'; + default: return parent::getTemplateFor($action, $entity, $request); } } - + /** - * @param string $action * @param mixed $entity - * @param \Symfony\Component\Form\FormInterface $form - * @param Request $request + * * @return \Symfony\Component\HttpFoundation\RedirectResponse */ protected function onBeforeRedirectAfterSubmission(string $action, $entity, \Symfony\Component\Form\FormInterface $form, Request $request) { - $next = $request->request->get("submit", "save-and-close"); - + $next = $request->request->get('submit', 'save-and-close'); + switch ($next) { - case "save-and-close": - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index', [ - 'person_id' => $this->getPerson($request)->getId() + case 'save-and-close': + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_index', [ + 'person_id' => $this->getPerson($request)->getId(), ]); - case "save-and-new": - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_new', [ - 'person_id' => $this->getPerson($request)->getId() + + case 'save-and-new': + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_new', [ + 'person_id' => $this->getPerson($request)->getId(), ]); - case "new": - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [ + + case 'new': + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_view', [ 'id' => $entity->getId(), - 'person_id' => $this->getPerson($request)->getId() + 'person_id' => $this->getPerson($request)->getId(), ]); + default: - return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [ + return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_view', [ 'id' => $entity->getId(), - 'person_id' => $this->getPerson($request)->getId() - ]); + 'person_id' => $this->getPerson($request)->getId(), + ]); } } - - /** - * Override the base method to add a filtering step to a person. - * - * @param string $action - * @param Request $request - * @return QueryBuilder - */ - protected function buildQueryEntities(string $action, Request $request) - { - $qb = parent::buildQueryEntities($action, $request); - - return $this->filterQueryEntitiesByPerson($action, $qb, $request); - } - - /** - * Add a where clause to the buildQuery - * - * @param string $action - * @param \Chill\PersonBundle\CRUD\Controller\QueryBuilder $qb - * @param Request $request - * @return \Chill\PersonBundle\CRUD\Controller\QueryBuilder - */ - protected function filterQueryEntitiesByPerson(string $action, QueryBuilder $qb, Request $request): QueryBuilder - { - $qb->andWhere($qb->expr()->eq('e.person', ':person')); - $qb->setParameter('person', $this->getPerson($request)); - - return $qb; - } } diff --git a/src/Bundle/ChillPersonBundle/CRUD/Controller/OneToOneEntityPersonCRUDController.php b/src/Bundle/ChillPersonBundle/CRUD/Controller/OneToOneEntityPersonCRUDController.php index 720de560c..684f78d61 100644 --- a/src/Bundle/ChillPersonBundle/CRUD/Controller/OneToOneEntityPersonCRUDController.php +++ b/src/Bundle/ChillPersonBundle/CRUD/Controller/OneToOneEntityPersonCRUDController.php @@ -1,89 +1,83 @@ - * - * 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\CRUD\Controller; - -use Chill\MainBundle\CRUD\Controller\CRUDController; -use Symfony\Component\HttpFoundation\Request; -use Chill\PersonBundle\Entity\Person; -use Symfony\Component\Form\FormInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\RedirectResponse; /** - * Controller for entities attached as one-to-on to a person - * + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\CRUD\Controller; + +use BadMethodCallException; +use Chill\MainBundle\CRUD\Controller\CRUDController; +use Chill\PersonBundle\Entity\Person; +use LogicException; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + class OneToOneEntityPersonCRUDController extends CRUDController { - protected function getTemplateFor($action, $entity, Request $request) + protected function generateRedirectOnCreateRoute($action, Request $request, $entity) { - if (!empty($this->crudConfig[$action]['template'])) { - return $this->crudConfig[$action]['template']; - } - - switch ($action) { - case 'new': - return '@ChillPerson/CRUD/new.html.twig'; - case 'edit': - return '@ChillPerson/CRUD/edit.html.twig'; - case 'index': - return '@ChillPerson/CRUD/index.html.twig'; - default: - throw new \LogicException("the view for action $action is not " - . "defined. You should override ".__METHOD__." to add this " - . "action"); - } + throw new BadMethodCallException('Not implemented yet.'); } - + protected function getEntity($action, $id, Request $request): ?object { $entity = parent::getEntity($action, $id, $request); - - if (NULL === $entity) { + + if (null === $entity) { $entity = $this->createEntity($action, $request); $person = $this->getDoctrine() ->getManager() ->getRepository(Person::class) ->find($id); - + $entity->setPerson($person); } - + return $entity; } - + + protected function getTemplateFor($action, $entity, Request $request) + { + if (!empty($this->crudConfig[$action]['template'])) { + return $this->crudConfig[$action]['template']; + } + + switch ($action) { + case 'new': + return '@ChillPerson/CRUD/new.html.twig'; + + case 'edit': + return '@ChillPerson/CRUD/edit.html.twig'; + + case 'index': + return '@ChillPerson/CRUD/index.html.twig'; + + default: + throw new LogicException("the view for action {$action} is not " + . 'defined. You should override ' . __METHOD__ . ' to add this ' + . 'action'); + } + } + + protected function onPostFetchEntity($action, Request $request, $entity): ?Response + { + if (false === $this->getDoctrine()->getManager()->contains($entity)) { + return new RedirectResponse($this->generateRedirectOnCreateRoute($action, $request, $entity)); + } + + return null; + } + protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) { $this->getDoctrine()->getManager()->persist($entity); } - - protected function onPostFetchEntity($action, Request $request, $entity): ?Response - { - if (FALSE === $this->getDoctrine()->getManager()->contains($entity)) { - return new RedirectResponse($this->generateRedirectOnCreateRoute($action, $request, $entity)); - } - - return null; - } - - protected function generateRedirectOnCreateRoute($action, Request $request, $entity) - { - throw new BadMethodCallException("not implemtented yet"); - } - } diff --git a/src/Bundle/ChillPersonBundle/ChillPersonBundle.php b/src/Bundle/ChillPersonBundle/ChillPersonBundle.php index 18bf8fb79..4e73d2137 100644 --- a/src/Bundle/ChillPersonBundle/ChillPersonBundle.php +++ b/src/Bundle/ChillPersonBundle/ChillPersonBundle.php @@ -1,21 +1,30 @@ getExtension('chill_main') ->addWidgetFactory(new PersonListWidgetFactory()); - + $container->addCompilerPass(new AccompanyingPeriodTimelineCompilerPass()); } } diff --git a/src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php b/src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php index b68997ed6..3af14fb0b 100644 --- a/src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php +++ b/src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php @@ -1,24 +1,34 @@ chillLogger = $chillLogger; } + protected function buildLoggingContext(Person $from, Person $to, $deleteEntities, $sqls) + { + $ctxt = [ + 'from' => $from->getId(), + 'to' => $to->getId(), + ]; + + foreach ($deleteEntities as $key => $de) { + $ctxt['delete_entity_' . $key] = $de; + } + + foreach ($sqls as $key => $sql) { + $ctxt['sql_' . $key] = $sql; + } + + return $ctxt; + } + protected function configure() { $this ->setName('chill:person:move') ->setDescription('Move all the associated entities on a "from" person to a "to" person and remove the old person') - ->addOption('from', 'f', InputOption::VALUE_REQUIRED, "The person id to delete, all associated data will be moved before") - ->addOption('to', 't', InputOption::VALUE_REQUIRED, "The person id which will received data") - ->addOption('dump-sql', null, InputOption::VALUE_NONE, "dump sql to stdout") - ->addOption('force', null, InputOption::VALUE_NONE, "execute sql instead of dumping it") - ->addOption('delete-entity', null, InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, "entity to delete", []) - ; - } - - protected function interact(InputInterface $input, OutputInterface $output) - { - if (FALSE === $input->hasOption('dump-sql') && FALSE === $input->hasOption('force')) { - $msg = "You must use \"--dump-sql\" or \"--force\""; - throw new RuntimeException($msg); - } - - foreach (["from", "to"] as $name) { - if (empty($input->getOption($name))) { - throw new RuntimeException("You must set a \"$name\" option"); - } - $id = $input->getOption($name); - if (\ctype_digit($id) === FALSE) { - throw new RuntimeException("The id in \"$name\" field does not contains " - . "only digits: $id"); - } - } + ->addOption('from', 'f', InputOption::VALUE_REQUIRED, 'The person id to delete, all associated data will be moved before') + ->addOption('to', 't', InputOption::VALUE_REQUIRED, 'The person id which will received data') + ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'dump sql to stdout') + ->addOption('force', null, InputOption::VALUE_NONE, 'execute sql instead of dumping it') + ->addOption('delete-entity', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'entity to delete', []); } protected function execute(InputInterface $input, OutputInterface $output) { $repository = $this->em->getRepository(Person::class); $from = $repository->find($input->getOption('from')); - $to = $repository->find($input->getOption('to')); + $to = $repository->find($input->getOption('to')); $deleteEntities = $input->getOption('delete-entity'); - if ($from === NULL) { - throw new RuntimeException(sprintf("Person \"from\" with id %d not found", $input->getOption('from'))); + if (null === $from) { + throw new RuntimeException(sprintf('Person "from" with id %d not found', $input->getOption('from'))); } - if ($to === NULL) { - throw new RuntimeException(sprintf("Person \"to\" with id %d not found", $input->getOption('to'))); + + if (null === $to) { + throw new RuntimeException(sprintf('Person "to" with id %d not found', $input->getOption('to'))); } $sqls = $this->mover->getSQL($from, $to, $deleteEntities); if ($input->getOption('dump-sql')) { - foreach($sqls as $sql) { + foreach ($sqls as $sql) { $output->writeln($sql); } } else { $ctxt = $this->buildLoggingContext($from, $to, $deleteEntities, $sqls); - $this->chillLogger->notice("Trying to move a person from command line", $ctxt); + $this->chillLogger->notice('Trying to move a person from command line', $ctxt); $connection = $this->em->getConnection(); $connection->beginTransaction(); - foreach($sqls as $sql) { + + foreach ($sqls as $sql) { if ($output->isVerbose()) { $output->writeln($sql); } @@ -97,25 +107,28 @@ final class ChillPersonMoveCommand extends Command } $connection->commit(); - $this->chillLogger->notice("Move a person from command line succeeded", $ctxt); + $this->chillLogger->notice('Move a person from command line succeeded', $ctxt); } } - protected function buildLoggingContext(Person $from, Person $to, $deleteEntities, $sqls) + protected function interact(InputInterface $input, OutputInterface $output) { - $ctxt = [ - 'from' => $from->getId(), - 'to' => $to->getId() - ]; + if (false === $input->hasOption('dump-sql') && false === $input->hasOption('force')) { + $msg = 'You must use "--dump-sql" or "--force"'; - foreach ($deleteEntities as $key => $de) { - $ctxt['delete_entity_'.$key] = $de; - } - foreach ($sqls as $key => $sql) { - $ctxt['sql_'.$key] = $sql; + throw new RuntimeException($msg); } - return $ctxt; + foreach (['from', 'to'] as $name) { + if (empty($input->getOption($name))) { + throw new RuntimeException("You must set a \"{$name}\" option"); + } + $id = $input->getOption($name); + + if (ctype_digit($id) === false) { + throw new RuntimeException("The id in \"{$name}\" field does not contains " + . "only digits: {$id}"); + } + } } - } diff --git a/src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php b/src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php index 35f596295..d3500acd6 100644 --- a/src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php +++ b/src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php @@ -1,55 +1,64 @@ addArgument('csv_file', InputArgument::REQUIRED, "The CSV file to import") - ->setDescription("Import people from a csv file") - ->setHelp(<<addArgument('locale', InputArgument::REQUIRED, - "The locale to use in displaying translatable strings from entities") - ->addOption( - 'force-center', - null, - InputOption::VALUE_REQUIRED, - "The id of the center" - ) - ->addOption( - 'force', - null, - InputOption::VALUE_NONE, - "Persist people in the database (default is not to persist people)" - ) - ->addOption( - 'delimiter', - 'd', - InputOption::VALUE_OPTIONAL, - "The delimiter character of the csv file", - ",") - ->addOption( - 'enclosure', - null, - InputOption::VALUE_OPTIONAL, - "The enclosure character of the csv file", - '"' - ) - ->addOption( - 'escape', - null, - InputOption::VALUE_OPTIONAL, - "The escape character of the csv file", - "\\" - ) - ->addOption( - 'length', - null, - InputOption::VALUE_OPTIONAL, - "The length of line to read. 0 means unlimited.", - 0 - ) - ->addOption( - 'dump-choice-matching', - null, - InputOption::VALUE_REQUIRED, - "The path of the file to dump the matching between label in CSV and answers" - ) - ->addOption( - 'load-choice-matching', - null, - InputOption::VALUE_OPTIONAL, - "The path of the file to load the matching between label in CSV and answers" - ) - ; - - // mapping columns - foreach (self::$mapping as $m) { - $this->addOptionShortcut($m[0], $m[1], $m[2]); - } - - // other information - $this->addOptionShortcut('birthdate_format', 'Format preference for ' - . 'birthdate. See help for date formats preferences.', - self::$defaultDateInterpreter); - $this->addOptionShortcut('opening_date_format', 'Format preference for ' - . 'opening date. See help for date formats preferences.', - self::$defaultDateInterpreter); - $this->addOptionShortcut('closing_date_format', 'Format preference for ' - . 'closing date. See help for date formats preferences.', - self::$defaultDateInterpreter); - - // mapping column to custom fields - $this->addOption('custom-field', NULL, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - "Mapping a column to a custom fields key. Example: 1=cf_slug"); - $this->addOption('skip-interactive-field-mapping', null, InputOption::VALUE_NONE, - "Do not ask for interactive mapping"); - } - /** * This function is a shortcut to addOption. * * @param string $name * @param string $description * @param string $default + * * @return ImportPeopleFromCSVCommand */ protected function addOptionShortcut($name, $description, $default) @@ -227,122 +151,242 @@ EOF return $this; } - /** - * @param InputInterface $input - * @param OutputInterface $output - */ - protected function interact(InputInterface $input, OutputInterface $output) + protected function configure() { - // preparing the basic - $this->input = $input; - $this->output = $output; - $this->logger = new ConsoleLogger($output); + $this + ->addArgument('csv_file', InputArgument::REQUIRED, 'The CSV file to import') + ->setDescription('Import people from a csv file') + ->setHelp( + <<<'EOF' + Import people from a csv file. The first row must contains the header column and will determines where the value will be matched. - $csv = $this->openCSV(); + Date format: the possible date format may be separatedby an |. The possible format will be tryed from the first to the last. The format should be explained as http://php.net/manual/en/function.strftime.php - // getting the first row - if (($row = fgetcsv( - $csv, - $input->getOption('length'), - $input->getOption('delimiter'), - $input->getOption('enclosure'), - $input->getOption('escape'))) !== false) { + php app/console chill:person:import /tmp/hepc.csv fr_FR.utf8 \ + --firstname="Prénom" --lastname="Nom" \ + --birthdate="D.N." --birthdate_format="%d/%m/%Y" \ + --opening_date_format="%B %Y|%Y" --closing_date="der.contact" \ + --closing_date_format="%Y" --custom-field="3=code" -vvv + EOF + ) + ->addArgument( + 'locale', + InputArgument::REQUIRED, + 'The locale to use in displaying translatable strings from entities' + ) + ->addOption( + 'force-center', + null, + InputOption::VALUE_REQUIRED, + 'The id of the center' + ) + ->addOption( + 'force', + null, + InputOption::VALUE_NONE, + 'Persist people in the database (default is not to persist people)' + ) + ->addOption( + 'delimiter', + 'd', + InputOption::VALUE_OPTIONAL, + 'The delimiter character of the csv file', + ',' + ) + ->addOption( + 'enclosure', + null, + InputOption::VALUE_OPTIONAL, + 'The enclosure character of the csv file', + '"' + ) + ->addOption( + 'escape', + null, + InputOption::VALUE_OPTIONAL, + 'The escape character of the csv file', + '\\' + ) + ->addOption( + 'length', + null, + InputOption::VALUE_OPTIONAL, + 'The length of line to read. 0 means unlimited.', + 0 + ) + ->addOption( + 'dump-choice-matching', + null, + InputOption::VALUE_REQUIRED, + 'The path of the file to dump the matching between label in CSV and answers' + ) + ->addOption( + 'load-choice-matching', + null, + InputOption::VALUE_OPTIONAL, + 'The path of the file to load the matching between label in CSV and answers' + ); - try { - $this->matchColumnToCustomField($row); - } finally { - $this->logger->debug('closing csv', array('method' => __METHOD__)); - fclose($csv); - } + // mapping columns + foreach (self::$mapping as $m) { + $this->addOptionShortcut($m[0], $m[1], $m[2]); } - // load the matching between csv and label - $this->loadAnswerMatching(); - } - - /** - * @param $row - */ - protected function matchColumnToCustomField($row) - { - - $cfMappingsOptions = $this->input->getOption('custom-field'); - /* @var $em \Doctrine\Persistence\ObjectManager */ - $em = $this->em; - - foreach($cfMappingsOptions as $cfMappingStringOption) { - list($rowNumber, $cfSlug) = preg_split('|=|', $cfMappingStringOption); - - // check that the column exists, getting the column name - $column = $row[$rowNumber]; - - if (empty($column)) { - $message = "The column with row $rowNumber is empty."; - $this->logger->error($message); - throw new \RuntimeException($message); - } - - // check a custom field exists - try { - $customField = $em->createQuery("SELECT cf " - . "FROM ChillCustomFieldsBundle:CustomField cf " - . "JOIN cf.customFieldGroup g " - . "WHERE cf.slug = :slug " - . "AND g.entity = :entity") - ->setParameters(array( - 'slug' => $cfSlug, - 'entity' => Person::class - )) - ->getSingleResult(); - } catch (\Doctrine\ORM\NoResultException $e) { - $message = sprintf( - "The customfield with slug '%s' does not exists. It was associated with column number %d", - $cfSlug, - $rowNumber - ); - $this->logger->error($message); - throw new \RuntimeException($message); - } - // skip if custom field does not exists - if ($customField === NULL) { - $this->logger->error("The custom field with slug $cfSlug could not be found. " - . "Stopping this command."); - throw new \RuntimeException("The custom field with slug $cfSlug could not be found. " - . "Stopping this command."); - } - - $this->logger->notice(sprintf("Matched custom field %s (question : '%s') on column %d (displayed in the file as '%s')", - $customField->getSlug(), $this->helper->localize($customField->getName()), $rowNumber, $column)); - - $this->customFieldMapping[$rowNumber] = $customField; - } - } - - /** - * Load the mapping between answer in CSV and value in choices from a json file - */ - protected function loadAnswerMatching() - { - if ($this->input->hasOption('load-choice-matching')) { - $fs = new Filesystem(); - $filename = $this->input->getOption('load-choice-matching'); - - if (!$fs->exists($filename)) { - $this->logger->warning("The file $filename is not found. Choice matching not loaded"); - } else { - $this->logger->debug("Loading $filename as choice matching"); - $this->cacheAnswersMapping = \json_decode(\file_get_contents($filename), true); - } - } + // other information + $this->addOptionShortcut( + 'birthdate_format', + 'Format preference for ' + . 'birthdate. See help for date formats preferences.', + self::$defaultDateInterpreter + ); + $this->addOptionShortcut( + 'opening_date_format', + 'Format preference for ' + . 'opening date. See help for date formats preferences.', + self::$defaultDateInterpreter + ); + $this->addOptionShortcut( + 'closing_date_format', + 'Format preference for ' + . 'closing date. See help for date formats preferences.', + self::$defaultDateInterpreter + ); + + // mapping column to custom fields + $this->addOption( + 'custom-field', + null, + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Mapping a column to a custom fields key. Example: 1=cf_slug' + ); + $this->addOption( + 'skip-interactive-field-mapping', + null, + InputOption::VALUE_NONE, + 'Do not ask for interactive mapping' + ); } /** + * @param array $headers the processed header : an array as prepared by self::processingHeaders * + * @throws Exception */ + protected function createPerson(array $row, array $headers): Person + { + // trying to get the opening date + $openingDateString = trim($row[array_search('opening_date', $headers, true)]); + $openingDate = $this->processDate($openingDateString, $this->input->getOption('opening_date_format')); + + // @TODO: Fix the constructor parameter, $openingDate does not exists. + $person = $openingDate instanceof DateTime ? new Person($openingDate) : new Person(); + // add the center + $center = $this->getCenter($row, $headers); + + if (null === $center) { + throw new Exception('center not found'); + } + + $person->setCenter($center); + + foreach ($headers as $column => $info) { + $value = trim($row[$column]); + + switch ($info) { + case 'firstname': + $person->setFirstName($value); + + break; + + case 'lastname': + $person->setLastName($value); + + break; + + case 'birthdate': + $this->processBirthdate($person, $value); + + break; + + case 'gender': + $person->setGender($value); + + break; + + case 'opening_date': + // we have processed this when creating the person object, skipping; + break; + + case 'closing_date': + $this->processClosingDate($person, $value); + + break; + + case 'memo': + $person->setMemo($value); + + break; + + case 'email': + $person->setEmail($value); + + break; + + case 'phonenumber': + $person->setPhonenumber($value); + + break; + + case 'mobilenumber': + $person->setMobilenumber($value); + + break; + // we just keep the column number for those data + case 'postalcode': + $postalCodeValue = $value; + + break; + + case 'street1': + $street1Value = $value; + + break; + + case 'locality': + $localityValue = $value; + + break; + } + } + + // handle address + if (in_array('postalcode', $headers, true)) { + if (!empty($postalCodeValue)) { + $address = new Address(); + $postalCode = $this->guessPostalCode($postalCodeValue, $localityValue ?? ''); + + if (null === $postalCode) { + throw new Exception('The locality is not found'); + } + + $address->setPostcode($postalCode); + + if (in_array('street1', $headers, true)) { + $address->setStreetAddress1($street1Value); + } + $address->setValidFrom(new DateTime('today')); + + $person->addAddress($address); + } + } + + return $person; + } + protected function dumpAnswerMatching() { if ($this->input->hasOption('dump-choice-matching') && !empty($this->input->getOption('dump-choice-matching'))) { - $this->logger->debug("Dump the matching between answer and choices"); + $this->logger->debug('Dump the matching between answer and choices'); $str = json_encode($this->cacheAnswersMapping, JSON_PRETTY_PRINT); $fs = new Filesystem(); @@ -353,17 +397,17 @@ EOF } /** - * @param InputInterface $input - * @param OutputInterface $output - * @return int|null|void - * @throws \Exception + * @throws Exception + * + * @return int|void|null */ protected function execute(InputInterface $input, OutputInterface $output) { + $headers = $rawHeaders = []; $this->input = $input; $this->output = $output; - $this->logger->debug("Setting locale to ".$input->getArgument('locale')); + $this->logger->debug('Setting locale to ' . $input->getArgument('locale')); setlocale(LC_TIME, $input->getArgument('locale')); // opening csv as resource @@ -373,14 +417,16 @@ EOF $line = $this->line = 1; try { - while (($row = fgetcsv( - $csv, - $input->getOption('length'), - $input->getOption('delimiter'), - $input->getOption('enclosure'), - $input->getOption('escape'))) !== false) { - $this->logger->debug("Processing line ".$this->line); - if ($line === 1 ) { + while (false !== ($row = fgetcsv( + $csv, + $input->getOption('length'), + $input->getOption('delimiter'), + $input->getOption('enclosure'), + $input->getOption('escape') + ))) { + $this->logger->debug('Processing line ' . $this->line); + + if (1 === $line) { $this->logger->debug('Processing line 1, headers'); $rawHeaders = $row; $headers = $this->processingHeaders($row); @@ -404,16 +450,16 @@ EOF $this->eventDispatcher->dispatch('chill_person.person_import', $event); - if ($this->input->getOption('force') === TRUE - && $event->skipPerson === false) { + if ($this->input->getOption('force') === true + && false === $event->skipPerson) { $this->em->persist($person); } - $num ++; + ++$num; } - $line ++; - $this->line++; + ++$line; + ++$this->line; } if ($this->input->getOption('force') === true) { @@ -421,7 +467,7 @@ EOF $this->em->flush(); } } finally { - $this->logger->debug('closing csv', array('method' => __METHOD__)); + $this->logger->debug('closing csv', ['method' => __METHOD__]); fclose($csv); // dump the matching between answer and choices $this->dumpAnswerMatching(); @@ -429,240 +475,87 @@ EOF } /** - * - * @return resource - * @throws \RuntimeException + * @return Center|mixed|object|null */ - protected function openCSV() - { - $fs = new Filesystem(); - $filename = $this->input->getArgument('csv_file'); - - if (!$fs->exists($filename)) { - throw new \RuntimeException("The file does not exists or you do not " - . "have the right to read it."); - } - - $resource = fopen($filename, 'r'); - - if ($resource == FALSE) { - throw new \RuntimeException("The file '$filename' could not be opened."); - } - - return $resource; - } - - /** - * - * @param type $firstRow - * @return array where keys are column number, and value is information mapped - */ - protected function processingHeaders($firstRow) - { - $availableOptions = array_map(function($m) { return $m[0]; }, self::$mapping); - $matchedColumnHeaders = array(); - $headers = array(); - - foreach($availableOptions as $option) { - $matchedColumnHeaders[$option] = $this->input->getOption($option); - } - - foreach($firstRow as $key => $content) { - $content = trim($content); - if (in_array($content, $matchedColumnHeaders)) { - $information = array_search($content, $matchedColumnHeaders); - $headers[$key] = $information; - $this->logger->notice("Matched $information on column $key (displayed in the file as '$content')"); - } else { - $this->logger->notice("Column with content '$content' is ignored"); - } - } - - return $headers; - } - - /** - * - * @param array $row - * @param array $headers the processed header : an array as prepared by self::processingHeaders - * @return Person - * @throws \Exception - */ - protected function createPerson($row, $headers) - { - // trying to get the opening date - $openingDateString = trim($row[array_search('opening_date', $headers)]); - $openingDate = $this->processDate($openingDateString, $this->input->getOption('opening_date_format')); - - $person = $openingDate instanceof \DateTime ? new Person($openingDate) : new Person(); - // add the center - $center = $this->getCenter($row, $headers); - - if ($center === null) { - throw new \Exception("center not found"); - } - - $person->setCenter($center); - - foreach($headers as $column => $info) { - - $value = trim($row[$column]); - - switch($info) { - case 'firstname': - $person->setFirstName($value); - break; - case 'lastname': - $person->setLastName($value); - break; - case 'birthdate': - $this->processBirthdate($person, $value); - break; - case 'gender': - $person->setGender($value); - break; - case 'opening_date': - // we have processed this when creating the person object, skipping; - break; - case 'closing_date': - $this->processClosingDate($person, $value); - break; - case 'memo': - $person->setMemo($value); - break; - case 'email': - $person->setEmail($value); - break; - case 'phonenumber': - $person->setPhonenumber($value); - break; - case 'mobilenumber': - $person->setMobilenumber($value); - break; - - // we just keep the column number for those data - case 'postalcode': - $postalCodeValue = $value; - break; - case 'street1': - $street1Value = $value; - break; - case 'locality': - $localityValue = $value; - break; - } - } - - // handle address - if (\in_array('postalcode', $headers)) { - - if (! empty($postalCodeValue)) { - - $address = new Address(); - $postalCode = $this->guessPostalCode($postalCodeValue, $localityValue ?? ''); - - if ($postalCode === null) { - throw new \Exception("The locality is not found"); - } - - $address->setPostcode($postalCode); - - if (\in_array('street1', $headers)) { - $address->setStreetAddress1($street1Value); - } - $address->setValidFrom(new \DateTime('today')); - - $person->addAddress($address); - } - } - - return $person; - } - - /** - * @param $row - * @param $headers - * @return Center|mixed|null|object - */ - protected function getCenter($row, $headers) + protected function getCenter(array $row, array $headers) { if ($this->input->hasOption('force-center') && !empty($this->input->getOption('force-center'))) { - return $this->em->getRepository('ChillMainBundle:Center') - ->find($this->input->getOption('force-center')); - } else { - $columnCenter = \array_search('center', $headers); - $centerName = \trim($row[$columnCenter]); + return $this->em->getRepository(Center::class)->find($this->input->getOption('force-center')); + } - try { - return $this->em->createQuery('SELECT c FROM ChillMainBundle:Center c ' - . 'WHERE c.name = :center_name') - ->setParameter('center_name', $centerName) - ->getSingleResult() - ; - } catch (\Doctrine\ORM\NonUniqueResultException $e) { - return $this->guessCenter($centerName); - } catch (\Doctrine\ORM\NoResultException $e) { - return $this->guessCenter($centerName); - } + $columnCenter = array_search('center', $headers, true); + $centerName = trim($row[$columnCenter]); + + try { + return $this + ->em + ->createQuery('SELECT c FROM ChillMainBundle:Center c WHERE c.name = :center_name') + ->setParameter('center_name', $centerName) + ->getSingleResult(); + } catch (NonUniqueResultException $e) { + return $this->guessCenter($centerName); + } catch (NoResultException $e) { + return $this->guessCenter($centerName); } } /** * @param $centerName - * @return Center|mixed|null|object + * + * @return Center|mixed|object|null */ protected function guessCenter($centerName) { - if (!\array_key_exists('_center_picked', $this->cacheAnswersMapping)) { + if (!array_key_exists('_center_picked', $this->cacheAnswersMapping)) { $this->cacheAnswersMapping['_center_picked'] = []; } - if (\array_key_exists($centerName, $this->cacheAnswersMapping['_center_picked'])) { + if (array_key_exists($centerName, $this->cacheAnswersMapping['_center_picked'])) { $id = $this->cacheAnswersMapping['_center_picked'][$centerName]; return $this->em->getRepository(Center::class) ->find($id); } - $centers = $this->em->createQuery("SELECT c FROM ChillMainBundle:Center c " - . "ORDER BY SIMILARITY(c.name, :center_name) DESC") + $centers = $this->em->createQuery('SELECT c FROM ChillMainBundle:Center c ' + . 'ORDER BY SIMILARITY(c.name, :center_name) DESC') ->setParameter('center_name', $centerName) ->setMaxResults(10) - ->getResult() - ; + ->getResult(); if (count($centers) > 1) { - if (\strtolower($centers[0]->getName()) === \strtolower($centerName)) { + if (strtolower($centers[0]->getName()) === strtolower($centerName)) { return $centers[0]; } } $centersByName = []; - $names = \array_map(function(Center $c) use (&$centersByName) { + $names = array_map(static function (Center $c) use (&$centersByName) { $n = $c->getName(); $centersByName[$n] = $c; - return $n; + return $n; }, $centers); - $names[] = "none of them"; + $names[] = 'none of them'; $helper = $this->getHelper('question'); - $question = new ChoiceQuestion(sprintf("Which center match the name \"%s\" ? (default to \"%s\")", $centerName, $names[0]), + $question = new ChoiceQuestion( + sprintf('Which center match the name "%s" ? (default to "%s")', $centerName, $names[0]), $names, - 0); + 0 + ); $answer = $helper->ask($this->input, $this->output, $question); - if ($answer === 'none of them') { - $questionCreate = new ConfirmationQuestion("Would you like to create it ?", false); + if ('none of them' === $answer) { + $questionCreate = new ConfirmationQuestion('Would you like to create it ?', false); $create = $helper->ask($this->input, $this->output, $questionCreate); if ($create) { $center = (new Center()) - ->setName($centerName) - ; + ->setName($centerName); - if ($this->input->getOption('force') === TRUE) { + if ($this->input->getOption('force') === true) { $this->em->persist($center); $this->em->flush(); } @@ -681,31 +574,32 @@ EOF /** * @param $postalCode * @param $locality + * * @return mixed|null */ protected function guessPostalCode($postalCode, $locality) { - if (!\array_key_exists('_postal_code_picked', $this->cacheAnswersMapping)) { + if (!array_key_exists('_postal_code_picked', $this->cacheAnswersMapping)) { $this->cacheAnswersMapping['_postal_code_picked'] = []; } - if (\array_key_exists($postalCode, $this->cacheAnswersMapping['_postal_code_picked'])) { - if (\array_key_exists($locality, $this->cacheAnswersMapping['_postal_code_picked'][$postalCode])) { + if (array_key_exists($postalCode, $this->cacheAnswersMapping['_postal_code_picked'])) { + if (array_key_exists($locality, $this->cacheAnswersMapping['_postal_code_picked'][$postalCode])) { $id = $this->cacheAnswersMapping['_postal_code_picked'][$postalCode][$locality]; return $this->em->getRepository(PostalCode::class)->find($id); } } - $postalCodes = $this->em->createQuery("SELECT pc FROM ".PostalCode::class." pc " - . "WHERE pc.code = :postal_code " - . "ORDER BY SIMILARITY(pc.name, :locality) DESC " - ) + $postalCodes = $this->em->createQuery( + 'SELECT pc FROM ' . PostalCode::class . ' pc ' + . 'WHERE pc.code = :postal_code ' + . 'ORDER BY SIMILARITY(pc.name, :locality) DESC ' + ) ->setMaxResults(10) ->setParameter('postal_code', $postalCode) ->setParameter('locality', $locality) - ->getResult() - ; + ->getResult(); if (count($postalCodes) >= 1) { if ($postalCodes[0]->getCode() === $postalCode @@ -719,7 +613,7 @@ EOF } $postalCodeByName = []; - $names = \array_map(function(PostalCode $pc) use (&$postalCodeByName) { + $names = array_map(static function (PostalCode $pc) use (&$postalCodeByName) { $n = $pc->getName(); $postalCodeByName[$n] = $pc; @@ -728,15 +622,21 @@ EOF $names[] = 'none of them'; $helper = $this->getHelper('question'); - $question = new ChoiceQuestion(sprintf("Which postal code match the " - . "name \"%s\" with postal code \"%s\" ? (default to \"%s\")", - $locality, $postalCode, $names[0]), + $question = new ChoiceQuestion( + sprintf( + 'Which postal code match the ' + . 'name "%s" with postal code "%s" ? (default to "%s")', + $locality, + $postalCode, + $names[0] + ), $names, - 0); + 0 + ); $answer = $helper->ask($this->input, $this->output, $question); - if ($answer === 'none of them') { + if ('none of them' === $answer) { return null; } @@ -748,22 +648,160 @@ EOF return $pc; } + protected function interact(InputInterface $input, OutputInterface $output) + { + // preparing the basic + $this->input = $input; + $this->output = $output; + $this->logger = new ConsoleLogger($output); + + $csv = $this->openCSV(); + + // getting the first row + if (false !== ($row = fgetcsv( + $csv, + $input->getOption('length'), + $input->getOption('delimiter'), + $input->getOption('enclosure'), + $input->getOption('escape') + ))) { + try { + $this->matchColumnToCustomField($row); + } finally { + $this->logger->debug('closing csv', ['method' => __METHOD__]); + fclose($csv); + } + } + + // load the matching between csv and label + $this->loadAnswerMatching(); + } + + /** + * Load the mapping between answer in CSV and value in choices from a json file. + */ + protected function loadAnswerMatching() + { + if ($this->input->hasOption('load-choice-matching')) { + $fs = new Filesystem(); + $filename = $this->input->getOption('load-choice-matching'); + + if (!$fs->exists($filename)) { + $this->logger->warning("The file {$filename} is not found. Choice matching not loaded"); + } else { + $this->logger->debug("Loading {$filename} as choice matching"); + $this->cacheAnswersMapping = json_decode(file_get_contents($filename), true); + } + } + } + + /** + * @param $row + */ + protected function matchColumnToCustomField($row) + { + $cfMappingsOptions = $this->input->getOption('custom-field'); + /** @var \Doctrine\Persistence\ObjectManager $em */ + $em = $this->em; + + foreach ($cfMappingsOptions as $cfMappingStringOption) { + [$rowNumber, $cfSlug] = preg_split('|=|', $cfMappingStringOption); + + // check that the column exists, getting the column name + $column = $row[$rowNumber]; + + if (empty($column)) { + $message = "The column with row {$rowNumber} is empty."; + $this->logger->error($message); + + throw new RuntimeException($message); + } + + // check a custom field exists + try { + $customField = $em->createQuery('SELECT cf ' + . 'FROM ChillCustomFieldsBundle:CustomField cf ' + . 'JOIN cf.customFieldGroup g ' + . 'WHERE cf.slug = :slug ' + . 'AND g.entity = :entity') + ->setParameters([ + 'slug' => $cfSlug, + 'entity' => Person::class, + ]) + ->getSingleResult(); + } catch (\Doctrine\ORM\NoResultException $e) { + $message = sprintf( + "The customfield with slug '%s' does not exists. It was associated with column number %d", + $cfSlug, + $rowNumber + ); + $this->logger->error($message); + + throw new RuntimeException($message); + } + // skip if custom field does not exists + if (null === $customField) { + $this->logger->error("The custom field with slug {$cfSlug} could not be found. " + . 'Stopping this command.'); + + throw new RuntimeException("The custom field with slug {$cfSlug} could not be found. " + . 'Stopping this command.'); + } + + $this->logger->notice(sprintf( + "Matched custom field %s (question : '%s') on column %d (displayed in the file as '%s')", + $customField->getSlug(), + $this->helper->localize($customField->getName()), + $rowNumber, + $column + )); + + $this->customFieldMapping[$rowNumber] = $customField; + } + } + + /** + * @throws RuntimeException + * + * @return resource + */ + protected function openCSV() + { + $fs = new Filesystem(); + $filename = $this->input->getArgument('csv_file'); + + if (!$fs->exists($filename)) { + throw new RuntimeException('The file does not exists or you do not ' + . 'have the right to read it.'); + } + + $resource = fopen($filename, 'rb'); + + if (false === $resource) { + throw new RuntimeException("The file '{$filename}' could not be opened."); + } + + return $resource; + } + /** - * @param Person $person * @param $value - * @throws \Exception + * + * @throws Exception */ protected function processBirthdate(Person $person, $value) { - if (empty($value)) { return; } + if (empty($value)) { + return; + } $date = $this->processDate($value, $this->input->getOption('birthdate_format')); - if ($date instanceof \DateTime) { + if ($date instanceof DateTime) { // we correct birthdate if the date is in the future // the most common error is to set date 100 years to late (ex. 2063 instead of 1963) - if ($date > new \DateTime('yesterday')) { - $date = $date->sub(new \DateInterval('P100Y')); + if ($date > new DateTime('yesterday')) { + $date = $date->sub(new DateInterval('P100Y')); } $person->setBirthdate($date); @@ -773,120 +811,12 @@ EOF // if we arrive here, we could not process the date $this->logger->warning(sprintf( - "Line %d : the birthdate could not be interpreted. Was %s.", - $this->line, - $value)); - + 'Line %d : the birthdate could not be interpreted. Was %s.', + $this->line, + $value + )); } - /** - * @param Person $person - * @param $value - * @throws \Exception - */ - protected function processClosingDate(Person $person, $value) - { - if (empty($value)) { return; } - - // we skip if the opening date is now (or after yesterday) - /* @var $period \Chill\PersonBundle\Entity\AccompanyingPeriod */ - $period = $person->getCurrentAccompanyingPeriod(); - - if ($period->getOpeningDate() > new \DateTime('yesterday')) { - $this->logger->debug(sprintf("skipping a closing date because opening date is after yesterday (%s)", - $period->getOpeningDate()->format('Y-m-d'))); - return; - } - - - $date = $this->processDate($value, $this->input->getOption('closing_date_format')); - - if ($date instanceof \DateTime) { - // we correct birthdate if the date is in the future - // the most common error is to set date 100 years to late (ex. 2063 instead of 1963) - if ($date > new \DateTime('yesterday')) { - $date = $date->sub(new \DateInterval('P100Y')); - } - - $period->setClosingDate($date); - $person->close(); - return; - } - - // if we arrive here, we could not process the date - $this->logger->warning(sprintf( - "Line %d : the closing date could not be interpreted. Was %s.", - $this->line, - $value)); - } - - /** - * @param Person $person - * @param $row - * @throws \Exception - */ - protected function processingCustomFields(Person $person, $row) - { - - /* @var $cfProvider \Chill\CustomFieldsBundle\Service\CustomFieldProvider */ - $cfProvider = $this->customFieldProvider; - $cfData = array(); - - /* @var $$customField \Chill\CustomFieldsBundle\Entity\CustomField */ - foreach($this->customFieldMapping as $rowNumber => $customField) { - $builder = $this->formFactory->createBuilder(); - $cfProvider->getCustomFieldByType($customField->getType()) - ->buildForm($builder, $customField); - $form = $builder->getForm(); - - // get the type of the form - $type = get_class($form->get($customField->getSlug()) - ->getConfig()->getType()->getInnerType()); - $this->logger->debug(sprintf("Processing a form of type %s", - $type)); - - switch ($type) { - case \Symfony\Component\Form\Extension\Core\Type\TextType::class: - $cfData[$customField->getSlug()] = - $this->processTextType($row[$rowNumber], $form, $customField); - break; - case \Symfony\Component\Form\Extension\Core\Type\ChoiceType::class: - case \Chill\MainBundle\Form\Type\Select2ChoiceType::class: - $cfData[$customField->getSlug()] = - $this->processChoiceType($row[$rowNumber], $form, $customField); - } - - } - - $person->setCFData($cfData); - } - - /** - * Process a text type on a custom field - * - * @param type $value - * @param \Symfony\Component\Form\FormInterface $form - * @return type - */ - protected function processTextType( - $value, - \Symfony\Component\Form\FormInterface $form, - \Chill\CustomFieldsBundle\Entity\CustomField $cf - ) - { - $form->submit(array($cf->getSlug() => $value)); - - $value = $form->getData()[$cf->getSlug()]; - - $this->logger->debug(sprintf("Found value : %s for custom field with question " - . "'%s'", $value, $this->helper->localize($cf->getName()))); - - return $value; - } - - protected $cacheAnswersMapping = array(); - - /** * Process a custom field choice. * @@ -894,17 +824,16 @@ EOF * choices. If the texts exists, then this is picked. Else, ask the user. * * @param string $value - * @param \Symfony\Component\Form\FormInterface $form - * @param \Chill\CustomFieldsBundle\Entity\CustomField $cf + * + * @throws Exception + * * @return string - * @throws \Exception */ protected function processChoiceType( - $value, - \Symfony\Component\Form\FormInterface $form, - \Chill\CustomFieldsBundle\Entity\CustomField $cf - ) - { + $value, + \Symfony\Component\Form\FormInterface $form, + \Chill\CustomFieldsBundle\Entity\CustomField $cf + ) { // getting the possible answer and their value : $view = $form->get($cf->getSlug())->createView(); $answers = $this->collectChoicesAnswers($view->vars['choices']); @@ -912,100 +841,294 @@ EOF // if we do not have any answer on the question, throw an error. if (count($answers) === 0) { $message = sprintf( - "The question '%s' with slug '%s' does not count any answer.", - $this->helper->localize($cf->getName()), - $cf->getSlug() - ); + "The question '%s' with slug '%s' does not count any answer.", + $this->helper->localize($cf->getName()), + $cf->getSlug() + ); - $this->logger->error($message, array( + $this->logger->error($message, [ 'method' => __METHOD__, 'slug' => $cf->getSlug(), - 'question' => $this->helper->localize($cf->getName()) - )); + 'question' => $this->helper->localize($cf->getName()), + ]); - throw new \RuntimeException($message); + throw new RuntimeException($message); } - if ($view->vars['required'] === false) { + if (false === $view->vars['required']) { $answers[null] = '** no answer'; } // the answer does not exists in cache. Try to find it, or asks the user if (!isset($this->cacheAnswersMapping[$cf->getSlug()][$value])) { - // try to find the answer (with array_keys and a search value $values = array_keys( - array_map(function($label) { return trim(strtolower($label)); }, $answers), - trim(strtolower($value)), - true - ); + array_map(static function ($label) { return trim(strtolower($label)); }, $answers), + trim(strtolower($value)), + true + ); if (count($values) === 1) { // we could guess an answer ! - $this->logger->info("This question accept multiple answers"); + $this->logger->info('This question accept multiple answers'); $this->cacheAnswersMapping[$cf->getSlug()][$value] = - $view->vars['multiple'] == false ? $values[0] : array($values[0]); - $this->logger->info(sprintf("Guessed that value '%s' match with key '%s' " - . "because the CSV and the label are equals.", - $value, $values[0])); + false === $view->vars['multiple'] ? $values[0] : [$values[0]]; + $this->logger->info(sprintf( + "Guessed that value '%s' match with key '%s' " + . 'because the CSV and the label are equals.', + $value, + $values[0] + )); } else { // we could nog guess an answer. Asking the user. - $this->output->writeln("I do not know the answer to this question : "); + $this->output->writeln('I do not know the answer to this question : '); $this->output->writeln($this->helper->localize($cf->getName())); // printing the possible answers - /* @var $table \Symfony\Component\Console\Helper\Table */ + /** @var \Symfony\Component\Console\Helper\Table $table */ $table = new Table($this->output); - $table->setHeaders(array('#', 'label', 'value')); + $table->setHeaders(['#', 'label', 'value']); $i = 0; - foreach($answers as $key => $answer) { - $table->addRow(array( - $i, $answer, $key - )); + $matchingTableRowAnswer = []; + + foreach ($answers as $key => $answer) { + $table->addRow([ + $i, $answer, $key, + ]); $matchingTableRowAnswer[$i] = $key; - $i++; + ++$i; } $table->render($this->output); $question = new ChoiceQuestion( - sprintf('Please pick your choice for the value "%s"', $value), - array_keys($matchingTableRowAnswer) - ); - $question->setErrorMessage("This choice is not possible"); + sprintf('Please pick your choice for the value "%s"', $value), + array_keys($matchingTableRowAnswer) + ); + $question->setErrorMessage('This choice is not possible'); if ($view->vars['multiple']) { - $this->logger->debug("this question is multiple"); + $this->logger->debug('this question is multiple'); $question->setMultiselect(true); } $selected = $this->getHelper('question')->ask($this->input, $this->output, $question); - $this->output->writeln(sprintf('You have selected "%s"', - is_array($answers[$matchingTableRowAnswer[$selected]]) ? + $this->output->writeln( + sprintf( + 'You have selected "%s"', + is_array($answers[$matchingTableRowAnswer[$selected]]) ? implode(',', $answers[$matchingTableRowAnswer[$selected]]) : - $answers[$matchingTableRowAnswer[$selected]]) - ); + $answers[$matchingTableRowAnswer[$selected]] + ) + ); // recording value in cache $this->cacheAnswersMapping[$cf->getSlug()][$value] = $matchingTableRowAnswer[$selected]; - $this->logger->debug(sprintf("Setting the value '%s' in cache for customfield '%s' and answer '%s'", - is_array($this->cacheAnswersMapping[$cf->getSlug()][$value]) ? + $this->logger->debug(sprintf( + "Setting the value '%s' in cache for customfield '%s' and answer '%s'", + is_array($this->cacheAnswersMapping[$cf->getSlug()][$value]) ? implode(', ', $this->cacheAnswersMapping[$cf->getSlug()][$value]) : $this->cacheAnswersMapping[$cf->getSlug()][$value], - $cf->getSlug(), - $value)); + $cf->getSlug(), + $value + )); } } - $form->submit(array($cf->getSlug() => $this->cacheAnswersMapping[$cf->getSlug()][$value])); + $form->submit([$cf->getSlug() => $this->cacheAnswersMapping[$cf->getSlug()][$value]]); $value = $form->getData()[$cf->getSlug()]; - $this->logger->debug(sprintf( + $this->logger->debug( + sprintf( "Found value : %s for custom field with question '%s'", is_array($value) ? implode(',', $value) : $value, - $this->helper->localize($cf->getName())) + $this->helper->localize($cf->getName()) + ) + ); + + return $value; + } + + /** + * @param $value + * + * @throws Exception + */ + protected function processClosingDate(Person $person, $value) + { + if (empty($value)) { + return; + } + + // we skip if the opening date is now (or after yesterday) + /** @var \Chill\PersonBundle\Entity\AccompanyingPeriod $period */ + $period = $person->getCurrentAccompanyingPeriod(); + + if ($period->getOpeningDate() > new DateTime('yesterday')) { + $this->logger->debug(sprintf( + 'skipping a closing date because opening date is after yesterday (%s)', + $period->getOpeningDate()->format('Y-m-d') + )); + + return; + } + + $date = $this->processDate($value, $this->input->getOption('closing_date_format')); + + if ($date instanceof DateTime) { + // we correct birthdate if the date is in the future + // the most common error is to set date 100 years to late (ex. 2063 instead of 1963) + if ($date > new DateTime('yesterday')) { + $date = $date->sub(new DateInterval('P100Y')); + } + + $period->setClosingDate($date); + $person->close(); + + return; + } + + // if we arrive here, we could not process the date + $this->logger->warning(sprintf( + 'Line %d : the closing date could not be interpreted. Was %s.', + $this->line, + $value + )); + } + + /** + * @param $value + * @param $formats + * + * @return bool|DateTime + */ + protected function processDate($value, $formats) + { + $possibleFormats = explode('|', $formats); + + foreach ($possibleFormats as $format) { + $this->logger->debug("Trying format {$format}", [__METHOD__]); + $dateR = strptime($value, $format); + + if (is_array($dateR) && '' === $dateR['unparsed']) { + $string = sprintf( + '%04d-%02d-%02d %02d:%02d:%02d', + ($dateR['tm_year'] + 1900), + ($dateR['tm_mon'] + 1), + ($dateR['tm_mday']), + ($dateR['tm_hour']), + ($dateR['tm_min']), + ($dateR['tm_sec']) ); + $date = DateTime::createFromFormat('Y-m-d H:i:s', $string); + $this->logger->debug(sprintf('Interpreting %s as date %s', $value, $date->format('Y-m-d H:i:s'))); + + return $date; + } + } + + // if we arrive here, we could not process the date + $this->logger->debug(sprintf( + 'Line %d : a date could not be interpreted. Was %s.', + $this->line, + $value + )); + + return false; + } + + /** + * @param $row + * + * @throws Exception + */ + protected function processingCustomFields(Person $person, $row) + { + /** @var \Chill\CustomFieldsBundle\Service\CustomFieldProvider $cfProvider */ + $cfProvider = $this->customFieldProvider; + $cfData = []; + + /** @var \Chill\CustomFieldsBundle\Entity\CustomField $$customField */ + foreach ($this->customFieldMapping as $rowNumber => $customField) { + $builder = $this->formFactory->createBuilder(); + $cfProvider->getCustomFieldByType($customField->getType()) + ->buildForm($builder, $customField); + $form = $builder->getForm(); + + // get the type of the form + $type = get_class($form->get($customField->getSlug()) + ->getConfig()->getType()->getInnerType()); + $this->logger->debug(sprintf( + 'Processing a form of type %s', + $type + )); + + switch ($type) { + case \Symfony\Component\Form\Extension\Core\Type\TextType::class: + $cfData[$customField->getSlug()] = + $this->processTextType($row[$rowNumber], $form, $customField); + + break; + + case \Symfony\Component\Form\Extension\Core\Type\ChoiceType::class: + case \Chill\MainBundle\Form\Type\Select2ChoiceType::class: + $cfData[$customField->getSlug()] = + $this->processChoiceType($row[$rowNumber], $form, $customField); + } + } + + $person->setCFData($cfData); + } + + /** + * @return array where keys are column number, and value is information mapped + */ + protected function processingHeaders(array $firstRow): array + { + $availableOptions = array_map( + static fn (array $m) => $m[0], + self::$mapping + ); + $matchedColumnHeaders = $headers = []; + + foreach ($availableOptions as $option) { + $matchedColumnHeaders[$option] = $this->input->getOption($option); + } + + foreach ($firstRow as $key => $content) { + $content = trim($content); + + if (in_array($content, $matchedColumnHeaders, true)) { + $information = array_search($content, $matchedColumnHeaders, true); + $headers[$key] = $information; + $this->logger->notice("Matched {$information} on column {$key} (displayed in the file as '{$content}')"); + } else { + $this->logger->notice("Column with content '{$content}' is ignored"); + } + } + + return $headers; + } + + /** + * Process a text type on a custom field. + * + * @param type $value + * + * @return type + */ + protected function processTextType( + $value, + \Symfony\Component\Form\FormInterface $form, + \Chill\CustomFieldsBundle\Entity\CustomField $cf + ) { + $form->submit([$cf->getSlug() => $value]); + + $value = $form->getData()[$cf->getSlug()]; + + $this->logger->debug(sprintf('Found value : %s for custom field with question ' + . "'%s'", $value, $this->helper->localize($cf->getName()))); return $value; } @@ -1014,69 +1137,32 @@ EOF * Recursive method to collect the possibles answer from a ChoiceType (or * its inherited types). * - * @param \Symfony\Component\Form\FormInterface $form + * @param mixed $choices + * + * @throws Exception + * * @return array where - * @throws \Exception */ private function collectChoicesAnswers($choices) { - $answers = array(); + $answers = []; - /* @var $choice \Symfony\Component\Form\ChoiceList\View\ChoiceView */ - foreach($choices as $choice) { + /** @var \Symfony\Component\Form\ChoiceList\View\ChoiceView $choice */ + foreach ($choices as $choice) { if ($choice instanceof \Symfony\Component\Form\ChoiceList\View\ChoiceView) { $answers[$choice->value] = $choice->label; } elseif ($choice instanceof \Symfony\Component\Form\ChoiceList\View\ChoiceGroupView) { $answers = $answers + $this->collectChoicesAnswers($choice->choices); } else { - throw new \Exception(sprintf( - "The choice type is not know. Expected '%s' or '%s', get '%s'", - \Symfony\Component\Form\ChoiceList\View\ChoiceView::class, - \Symfony\Component\Form\ChoiceList\View\ChoiceGroupView::class, - get_class($choice) - )); + throw new Exception(sprintf( + "The choice type is not know. Expected '%s' or '%s', get '%s'", + \Symfony\Component\Form\ChoiceList\View\ChoiceView::class, + \Symfony\Component\Form\ChoiceList\View\ChoiceGroupView::class, + get_class($choice) + )); } } return $answers; } - - /** - * @param $value - * @param $formats - * @return bool|\DateTime - */ - protected function processDate($value, $formats) - { - $possibleFormats = explode("|", $formats); - - foreach($possibleFormats as $format) { - $this->logger->debug("Trying format $format", array(__METHOD__)); - $dateR = strptime($value, $format); - - if (is_array($dateR) && $dateR['unparsed'] === '') { - $string = sprintf("%04d-%02d-%02d %02d:%02d:%02d", - ($dateR['tm_year']+1900), - ($dateR['tm_mon']+1), - ($dateR['tm_mday']), - ($dateR['tm_hour']), - ($dateR['tm_min']), - ($dateR['tm_sec'])); - $date = \DateTime::createFromFormat("Y-m-d H:i:s", $string); - $this->logger->debug(sprintf("Interpreting %s as date %s", $value, $date->format("Y-m-d H:i:s"))); - - return $date; - } - } - - // if we arrive here, we could not process the date - $this->logger->debug(sprintf( - "Line %d : a date could not be interpreted. Was %s.", - $this->line, - $value)); - - return false; - } - - } diff --git a/src/Bundle/ChillPersonBundle/Command/ImportSocialWorkMetadata.php b/src/Bundle/ChillPersonBundle/Command/ImportSocialWorkMetadata.php index e42f0ef26..b04cff13a 100644 --- a/src/Bundle/ChillPersonBundle/Command/ImportSocialWorkMetadata.php +++ b/src/Bundle/ChillPersonBundle/Command/ImportSocialWorkMetadata.php @@ -1,26 +1,30 @@ setDelimiter(';'); return true === $this->importer->import($csv) ? - 0: + 0 : 1; } } diff --git a/src/Bundle/ChillPersonBundle/Config/ConfigPersonAltNamesHelper.php b/src/Bundle/ChillPersonBundle/Config/ConfigPersonAltNamesHelper.php index 92c093385..ae291067b 100644 --- a/src/Bundle/ChillPersonBundle/Config/ConfigPersonAltNamesHelper.php +++ b/src/Bundle/ChillPersonBundle/Config/ConfigPersonAltNamesHelper.php @@ -1,61 +1,58 @@ config = $config; } - + /** - * Return true if at least one alt name is configured - * - * @return bool - */ - public function hasAltNames(): bool - { - return count($this->config) > 0; - } - - /** - * get the choices as key => values - * - * @return array + * get the choices as key => values. */ public function getChoices(): array { $choices = []; + foreach ($this->config as $entry) { - $labels = $entry['labels']; $lang = false; $label = false; $cur = reset($labels); + while ($cur) { if (key($labels) === 'lang') { $lang = current($labels); } - + if (key($labels) === 'label') { $label = current($labels); } - - if ($lang !== FALSE && $label !== FALSE) { + + if (false !== $lang && false !== $label) { $choices[$entry['key']][$lang] = $label; $lang = false; $label = false; @@ -63,8 +60,15 @@ class ConfigPersonAltNamesHelper $cur = next($labels); } } - + return $choices; } + /** + * Return true if at least one alt name is configured. + */ + public function hasAltNames(): bool + { + return count($this->config) > 0; + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php index bce888876..9445dc302 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php @@ -1,63 +1,92 @@ eventDispatcher = $eventDispatcher; $this->validator = $validator; $this->registry = $registry; + $this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository; $this->referralAvailable = $referralAvailable; } + public function commentApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('comment', $id, $request, $_format, 'comment', Comment::class); + } + public function confirmApi($id, Request $request, $_format): Response { /** @var AccompanyingPeriod $accompanyingPeriod */ $accompanyingPeriod = $this->getEntity('participation', $id, $request); $this->checkACL('confirm', $request, $_format, $accompanyingPeriod); -$workflow = $this->registry->get($accompanyingPeriod); + $workflow = $this->registry->get($accompanyingPeriod); - if (FALSE === $workflow->can($accompanyingPeriod, 'confirm')) { - throw new BadRequestException('It is not possible to confirm this period'); + if (false === $workflow->can($accompanyingPeriod, 'confirm')) { + // throw new BadRequestException('It is not possible to confirm this period'); + $errors = $this->validator->validate($accompanyingPeriod, null, [$accompanyingPeriod::STEP_CONFIRMED]); + + if (count($errors) > 0) { + return $this->json($errors, 422); + } } $workflow->apply($accompanyingPeriod, 'confirm'); @@ -65,10 +94,26 @@ $workflow = $this->registry->get($accompanyingPeriod); $this->getDoctrine()->getManager()->flush(); return $this->json($accompanyingPeriod, Response::HTTP_OK, [], [ - 'groups' => [ 'read' ] + 'groups' => ['read'], ]); } + /** + * @ParamConverter("person", options={"id": "person_id"}) + */ + public function getAccompanyingPeriodsByPerson(Person $person) + { + $accompanyingPeriods = $person->getCurrentAccompanyingPeriods(); + $accompanyingPeriodsChecked = array_filter( + $accompanyingPeriods, + function (AccompanyingPeriod $period) { + return $this->isGranted(AccompanyingPeriodVoter::SEE, $period); + } + ); + + return $this->json(array_values($accompanyingPeriodsChecked), Response::HTTP_OK, [], ['groups' => ['read']]); + } + public function participationApi($id, Request $request, $_format) { /** @var AccompanyingPeriod $accompanyingPeriod */ @@ -76,7 +121,7 @@ $workflow = $this->registry->get($accompanyingPeriod); $person = $this->getSerializer() ->deserialize($request->getContent(), Person::class, $_format, []); - if (NULL === $person) { + if (null === $person) { throw new BadRequestException('person id not found'); } @@ -87,12 +132,16 @@ $workflow = $this->registry->get($accompanyingPeriod); switch ($request->getMethod()) { case Request::METHOD_POST: $participation = $accompanyingPeriod->createParticipationFor($person); + break; + case Request::METHOD_DELETE: $participation = $accompanyingPeriod->closeParticipationFor($person); + break; + default: - throw new BadRequestException("This method is not supported"); + throw new BadRequestException('This method is not supported'); } $errors = $this->validator->validate($accompanyingPeriod); @@ -104,41 +153,7 @@ $workflow = $this->registry->get($accompanyingPeriod); $this->getDoctrine()->getManager()->flush(); - return $this->json($participation, 200, [], ['groups' => [ 'read' ]]); - } - - public function resourceApi($id, Request $request, string $_format): Response - { - return $this->addRemoveSomething('resource', $id, $request, $_format, 'resource', Resource::class); - } - - public function scopeApi($id, Request $request, string $_format): Response - { - return $this->addRemoveSomething('scope', $id, $request, $_format, 'scope', Scope::class, [ 'groups' => [ 'read' ] ]); - } - - public function commentApi($id, Request $request, string $_format): Response - { - return $this->addRemoveSomething('comment', $id, $request, $_format, 'comment', Comment::class); - } - - public function socialIssueApi($id, Request $request, string $_format): Response - { - return $this->addRemoveSomething('socialissue', $id, $request, $_format, 'socialIssue', SocialIssue::class, [ 'groups' => [ 'read' ] ]); - } - - public function workApi($id, Request $request, string $_format): Response - { - return $this->addRemoveSomething( - 'work', - $id, - $request, - $_format, - 'work', - AccompanyingPeriodWork::class, - [ 'groups' => [ 'accompanying_period_work:create' ] ], - true // force persist - ); + return $this->json($participation, 200, [], ['groups' => ['read']]); } public function requestorApi($id, Request $request, string $_format): Response @@ -152,10 +167,11 @@ $workflow = $this->registry->get($accompanyingPeriod); $this->onPostCheckACL($action, $request, $_format, $accompanyingPeriod); if (Request::METHOD_DELETE === $request->getMethod()) { - $accompanyingPeriod->setRequestor(NULL); + $accompanyingPeriod->setRequestor(null); } elseif (Request::METHOD_POST === $request->getMethod()) { $requestor = null; $exceptions = []; + foreach ([Person::class, ThirdParty::class] as $class) { try { $requestor = $this->getSerializer() @@ -164,7 +180,8 @@ $workflow = $this->registry->get($accompanyingPeriod); $exceptions[] = $e; } } - if ($requestor === null) { + + if (null === $requestor) { throw new BadRequestException('Could not find any person or requestor', 0, $exceptions[0]); } @@ -182,28 +199,35 @@ $workflow = $this->registry->get($accompanyingPeriod); $this->getDoctrine()->getManager()->flush(); - return $this->json($accompanyingPeriod->getRequestor(), 200, [], ['groups' => [ 'read']]); + return $this->json($accompanyingPeriod->getRequestor(), 200, [], ['groups' => ['read']]); } - protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response + public function resourceApi($id, Request $request, string $_format): Response { - $this->eventDispatcher->dispatch( - AccompanyingPeriodPrivacyEvent::ACCOMPANYING_PERIOD_PRIVACY_EVENT, - new AccompanyingPeriodPrivacyEvent($entity, [ - 'action' => $action, - 'request' => $request->getMethod() - ]) - ); + $accompanyingPeriod = $this->getEntity('resource', $id, $request); + $errors = $this->validator->validate($accompanyingPeriod); - return null; + if ($errors->count() > 0) { + return $this->json($errors, 422); + } + + return $this->addRemoveSomething('resource', $id, $request, $_format, 'resource', Resource::class); + } + + public function scopeApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('scope', $id, $request, $_format, 'scope', Scope::class, ['groups' => ['read']]); + } + + public function socialIssueApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('socialissue', $id, $request, $_format, 'socialIssue', SocialIssue::class, ['groups' => ['read']]); } /** * @Route("/api/1.0/person/accompanying-course/{id}/referrers-suggested.{_format}", - * requirements={ "_format"="json"}, - * name="chill_api_person_accompanying_period_referrers_suggested") - * @param AccompanyingPeriod $period - * @return JsonResponse + * requirements={ "_format": "json"}, + * name="chill_api_person_accompanying_period_referrers_suggested") */ public function suggestReferrals(AccompanyingPeriod $period, string $_format = 'json'): JsonResponse { @@ -213,13 +237,76 @@ $workflow = $this->registry->get($accompanyingPeriod); $paginator = $this->getPaginatorFactory()->create($total); if (0 < $total) { - $users = $this->referralAvailable->findReferralSuggested($period, $paginator->getItemsPerPage(), - $paginator->getCurrentPageFirstItemNumber()); + $users = $this->referralAvailable->findReferralSuggested( + $period, + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); } else { $users = []; } - return $this->json(new Collection($users, $paginator), Response::HTTP_OK, - [], [ AbstractNormalizer::GROUPS => [ 'read' ]]); + return $this->json( + new Collection($users, $paginator), + Response::HTTP_OK, + [], + [AbstractNormalizer::GROUPS => ['read']] + ); + } + + /** + * @Route("/api/1.0/person/accompanying-course/{id}/confidential.json", name="chill_api_person_accompanying_period_confidential") + * @ParamConverter("accompanyingCourse", options={"id": "id"}) + */ + public function toggleConfidentialApi(AccompanyingPeriod $accompanyingCourse, Request $request) + { + if ($request->getMethod() === 'POST') { + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL, $accompanyingCourse); + + $accompanyingCourse->setConfidential(!$accompanyingCourse->isConfidential()); + $this->getDoctrine()->getManager()->flush(); + } + + return $this->json($accompanyingCourse->isConfidential(), Response::HTTP_OK, [], ['groups' => ['read']]); + } + + public function workApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething( + 'work', + $id, + $request, + $_format, + 'work', + AccompanyingPeriodWork::class, + ['groups' => ['accompanying_period_work:create']], + true // force persist + ); + } + + protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response + { + $this->eventDispatcher->dispatch( + AccompanyingPeriodPrivacyEvent::ACCOMPANYING_PERIOD_PRIVACY_EVENT, + new AccompanyingPeriodPrivacyEvent($entity, [ + 'action' => $action, + 'request' => $request->getMethod(), + ]) + ); + + return null; + } + + protected function validate(string $action, Request $request, string $_format, $entity, array $more = []): ConstraintViolationListInterface + { + if ('work' !== $action) { + return parent::validate($action, $request, $_format, $entity, $more); + } + + if (Request::METHOD_POST === $request->getMethod()) { + return $this->getValidator()->validate($more[0], null); + } + + return new ConstraintViolationList([]); } } diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php index 5c11f4802..7d0e841cf 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php @@ -1,91 +1,150 @@ serializer = $serializer; $this->dispatcher = $dispatcher; $this->validator = $validator; $this->workRepository = $workRepository; + $this->registry = $registry; + $this->translator = $translator; } /** - * @Route("/{_locale}/person/parcours/new", name="chill_person_accompanying_course_new") + * @Route("/{_locale}/parcours/{accompanying_period_id}/close", name="chill_person_accompanying_course_close") + * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) */ - public function newAction(Request $request): Response + public function closeAction(AccompanyingPeriod $accompanyingCourse, Request $request): Response { - $period = new AccompanyingPeriod(); - $em = $this->getDoctrine()->getManager(); + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::EDIT, $accompanyingCourse); - if ($request->query->has('person_id')) { - $personIds = $request->query->get('person_id'); + $form = $this->createForm(AccompanyingCourseType::class, $accompanyingCourse, [ + 'validation_groups' => [AccompanyingPeriod::STEP_CLOSED], + ]); - if (FALSE === \is_array($personIds)) { - throw new BadRequestException("person_id parameter should be an array"); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + + $workflow = $this->registry->get($accompanyingCourse); + + if ($workflow->can($accompanyingCourse, 'close')) { + $workflow->apply($accompanyingCourse, 'close'); + + $em->flush(); + + return $this->redirectToRoute('chill_person_accompanying_course_index', [ + 'accompanying_period_id' => $accompanyingCourse->getId(), + ]); } + /** @var ConstraintViolationListInterface $errors */ + $errors = $this->validator->validate($accompanyingCourse, null, [$accompanyingCourse::STEP_CLOSED]); + $this->addFlash('error', $this->translator->trans('It is not possible to close this course')); - foreach ($personIds as $personId) { - $person = $em->getRepository(Person::class)->find($personId); - if (NULL !== $person) { - $period->addPerson($person); - } + foreach ($errors as $e) { + /** @var ConstraintViolationInterface $e */ + $this->addFlash('error', $e->getMessage()); } } - $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::CREATE, $period); - - $em->persist($period); - $em->flush(); - - return $this->redirectToRoute('chill_person_accompanying_course_edit', [ - 'accompanying_period_id' => $period->getId() + return $this->render('@ChillPerson/AccompanyingCourse/close.html.twig', [ + 'form' => $form->createView(), + 'accompanyingCourse' => $accompanyingCourse, ]); - } /** - * Homepage of Accompanying Course section + * Edit page of Accompanying Course section. + * + * the page edit all blocks managed by vuejs component + * + * @Route("/{_locale}/parcours/{accompanying_period_id}/edit", name="chill_person_accompanying_course_edit") + * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) + */ + public function editAction(AccompanyingPeriod $accompanyingCourse): Response + { + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse); + + return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [ + 'accompanyingCourse' => $accompanyingCourse, + ]); + } + + /** + * History page of Accompanying Course section. + * + * the page show anti chronologic history with all actions, title of page is 'Accompanying Course History' + * + * @Route("/{_locale}/parcours/{accompanying_period_id}/history", name="chill_person_accompanying_course_history") + * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) + */ + public function historyAction(AccompanyingPeriod $accompanyingCourse): Response + { + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse); + + return $this->render('@ChillPerson/AccompanyingCourse/history.html.twig', [ + 'accompanyingCourse' => $accompanyingCourse, + ]); + } + + /** + * Homepage of Accompanying Course section. * * @Route("/{_locale}/parcours/{accompanying_period_id}", name="chill_person_accompanying_course_index") * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) @@ -97,9 +156,9 @@ class AccompanyingCourseController extends Controller // compute some warnings // get persons without household $withoutHousehold = []; - foreach ($accompanyingCourse->getParticipations() as - $p) { - if (FALSE === $p->getPerson()->isSharingHousehold()) { + + foreach ($accompanyingCourse->getParticipations() as $p) { + if (false === $p->getPerson()->isSharingHousehold()) { $withoutHousehold[] = $p->getPerson(); } } @@ -120,42 +179,41 @@ class AccompanyingCourseController extends Controller 'withoutHousehold' => $withoutHousehold, 'participationsByHousehold' => $accompanyingCourse->actualParticipationsByHousehold(), 'works' => $works, - 'activities' => $activities + 'activities' => $activities, ]); } /** - * Edit page of Accompanying Course section - * - * the page edit all blocks managed by vuejs component - * - * @Route("/{_locale}/parcours/{accompanying_period_id}/edit", name="chill_person_accompanying_course_edit") - * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) + * @Route("/{_locale}/person/parcours/new", name="chill_person_accompanying_course_new") */ - public function editAction(AccompanyingPeriod $accompanyingCourse): Response + public function newAction(Request $request): Response { - $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse); + $period = new AccompanyingPeriod(); + $em = $this->getDoctrine()->getManager(); - return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [ - 'accompanyingCourse' => $accompanyingCourse + if ($request->query->has('person_id')) { + $personIds = $request->query->get('person_id'); + + if (false === is_array($personIds)) { + throw new BadRequestException('person_id parameter should be an array'); + } + + foreach ($personIds as $personId) { + $person = $em->getRepository(Person::class)->find($personId); + + if (null !== $person) { + $period->addPerson($person); + } + } + } + + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::CREATE, $period); + + $em->persist($period); + $em->flush(); + + return $this->redirectToRoute('chill_person_accompanying_course_edit', [ + 'accompanying_period_id' => $period->getId(), ]); } - - /** - * History page of Accompanying Course section - * - * the page show anti chronologic history with all actions, title of page is 'Accompanying Course History' - * - * @Route("/{_locale}/parcours/{accompanying_period_id}/history", name="chill_person_accompanying_course_history") - * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) - */ - public function historyAction(AccompanyingPeriod $accompanyingCourse): Response - { - $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse); - - return $this->render('@ChillPerson/AccompanyingCourse/history.html.twig', [ - 'accompanyingCourse' => $accompanyingCourse - ]); - } - } diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkApiController.php index 6263736b0..95cbaf023 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkApiController.php @@ -1,5 +1,14 @@ getMethod()) { case Request::METHOD_PUT: - return [ 'groups' => [ 'accompanying_period_work:edit' ] ]; + return ['groups' => ['accompanying_period_work:edit']]; } } return parent::getContextForSerialization($action, $request, $_format, $entity); } - } diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php index 739b14e2b..1da7424bf 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php @@ -1,91 +1,159 @@ trans = $trans; $this->serializer = $serializer; $this->workRepository = $workRepository; $this->paginator = $paginator; + $this->chillLogger = $chillLogger; } /** * @Route( - * "{_locale}/person/accompanying-period/{id}/work/new", - * name="chill_person_accompanying_period_work_new", - * methods={"GET"} - * ) + * "{_locale}/person/accompanying-period/{id}/work/new", + * name="chill_person_accompanying_period_work_new", + * methods={"GET"} + * ) */ public function createWork(AccompanyingPeriod $period): Response { // TODO ACL if ($period->getSocialIssues()->count() === 0) { - $this->addFlash('error', $this->trans->trans( - "accompanying_work.You must add at least ". - "one social issue on accompanying period") + $this->addFlash( + 'error', + $this->trans->trans( + 'accompanying_work.You must add at least ' . + 'one social issue on accompanying period' + ) ); return $this->redirectToRoute('chill_person_accompanying_course_index', [ - 'accompanying_period_id' => $period->getId() + 'accompanying_period_id' => $period->getId(), ]); } - $json = $this->serializer->normalize($period, 'json', [ "groups" => [ "read" ]]); + $json = $this->serializer->normalize($period, 'json', ['groups' => ['read']]); return $this->render('@ChillPerson/AccompanyingCourseWork/create.html.twig', [ 'accompanyingCourse' => $period, - 'json' => $json + 'json' => $json, ]); } /** * @Route( - * "{_locale}/person/accompanying-period/work/{id}/edit", - * name="chill_person_accompanying_period_work_edit", - * methods={"GET"} - * ) + * "{_locale}/person/accompanying-period/work/{id}/delete", + * name="chill_person_accompanying_period_work_delete", + * methods={"GET", "POST", "DELETE"} + * ) + */ + public function deleteWork(AccompanyingPeriodWork $work, Request $request): Response + { + // TODO ACL + $em = $this->getDoctrine()->getManager(); + + $form = $this->createDeleteForm($work->getId()); + + if ($request->getMethod() === Request::METHOD_DELETE) { + $form->handleRequest($request); + + if ($form->isValid()) { + $this->chillLogger->notice('An accompanying period work has been removed', [ + 'by_user' => $this->getUser()->getUsername(), + 'work_id' => $work->getId(), + 'accompanying_period_id' => $work->getAccompanyingPeriod()->getId(), + ]); + + $em->remove($work); + $em->flush(); + + $this->addFlash( + 'success', + $this->trans->trans('The accompanying period work has been successfully removed.') + ); + + return $this->redirectToRoute('chill_person_accompanying_period_work_list', [ + 'id' => $work->getAccompanyingPeriod()->getId(), + ]); + } + } + + return $this->render('@ChillPerson/AccompanyingCourseWork/delete.html.twig', [ + 'accompanyingCourse' => $work->getAccompanyingPeriod(), + 'work' => $work, + 'delete_form' => $form->createView(), + ]); + } + + /** + * @Route( + * "{_locale}/person/accompanying-period/work/{id}/edit", + * name="chill_person_accompanying_period_work_edit", + * methods={"GET"} + * ) */ public function editWork(AccompanyingPeriodWork $work): Response { // TODO ACL - $json = $this->serializer->normalize($work, 'json', [ "groups" => [ "read" ] ]); + $json = $this->serializer->normalize($work, 'json', ['groups' => ['read']]); + return $this->render('@ChillPerson/AccompanyingCourseWork/edit.html.twig', [ 'accompanyingCourse' => $work->getAccompanyingPeriod(), 'work' => $work, - 'json' => $json + 'json' => $json, ]); } /** * @Route( - * "{_locale}/person/accompanying-period/{id}/work", - * name="chill_person_accompanying_period_work_list", - * methods={"GET"} - * ) + * "{_locale}/person/accompanying-period/{id}/work", + * name="chill_person_accompanying_period_work_list", + * methods={"GET"} + * ) */ public function listWorkByAccompanyingPeriod(AccompanyingPeriod $period): Response { @@ -103,7 +171,19 @@ class AccompanyingCourseWorkController extends AbstractController return $this->render('@ChillPerson/AccompanyingCourseWork/index.html.twig', [ 'accompanyingCourse' => $period, 'works' => $works, - 'paginator' => $paginator + 'paginator' => $paginator, ]); } + + private function createDeleteForm(int $id): Form + { + $params = []; + $params['id'] = $id; + + return $this->createFormBuilder() + ->setAction($this->generateUrl('chill_person_accompanying_period_work_delete', $params)) + ->setMethod('DELETE') + ->add('submit', SubmitType::class, ['label' => 'Delete']) + ->getForm(); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php index 255c6d9ba..bfc85a5b3 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php @@ -1,51 +1,42 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller; +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Form\AccompanyingPeriodType; use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use DateTime; use Doctrine\DBAL\Exception; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Form\AccompanyingPeriodType; -use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Doctrine\Common\Collections\Criteria; -use Chill\PersonBundle\Security\Authorization\PersonVoter; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; +use function array_filter; +use function count; /** - * Class AccompanyingPeriodController - * - * @package Chill\PersonBundle\Controller + * Class AccompanyingPeriodController. */ class AccompanyingPeriodController extends AbstractController { + protected AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository; + /** * @var EventDispatcherInterface */ @@ -56,8 +47,6 @@ class AccompanyingPeriodController extends AbstractController */ protected $validator; - protected AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository; - public function __construct( AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository, EventDispatcherInterface $eventDispatcher, @@ -68,218 +57,71 @@ class AccompanyingPeriodController extends AbstractController $this->validator = $validator; } - /** - * @ParamConverter("person", options={"id"="person_id"}) - */ - public function listAction(Person $person): Response - { - $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $person); - - $event = new PrivacyEvent($person, [ - 'element_class' => AccompanyingPeriod::class, - 'action' => 'list' - ]); - $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); - - $accompanyingPeriods = $this->accompanyingPeriodACLAwareRepository - ->findByPerson($person, AccompanyingPeriodVoter::SEE); - - return $this->render('@ChillPerson/AccompanyingPeriod/list.html.twig', [ - 'accompanying_periods' => $accompanyingPeriods, - 'person' => $person - ]); - } - - public function createAction(int $person_id, Request $request): Response - { - $person = $this->_getPerson($person_id); - - $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, - 'You are not allowed to update this person'); - - $accompanyingPeriod = new AccompanyingPeriod(new \DateTime('now')); - $accompanyingPeriod->setClosingDate(new \DateTime('now')); - - $accompanyingPeriod->addPerson($person); - //or $person->addAccompanyingPeriod($accompanyingPeriod); - - $form = $this->createForm( - AccompanyingPeriodType::class, - $accompanyingPeriod, [ - 'period_action' => 'create', - 'center' => $person->getCenter() - ]); - - if ($request->getMethod() === 'POST') { - $form->handleRequest($request); - $errors = $this->_validatePerson($person); - $flashBag = $this->get('session')->getFlashBag(); - - if ($form->isValid(['Default', 'closed']) - && count($errors) === 0) { - - $em = $this->getDoctrine()->getManager(); - $em->persist($accompanyingPeriod); - $em->flush(); - $flashBag->add('success', - $this->get('translator')->trans( - 'A period has been created.')); - - return $this->redirect( - $this->generateUrl('chill_person_accompanying_period_list', [ - 'person_id' => $person->getId() - ])); - - } else { - $flashBag->add('error', $this->get('translator') - ->trans('Error! Period not created!')); - - foreach($errors as $error) { - $flashBag->add('info', $error->getMessage()); - } - } - } - - return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [ - 'form' => $form->createView(), - 'person' => $person, - 'accompanying_period' => $accompanyingPeriod - ]); - } - - /** - * @throws Exception - */ - public function updateAction(int $person_id, int $period_id, Request $request): Response - { - $em = $this->getDoctrine()->getManager(); - - /** @var AccompanyingPeriod $accompanyingPeriod */ - $accompanyingPeriod = $em->getRepository(AccompanyingPeriod::class)->find($period_id); - - if ($accompanyingPeriod === null) { - throw $this->createNotFoundException("Period with id " . $period_id . " is not found"); - } - - /** @var Person $person */ - $person = $this->_getPerson($person_id); - - // CHECK - if (! $accompanyingPeriod->containsPerson($person)) { - throw new Exception("Accompanying period " . $period_id . " does not contain person " . $person_id); - } - - $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, - 'You are not allowed to update this person'); - - $form = $this->createForm(AccompanyingPeriodType::class, - $accompanyingPeriod, [ - 'period_action' => 'update', - 'center' => $person->getCenter() - ]); - - if ($request->getMethod() === 'POST') { - $form->handleRequest($request); - $errors = $this->_validatePerson($person); - $flashBag = $this->get('session')->getFlashBag(); - - if ($form->isValid(['Default', 'closed']) - && count($errors) === 0) { - - $em->flush(); - - $flashBag->add('success', - $this->get('translator')->trans('An accompanying period has been updated.')); - - return $this->redirect( - $this->generateUrl('chill_person_accompanying_period_list', [ - 'person_id' => $person->getId() - ])); - - } else { - - $flashBag->add('error', $this->get('translator') - ->trans('Error when updating the period')); - - foreach($errors as $error) { - $flashBag->add('info', $error->getMessage()); - } - } - } - - return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [ - 'form' => $form->createView(), - 'person' => $person, - 'accompanying_period' => $accompanyingPeriod - ]); - } - /** * @throws \Exception */ public function closeAction(int $person_id, Request $request): Response { - $person = $this->_getPerson($person_id); $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, 'You are not allowed to update this person'); if ($person->isOpen() === false) { - $this->get('session')->getFlashBag() ->add('error', $this->get('translator') - ->trans('Beware period is closed', ['%name%' => $person->__toString()] - )); + ->trans( + 'Beware period is closed', + ['%name%' => $person->__toString()] + )); return $this->redirect( $this->generateUrl('chill_person_accompanying_period_list', [ - 'person_id' => $person->getId() - ])); + 'person_id' => $person->getId(), + ]) + ); } $current = $person->getCurrentAccompanyingPeriod(); $form = $this->createForm(AccompanyingPeriodType::class, $current, [ 'period_action' => 'close', - 'center' => $person->getCenter() + 'center' => $person->getCenter(), ]); if ($request->getMethod() === 'POST') { $form->handleRequest($request); - if ($form->isValid()){ + if ($form->isValid()) { $person->close($current); $errors = $this->_validatePerson($person); if (count($errors) === 0) { $this->get('session')->getFlashBag() ->add('success', $this->get('translator') - ->trans('An accompanying period has been closed.', [ - '%name%' => $person->__toString() - ])); + ->trans('An accompanying period has been closed.', [ + '%name%' => $person->__toString(), + ])); $this->getDoctrine()->getManager()->flush(); return $this->redirect( $this->generateUrl('chill_person_accompanying_period_list', [ - 'person_id' => $person->getId() + 'person_id' => $person->getId(), ]) ); - - } else { - $this->get('session')->getFlashBag() - ->add('error', $this->get('translator') - ->trans('Error! Period not closed!')); - - foreach ($errors as $error) { - $this->get('session')->getFlashBag() - ->add('info', $error->getMessage()); - } } + $this->get('session')->getFlashBag() + ->add('error', $this->get('translator') + ->trans('Error! Period not closed!')); + foreach ($errors as $error) { + $this->get('session')->getFlashBag() + ->add('info', $error->getMessage()); + } } else { //if form is not valid $this->get('session')->getFlashBag() - ->add('error', + ->add( + 'error', $this->get('translator') ->trans('Pediod closing form is not valid') ); @@ -294,57 +136,136 @@ class AccompanyingPeriodController extends AbstractController return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [ 'form' => $form->createView(), 'person' => $person, - 'accompanying_period' => $current + 'accompanying_period' => $current, ]); } - private function _validatePerson(Person $person): ConstraintViolationListInterface + public function createAction(int $person_id, Request $request): Response { - $errors = $this->validator->validate($person, null, - ['Default']); + $person = $this->_getPerson($person_id); - // Can be disabled with config - if (false === $this->container->getParameter('chill_person.allow_multiple_simultaneous_accompanying_periods')) { + $this->denyAccessUnlessGranted( + PersonVoter::UPDATE, + $person, + 'You are not allowed to update this person' + ); - $errors_accompanying_period = $this->validator->validate($person, null, - ['accompanying_period_consistent']); + $accompanyingPeriod = new AccompanyingPeriod(new DateTime('now')); + $accompanyingPeriod->setClosingDate(new DateTime('now')); - foreach($errors_accompanying_period as $error ) { - $errors->add($error); + $accompanyingPeriod->addPerson($person); + //or $person->addAccompanyingPeriod($accompanyingPeriod); + + $form = $this->createForm( + AccompanyingPeriodType::class, + $accompanyingPeriod, + [ + 'period_action' => 'create', + 'center' => $person->getCenter(), + ] + ); + + if ($request->getMethod() === 'POST') { + $form->handleRequest($request); + $errors = $this->_validatePerson($person); + $flashBag = $this->get('session')->getFlashBag(); + + if ($form->isValid(['Default', 'closed']) + && count($errors) === 0) { + $em = $this->getDoctrine()->getManager(); + $em->persist($accompanyingPeriod); + $em->flush(); + $flashBag->add( + 'success', + $this->get('translator')->trans( + 'A period has been created.' + ) + ); + + return $this->redirect( + $this->generateUrl('chill_person_accompanying_period_list', [ + 'person_id' => $person->getId(), + ]) + ); + } + $flashBag->add('error', $this->get('translator') + ->trans('Error! Period not created!')); + + foreach ($errors as $error) { + $flashBag->add('info', $error->getMessage()); } } - return $errors; + return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [ + 'form' => $form->createView(), + 'person' => $person, + 'accompanying_period' => $accompanyingPeriod, + ]); + } + + /** + * @ParamConverter("person", options={"id": "person_id"}) + */ + public function listAction(Person $person): Response + { + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $person); + + $event = new PrivacyEvent($person, [ + 'element_class' => AccompanyingPeriod::class, + 'action' => 'list', + ]); + $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); + + $accompanyingPeriodsRaw = $this->accompanyingPeriodACLAwareRepository + ->findByPerson($person, AccompanyingPeriodVoter::SEE); + + // filter visible or not visible + $accompanyingPeriods = array_filter($accompanyingPeriodsRaw, function (AccompanyingPeriod $ap) { + return $this->isGranted(AccompanyingPeriodVoter::SEE, $ap); + }); + + return $this->render('@ChillPerson/AccompanyingPeriod/list.html.twig', [ + 'accompanying_periods' => $accompanyingPeriods, + 'person' => $person, + ]); } public function openAction(int $person_id, Request $request): Response { $person = $this->_getPerson($person_id); - $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, - 'You are not allowed to update this person'); + $this->denyAccessUnlessGranted( + PersonVoter::UPDATE, + $person, + 'You are not allowed to update this person' + ); //in case the person is already open if ($person->isOpen()) { $this->get('session')->getFlashBag() ->add('error', $this->get('translator') - ->trans('Error! Period %name% is not closed ; it can be open', - ['%name%' => $person->__toString()] - )); + ->trans( + 'Error! Period %name% is not closed ; it can be open', + ['%name%' => $person->__toString()] + )); return $this->redirect( $this->generateUrl('chill_person_accompanying_period_list', [ - 'person_id' => $person->getId() - ])); + 'person_id' => $person->getId(), + ]) + ); } - $accompanyingPeriod = new AccompanyingPeriod(new \DateTime()); + $accompanyingPeriod = new AccompanyingPeriod(new DateTime()); - $form = $this->createForm(AccompanyingPeriodType::class, - $accompanyingPeriod, [ + $form = $this->createForm( + AccompanyingPeriodType::class, + $accompanyingPeriod, + [ 'period_action' => 'open', - 'center' => $person->getCenter() - ]); + 'center' => $person->getCenter(), + ] + ); if ($request->getMethod() === 'POST') { $form->handleRequest($request); @@ -357,40 +278,41 @@ class AccompanyingPeriodController extends AbstractController if (count($errors) <= 0) { $this->get('session')->getFlashBag() ->add('success', $this->get('translator') - ->trans('An accompanying period has been opened.', + ->trans( + 'An accompanying period has been opened.', ['%name%' => $person->__toString()] - )); + )); $this->getDoctrine()->getManager()->flush(); return $this->redirect( $this->generateUrl('chill_person_accompanying_period_list', [ - 'person_id' => $person->getId() - ])); - - } else { - $this->get('session')->getFlashBag() - ->add('error', $this->get('translator') - ->trans('Period not opened')); - - foreach ($errors as $error) { - $this->get('session')->getFlashBag() - ->add('info', $error->getMessage()); - } + 'person_id' => $person->getId(), + ]) + ); } + $this->get('session')->getFlashBag() + ->add('error', $this->get('translator') + ->trans('Period not opened')); + foreach ($errors as $error) { + $this->get('session')->getFlashBag() + ->add('info', $error->getMessage()); + } } else { // if errors in forms $this->get('session')->getFlashBag() - ->add('error', $this->get('translator') - ->trans('Period not opened : form is invalid') - ); + ->add( + 'error', + $this->get('translator') + ->trans('Period not opened : form is invalid') + ); } } return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [ 'form' => $form->createView(), 'person' => $person, - 'accompanying_period' => $accompanyingPeriod + 'accompanying_period' => $accompanyingPeriod, ]); } @@ -399,21 +321,21 @@ class AccompanyingPeriodController extends AbstractController /** @var Person $person */ $person = $this->_getPerson($person_id); - /* @var $period AccompanyingPeriod */ - $period = \array_filter( + /** @var AccompanyingPeriod $period */ + $period = array_filter( $person->getAccompanyingPeriods(), - function (AccompanyingPeriod $p) use ($period_id) { + static function (AccompanyingPeriod $p) use ($period_id) { return $p->getId() === ($period_id); } - )[0] ?? NULL; + )[0] ?? null; - if ($period === NULL) { + if (null === $period) { throw $this->createNotFoundException('period not found'); } $confirm = $request->query->getBoolean('confirm', false); - if ($confirm === true && $period->canBeReOpened($person)) { + if (true === $confirm && $period->canBeReOpened($person)) { $period->reOpen(); $this->_validatePerson($person); @@ -421,40 +343,141 @@ class AccompanyingPeriodController extends AbstractController $this->getDoctrine()->getManager()->flush(); $this->addFlash('success', $this->get('translator')->trans( - 'The period has been re-opened')); + 'The period has been re-opened' + )); return $this->redirectToRoute('chill_person_accompanying_period_list', [ - 'person_id' => $person->getId() + 'person_id' => $person->getId(), ]); + } - } elseif ($confirm === false && $period->canBeReOpened($person)) { + if (false === $confirm && $period->canBeReOpened($person)) { return $this->render('ChillPersonBundle:AccompanyingPeriod:re_open.html.twig', [ 'period' => $period, - 'person' => $person + 'person' => $person, ]); - - } else { - return (new Response()) - ->setStatusCode(Response::HTTP_BAD_REQUEST) - ->setContent("You cannot re-open this period"); } + + return (new Response()) + ->setStatusCode(Response::HTTP_BAD_REQUEST) + ->setContent('You cannot re-open this period'); + } + + /** + * @throws Exception + */ + public function updateAction(int $person_id, int $period_id, Request $request): Response + { + $em = $this->getDoctrine()->getManager(); + + /** @var AccompanyingPeriod $accompanyingPeriod */ + $accompanyingPeriod = $em->getRepository(AccompanyingPeriod::class)->find($period_id); + + if (null === $accompanyingPeriod) { + throw $this->createNotFoundException('Period with id ' . $period_id . ' is not found'); + } + + /** @var Person $person */ + $person = $this->_getPerson($person_id); + + // CHECK + if (!$accompanyingPeriod->containsPerson($person)) { + throw new Exception('Accompanying period ' . $period_id . ' does not contain person ' . $person_id); + } + + $this->denyAccessUnlessGranted( + PersonVoter::UPDATE, + $person, + 'You are not allowed to update this person' + ); + + $form = $this->createForm( + AccompanyingPeriodType::class, + $accompanyingPeriod, + [ + 'period_action' => 'update', + 'center' => $person->getCenter(), + ] + ); + + if ($request->getMethod() === 'POST') { + $form->handleRequest($request); + $errors = $this->_validatePerson($person); + $flashBag = $this->get('session')->getFlashBag(); + + if ($form->isValid(['Default', 'closed']) + && count($errors) === 0) { + $em->flush(); + + $flashBag->add( + 'success', + $this->get('translator')->trans('An accompanying period has been updated.') + ); + + return $this->redirect( + $this->generateUrl('chill_person_accompanying_period_list', [ + 'person_id' => $person->getId(), + ]) + ); + } + + $flashBag->add('error', $this->get('translator') + ->trans('Error when updating the period')); + + foreach ($errors as $error) { + $flashBag->add('info', $error->getMessage()); + } + } + + return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [ + 'form' => $form->createView(), + 'person' => $person, + 'accompanying_period' => $accompanyingPeriod, + ]); } /** * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ - private function _getPerson(int $id) : Person + private function _getPerson(int $id): Person { $person = $this->getDoctrine()->getManager() ->getRepository('ChillPersonBundle:Person')->find($id); - if ($person === null) { + if (null === $person) { throw $this->createNotFoundException('Person not found'); } - $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, - "You are not allowed to see this person"); + $this->denyAccessUnlessGranted( + PersonVoter::SEE, + $person, + 'You are not allowed to see this person' + ); return $person; } + + private function _validatePerson(Person $person): ConstraintViolationListInterface + { + $errors = $this->validator->validate( + $person, + null, + ['Default'] + ); + + // Can be disabled with config + if (false === $this->container->getParameter('chill_person.allow_multiple_simultaneous_accompanying_periods')) { + $errors_accompanying_period = $this->validator->validate( + $person, + null, + ['accompanying_period_consistent'] + ); + + foreach ($errors_accompanying_period as $error) { + $errors->add($error); + } + } + + return $errors; + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkEvaluationApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkEvaluationApiController.php new file mode 100644 index 000000000..ddd3445a4 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkEvaluationApiController.php @@ -0,0 +1,78 @@ +docGeneratorTemplateRepository = $docGeneratorTemplateRepository; + $this->serializer = $serializer; + $this->paginatorFactory = $paginatorFactory; + } + + /** + * @Route("/api/1.0/person/docgen/template/by-evaluation/{id}.{_format}", + * requirements={"format": "json"}) + */ + public function listTemplateByEvaluation(Evaluation $evaluation, string $_format): JsonResponse + { + if ('json' !== $_format) { + throw new BadRequestHttpException('format not supported'); + } + + $evaluations = + array_filter( + $this->docGeneratorTemplateRepository + ->findByEntity(AccompanyingPeriodWorkEvaluation::class), + static function (DocGeneratorTemplate $t) use ($evaluation) { + $ids = $t->getOptions()['evaluations'] ?? []; + + return in_array($evaluation->getId(), $ids, true); + } + ); + + $paginator = $this->paginatorFactory->create(count($evaluations)); + $paginator->setItemsPerPage(count($evaluations)); + + return new JsonResponse($this->serializer->serialize( + new Collection($evaluations, $paginator), + 'json', + [ + AbstractNormalizer::GROUPS => ['read'], + ] + ), JsonResponse::HTTP_OK, [], true); + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/AdminClosingMotiveController.php b/src/Bundle/ChillPersonBundle/Controller/AdminClosingMotiveController.php index 483619be6..9eee2c4a0 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AdminClosingMotiveController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AdminClosingMotiveController.php @@ -1,50 +1,53 @@ query->has('parent_id')) { $parentId = $request->query->getInt('parent_id'); - + $parent = $this->getDoctrine()->getManager() ->getRepository($this->getEntityClass()) ->find($parentId); - - if (NULL === $parent) { + + if (null === $parent) { throw $this->createNotFoundException('parent id not found'); } - + $entity->setParent($parent); } + return $entity; } - + /** - * @param string $action * @param \Doctrine\ORM\QueryBuilder|mixed $query - * @param Request $request - * @param PaginatorInterface $paginator + * * @return \Doctrine\ORM\QueryBuilder|mixed */ protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) diff --git a/src/Bundle/ChillPersonBundle/Controller/AdminController.php b/src/Bundle/ChillPersonBundle/Controller/AdminController.php index 751beb7a8..9d16bdc13 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AdminController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AdminController.php @@ -1,26 +1,33 @@ render('ChillPersonBundle:Admin:layout.html.twig', []); } - + /** * @return \Symfony\Component\HttpFoundation\RedirectResponse */ diff --git a/src/Bundle/ChillPersonBundle/Controller/AdminMaritalStatusController.php b/src/Bundle/ChillPersonBundle/Controller/AdminMaritalStatusController.php index da46ac5c5..d27ebe2e8 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AdminMaritalStatusController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AdminMaritalStatusController.php @@ -1,15 +1,22 @@ householdACLAwareRepository = $householdACLAwareRepository; } - public function householdAddressApi($id, Request $request, string $_format): Response - { - return $this->addRemoveSomething('address', $id, $request, $_format, 'address', Address::class, [ 'groups' => [ 'read' ] ]); - } - /** - * Find Household of people participating to the same AccompanyingPeriod + * @Route("/api/1.0/person/household/by-address-reference/{id}.json", + * name="chill_api_person_household_by_address_reference") * - * @ParamConverter("person", options={"id" = "person_id"}) + * @return \Symfony\Component\HttpFoundation\JsonResponse */ - public function suggestHouseholdByAccompanyingPeriodParticipationApi(Person $person, string $_format) + public function getHouseholdByAddressReference(AddressReference $addressReference): Response { - // TODO add acl - - $count = $this->householdRepository->countByAccompanyingPeriodParticipation($person); - $paginator = $this->getPaginatorFactory()->create($count); - - if ($count === 0) { - $households = []; - } else { - $households = $this->householdRepository->findByAccompanyingPeriodParticipation($person, - $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber()); - } + // TODO ACL + $this->denyAccessUnlessGranted('ROLE_USER'); + $total = $this->householdACLAwareRepository->countByAddressReference($addressReference); + $paginator = $this->getPaginatorFactory()->create($total); + $households = $this->householdACLAwareRepository->findByAddressReference( + $addressReference, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); $collection = new Collection($households, $paginator); - return $this->json($collection, Response::HTTP_OK, [], - [ "groups" => ["read"]]); + return $this->json($collection, Response::HTTP_OK, [], [ + AbstractNormalizer::GROUPS => ['read'], + ]); + } + + public function householdAddressApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething('address', $id, $request, $_format, 'address', Address::class, ['groups' => ['read']]); } /** * @Route("/api/1.0/person/address/suggest/by-household/{household_id}.{_format}", - * name="chill_person_address_suggest_by_household", - * requirements={ - * "_format"="json" + * name="chill_person_address_suggest_by_household", + * requirements={ + * "_format": "json" * } - * ) - * @ParamConverter("household", options={"id" = "household_id"}) + * ) + * @ParamConverter("household", options={"id": "household_id"}) */ public function suggestAddressByHousehold(Household $household, string $_format) { @@ -81,9 +92,11 @@ class HouseholdApiController extends ApiController $a = $participation->getAccompanyingPeriod()->getAddressLocation(); $addresses[$a->getId()] = $a; } + if (null !== $personLocation = $participation ->getAccompanyingPeriod()->getPersonLocation()) { $a = $personLocation->getCurrentHouseholdAddress(); + if (null !== $a) { $addresses[$a->getId()] = $a; } @@ -93,34 +106,59 @@ class HouseholdApiController extends ApiController // remove the actual address $actual = $household->getCurrentAddress(); + if (null !== $actual) { - $addresses = \array_filter($addresses, fn($a) => $a !== $actual); + $addresses = array_filter($addresses, static fn ($a) => $a !== $actual); } - return $this->json(\array_values($addresses), Response::HTTP_OK, [], - [ 'groups' => [ 'read' ] ]); + return $this->json( + array_values($addresses), + Response::HTTP_OK, + [], + ['groups' => ['read']] + ); } /** + * Find Household of people participating to the same AccompanyingPeriod. * - * @Route("/api/1.0/person/household/by-address-reference/{id}.json", - * name="chill_api_person_household_by_address_reference") - * @param AddressReference $addressReference - * @return \Symfony\Component\HttpFoundation\JsonResponse + * @ParamConverter("person", options={"id": "person_id"}) */ - public function getHouseholdByAddressReference(AddressReference $addressReference): Response + public function suggestHouseholdByAccompanyingPeriodParticipationApi(Person $person, string $_format) { - // TODO ACL - $this->denyAccessUnlessGranted('ROLE_USER'); + // TODO add acl - $total = $this->householdACLAwareRepository->countByAddressReference($addressReference); - $paginator = $this->getPaginatorFactory()->create($total); - $households = $this->householdACLAwareRepository->findByAddressReference($addressReference, - $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage()); + $count = $this->householdRepository->countByAccompanyingPeriodParticipation($person); + $paginator = $this->getPaginatorFactory()->create($count); + + $households = []; + + if (0 !== $count) { + $allHouseholds = $this->householdRepository->findByAccompanyingPeriodParticipation( + $person, + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + $currentHouseholdPerson = $person->getCurrentHousehold(); + + foreach ($allHouseholds as $h) { + if ($h !== $currentHouseholdPerson) { + $households[] = $h; + } + } + + if (null !== $currentHouseholdPerson) { + $count = $count - 1; + $paginator = $this->getPaginatorFactory()->create($count); + } + } $collection = new Collection($households, $paginator); - return $this->json($collection, Response::HTTP_OK, [], [ - AbstractNormalizer::GROUPS => ['read'] - ]); + return $this->json( + $collection, + Response::HTTP_OK, + [], + ['groups' => ['read']] + ); } } diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php index 1b9824184..6ce73d508 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php @@ -1,51 +1,256 @@ translator = $translator; $this->positionRepository = $positionRepository; + $this->serializer = $serializer; + $this->security = $security; } /** * @Route( - * "/{household_id}/summary", - * name="chill_person_household_summary", - * methods={"GET", "HEAD"} - * ) - * @ParamConverter("household", options={"id" = "household_id"}) + * "/{household_id}/accompanying-period", + * name="chill_person_household_accompanying_period", + * methods={"GET", "HEAD"} + * ) + * @ParamConverter("household", options={"id": "household_id"}) + */ + public function accompanyingPeriod(Request $request, Household $household) + { + $currentMembers = $household->getCurrentPersons(); + $accompanyingPeriods = []; + + foreach ($currentMembers as $p) { + $accompanyingPeriodsMember = $p->getCurrentAccompanyingPeriods(); + + foreach ($accompanyingPeriodsMember as $accompanyingPeriod) { + if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $accompanyingPeriod)) { + continue; + } + $accompanyingPeriods[$accompanyingPeriod->getId()] = $accompanyingPeriod; + } + } + + $oldMembers = $household->getNonCurrentMembers(); + $accompanyingPeriodsOld = []; + + foreach ($oldMembers as $m) { + $accompanyingPeriodsOldMember = $m->getPerson()->getAccompanyingPeriods(); + + foreach ($accompanyingPeriodsOldMember as $accompanyingPeriod) { + if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $accompanyingPeriod)) { + continue; + } + $id = $accompanyingPeriod->getId(); + + if (!array_key_exists($id, $accompanyingPeriodsOld) && !array_key_exists($id, $accompanyingPeriods)) { + $accompanyingPeriodsOld[$id] = $accompanyingPeriod; + } + } + } + + return $this->render( + '@ChillPerson/Household/accompanying_period.html.twig', + [ + 'household' => $household, + 'accompanying_periods' => $accompanyingPeriods, + 'accompanying_periods_old' => $accompanyingPeriodsOld, + ] + ); + } + + /** + * @Route( + * "/{household_id}/address/edit", + * name="chill_person_household_address_edit", + * methods={"GET", "HEAD", "POST"} + * ) + * @ParamConverter("household", options={"id": "household_id"}) + */ + public function addressEdit(Request $request, Household $household) + { + // TODO ACL + + $address_id = $request->query->get('address_id'); + $address = $this->getDoctrine()->getManager() + ->getRepository(Address::class) + ->find($address_id); + + return $this->render( + '@ChillPerson/Household/address_edit.html.twig', + [ + 'household' => $household, + 'address' => $address, + ] + ); + } + + /** + * @Route( + * "/{household_id}/addresses", + * name="chill_person_household_addresses", + * methods={"GET", "HEAD"} + * ) + * @ParamConverter("household", options={"id": "household_id"}) + */ + public function addresses(Request $request, Household $household) + { + // TODO ACL + + //TODO put these lines into a validator constraint on household->getAddress + $addresses = $household->getAddresses(); + $cond = true; + + for ($i = 0; count($addresses) - 1 > $i; ++$i) { + if ($addresses[$i]->getValidFrom() !== $addresses[$i + 1]->getValidTo()) { + $cond = false; + } + } + + return $this->render( + '@ChillPerson/Household/addresses.html.twig', + [ + 'household' => $household, + ] + ); + } + + /** + * @Route( + * "/{household_id}/address/move", + * name="chill_person_household_address_move", + * methods={"GET", "HEAD", "POST"} + * ) + * @ParamConverter("household", options={"id": "household_id"}) + */ + public function addressMove(Request $request, Household $household) + { + // TODO ACL + + return $this->render( + '@ChillPerson/Household/address_move.html.twig', + [ + 'household' => $household, + ] + ); + } + + /** + * @Route( + * "/{household_id}/members/metadata/edit", + * name="chill_person_household_members_metadata_edit", + * methods={"GET", "POST"} + * ) + * @ParamConverter("household", options={"id": "household_id"}) + */ + public function editHouseholdMetadata(Request $request, Household $household) + { + // TODO ACL + $form = $this->createMetadataForm($household); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->getDoctrine()->getManager()->flush(); + + $this->addFlash('success', $this->translator->trans('household.data_saved')); + + return $this->redirectToRoute('chill_person_household_summary', [ + 'household_id' => $household->getId(), + ]); + } + + return $this->render('@ChillPerson/Household/edit_member_metadata.html.twig', [ + 'household' => $household, + 'form' => $form->createView(), + ]); + } + + /** + * @Route( + * "/{household_id}/relationship", + * name="chill_person_household_relationship", + * methods={"GET", "HEAD"} + * ) + * @ParamConverter("household", options={"id": "household_id"}) + */ + public function showRelationship(Request $request, Household $household) + { + $jsonString = $this->serializer->serialize( + $household->getCurrentPersons(), + 'json', + [AbstractNormalizer::GROUPS => ['read']] + ); + + return $this->render( + '@ChillPerson/Household/relationship.html.twig', + [ + 'household' => $household, + 'persons' => $jsonString, + ] + ); + } + + /** + * @Route( + * "/{household_id}/summary", + * name="chill_person_household_summary", + * methods={"GET", "HEAD"} + * ) + * @ParamConverter("household", options={"id": "household_id"}) */ public function summary(Request $request, Household $household) { // TODO ACL $positions = $this->positionRepository - ->findByActiveOrdered() - ; + ->findByActiveOrdered(); // little performance improvement: // initialize members collection, which will avoid @@ -58,179 +263,29 @@ class HouseholdController extends AbstractController $form = null; } - return $this->render('@ChillPerson/Household/summary.html.twig', + return $this->render( + '@ChillPerson/Household/summary.html.twig', [ 'household' => $household, 'positions' => $positions, - 'form' => NULL !== $form ? $form->createView() : null, + 'form' => null !== $form ? $form->createView() : null, ] ); } - /** - * @Route( - * "/{household_id}/accompanying-period", - * name="chill_person_household_accompanying_period", - * methods={"GET", "HEAD"} - * ) - * @ParamConverter("household", options={"id" = "household_id"}) - */ - public function accompanyingPeriod(Request $request, Household $household) - { - // TODO ACL - - $members = $household->getMembers(); - foreach($members as $m) { - $accompanyingPeriods = $m->getPerson()->getAccompanyingPeriods(); - } - - return $this->render('@ChillPerson/Household/accompanying_period.html.twig', - [ - 'household' => $household, - 'accompanying_periods' => $accompanyingPeriods - ] - ); - } - - /** - * @Route( - * "/{household_id}/addresses", - * name="chill_person_household_addresses", - * methods={"GET", "HEAD"} - * ) - * @ParamConverter("household", options={"id" = "household_id"}) - */ - public function addresses(Request $request, Household $household) - { - // TODO ACL - - //TODO put these lines into a validator constraint on household->getAddress - $addresses = $household->getAddresses(); - $cond = True; - for ($i=0; $i < count($addresses) - 1; $i++) { - if ($addresses[$i]->getValidFrom() != $addresses[$i + 1]->getValidTo()) { - $cond = False; - } - } - - return $this->render('@ChillPerson/Household/addresses.html.twig', - [ - 'household' => $household - ] - ); - } - - - /** - * @Route( - * "/{household_id}/address/move", - * name="chill_person_household_address_move", - * methods={"GET", "HEAD", "POST"} - * ) - * @ParamConverter("household", options={"id" = "household_id"}) - */ - public function addressMove(Request $request, Household $household) - { - // TODO ACL - - return $this->render('@ChillPerson/Household/address_move.html.twig', - [ - 'household' => $household - ] - ); - } - - /** - * @Route( - * "/{household_id}/address/edit", - * name="chill_person_household_address_edit", - * methods={"GET", "HEAD", "POST"} - * ) - * @ParamConverter("household", options={"id" = "household_id"}) - */ - public function addressEdit(Request $request, Household $household) - { - - // TODO ACL - - $address_id = $request->query->get('address_id'); - $address = $this->getDoctrine()->getManager() - ->getRepository(Address::class) - ->find($address_id) - ; - - return $this->render('@ChillPerson/Household/address_edit.html.twig', - [ - 'household' => $household, - 'address' => $address, - ] - ); - } - - /** - * @Route( - * "/{household_id}/relationship", - * name="chill_person_household_relationship", - * methods={"GET", "HEAD"} - * ) - * @ParamConverter("household", options={"id" = "household_id"}) - */ - public function showRelationship(Request $request, Household $household) - { - return $this->render('@ChillPerson/Household/relationship.html.twig', - [ - 'household' => $household - ] - ); - } - - /** - * @Route( - * "/{household_id}/members/metadata/edit", - * name="chill_person_household_members_metadata_edit", - * methods={"GET", "POST"} - * ) - * @ParamConverter("household", options={"id" = "household_id"}) - */ - public function editHouseholdMetadata(Request $request, Household $household) - { - // TODO ACL - $form = $this->createMetadataForm($household); - - $form->handleRequest($request); - - if ($form->isSubmitted() and $form->isValid()) { - $this->getDoctrine()->getManager()->flush(); - - $this->addFlash('success', $this->translator->trans('household.data_saved')); - - return $this->redirectToRoute('chill_person_household_summary', [ - 'household_id' => $household->getId() - ]); - } - - return $this->render('@ChillPerson/Household/edit_member_metadata.html.twig', [ - 'household' => $household, - 'form' => $form->createView() - ]); - } - private function createMetadataForm(Household $household): FormInterface { - $form = $this->createForm( + return $this->createForm( HouseholdType::class, $household, [ 'action' => $this->generateUrl( 'chill_person_household_members_metadata_edit', [ - 'household_id' => $household->getId() + 'household_id' => $household->getId(), ] - ) + ), ] ); - - return $form; } - } diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php index 2cfd12842..012d0fd68 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php @@ -1,56 +1,191 @@ generator = $generator; $this->translator = $translator; $this->periodRepository = $periodRepository; } - + /** * @Route( - * "/api/1.0/person/household/members/move.{_format}", - * name="chill_api_person_household_members_move" - * ) + * "/{_locale}/person/household/member/{id}/edit", + * name="chill_person_household_member_edit" + * ) + */ + public function editMembership(Request $request, HouseholdMember $member): Response + { + // TODO ACL + + $form = $this->createForm(HouseholdMemberType::class, $member, [ + 'validation_groups' => ['household_memberships'], + ]); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->getDoctrine()->getManager()->flush(); + + $this->addFlash('success', $this->translator + ->trans('household.successfully saved member')); + + return $this->redirect( + $request->get('returnPath', null) ?? + $this->generator->generate('chill_person_household_summary', ['household_id' => $member->getHousehold()->getId()]) + ); + } + + return $this->render('@ChillPerson/Household/Member/edit.html.twig', [ + 'household' => $member->getHousehold(), + 'member' => $member, + 'form' => $form->createView(), + ]); + } + + /** + * Route for showing an editor to leave a household. + * + * Possibles arguments are: + * + * * persons[]: an id of the person to add to the form + * * household: the id of the destination household + * * allow_leave_without_household: if present, the editor will allow + * to leave household without joining another + * + * @Route( + * "/{_locale}/person/household/members/editor", + * name="chill_person_household_members_editor" + * ) + */ + public function editor(Request $request) + { + $em = $this->getDoctrine()->getManager(); + + if ($request->query->has('persons')) { + $ids = $request->query->get('persons', []); + + if (0 === count($ids)) { + throw new BadRequestException('parameters persons in query ' . + 'is not an array or empty'); + } + + $persons = $em->getRepository(Person::class) + ->findById($ids); + + foreach ($persons as $person) { + $this->denyAccessUnlessGranted( + PersonVoter::SEE, + $person, + "You are not allowed to see person with id {$person->getId()}" + ); + } + } + + if ($householdId = $request->query->get('household', false)) { + $household = $em->getRepository(Household::class) + ->find($householdId); + $allowHouseholdCreate = false; + $allowHouseholdSearch = false; + $allowLeaveWithoutHousehold = false; + + if (null === $household) { + throw $this->createNotFoundException('household not found'); + } + // TODO ACL on household + } + + $positions = $this->getDoctrine()->getManager() + ->getRepository(Position::class) + ->findAll(); + + $data = [ + 'persons' => $persons ?? false ? + $this->getSerializer()->normalize($persons, 'json', ['groups' => ['read']]) : [], + 'household' => $household ?? false ? + $this->getSerializer()->normalize($household, 'json', ['groups' => ['read']]) : null, + 'positions' => $this->getSerializer()->normalize($positions, 'json', ['groups' => ['read']]), + 'allowHouseholdCreate' => $allowHouseholdCreate ?? true, + 'allowHouseholdSearch' => $allowHouseholdSearch ?? true, + 'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'), + ]; + + // context + if ($request->query->has('accompanying_period_id')) { + $period = $this->periodRepository->find( + $request->query->getInt('accompanying_period_id') + ); + + if (null === $period) { + throw $this->createNotFoundException('period not found'); + } + + // TODO add acl on accompanying Course + } + + return $this->render('@ChillPerson/Household/members_editor.html.twig', [ + 'data' => $data, + 'expandSuggestions' => (int) $request->query->getBoolean('expand_suggestions', false), + 'accompanyingCourse' => $period ?? null, + ]); + } + + /** + * @Route( + * "/api/1.0/person/household/members/move.{_format}", + * name="chill_api_person_household_members_move" + * ) + * + * @param mixed $_format */ public function move(Request $request, $_format): Response { try { $editor = $this->getSerializer() - ->deserialize($request->getContent(), MembersEditor::class, - $_format, ['groups' => [ "read" ]]); + ->deserialize( + $request->getContent(), + MembersEditor::class, + $_format, + ['groups' => ['read']] + ); } catch (Exception\InvalidArgumentException | Exception\UnexpectedValueException $e) { throw new BadRequestException("Deserialization error: {$e->getMessage()}", 45896, $e); } @@ -68,143 +203,16 @@ class HouseholdMemberController extends ApiController // if new household, persist it if ( $editor->hasHousehold() - && - FALSE === $em->contains($editor->getHousehold()) + && false === $em->contains($editor->getHousehold()) ) { $em->persist($editor->getHousehold()); } foreach ($editor->getPersistable() as $el) { - $em->persist($el); + $em->persist($el); } $em->flush(); - return $this->json($editor->getHousehold(), Response::HTTP_OK, [], ["groups" => ["read"]]); - } - - /** - * Route for showing an editor to leave a household. - * - * Possibles arguments are: - * - * * persons[]: an id of the person to add to the form - * * household: the id of the destination household - * * allow_leave_without_household: if present, the editor will allow - * to leave household without joining another - * - * @Route( - * "/{_locale}/person/household/members/editor", - * name="chill_person_household_members_editor" - * ) - */ - public function editor(Request $request) - { - $em = $this->getDoctrine()->getManager(); - - if ($request->query->has('persons')) { - $ids = $request->query->get('persons', []); - - if (0 === count($ids)) { - throw new BadRequestExceptions("parameters persons in query ". - "is not an array or empty"); - } - - $persons = $em->getRepository(Person::class) - ->findById($ids) - ; - - foreach ($persons as $person) { - $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, - "You are not allowed to see person with id {$person->getId()}" - ); - } - } - - if ($householdId = $request->query->get('household', false)) { - $household = $em->getRepository(Household::class) - ->find($householdId) - ; - $allowHouseholdCreate = false; - $allowHouseholdSearch = false; - $allowLeaveWithoutHousehold = false; - - if (NULL === $household) { - throw $this->createNotFoundException('household not found'); - } - // TODO ACL on household - } - - $positions = $this->getDoctrine()->getManager() - ->getRepository(Position::class) - ->findAll() - ; - - $data = [ - 'persons' => $persons ?? false ? - $this->getSerializer()->normalize($persons, 'json', [ 'groups' => [ 'read' ]]) : [], - 'household' => $household ?? false ? - $this->getSerializer()->normalize($household, 'json', [ 'groups' => [ 'read' ]]) : null, - 'positions' => - $this->getSerializer()->normalize($positions, 'json', [ 'groups' => [ 'read' ]]), - 'allowHouseholdCreate' => $allowHouseholdCreate ?? true, - 'allowHouseholdSearch' => $allowHouseholdSearch ?? true, - 'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'), - ]; - - // context - if ($request->query->has('accompanying_period_id')) { - $period = $this->periodRepository->find( - $request->query->getInt('accompanying_period_id') - ); - - if ($period === null) { - throw $this->createNotFoundException('period not found'); - } - - // TODO add acl on accompanying Course - } - - return $this->render('@ChillPerson/Household/members_editor.html.twig', [ - 'data' => $data, - 'expandSuggestions' => (int) $request->query->getBoolean('expand_suggestions', false), - 'accompanyingCourse' => $period ?? null, - ]); - } - - /** - * @Route( - * "/{_locale}/person/household/member/{id}/edit", - * name="chill_person_household_member_edit" - * ) - */ - public function editMembership(Request $request, HouseholdMember $member): Response - { - // TODO ACL - - $form = $this->createForm(HouseholdMemberType::class, $member, [ - 'validation_groups' => [ 'household_memberships' ] - ]); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $this->getDoctrine()->getManager()->flush(); - - $this->addFlash('success', $this->translator - ->trans('household.successfully saved member')) - ; - - return $this->redirect( - $request->get('returnPath', null) ?? - $this->generator->generate('chill_person_household_summary', [ 'household_id' => - $member->getHousehold()->getId() ]) - ); - } - - return $this->render('@ChillPerson/Household/Member/edit.html.twig', [ - 'household' => $member->getHousehold(), - 'member' => $member, - 'form' => $form->createView() - ]); - + return $this->json($editor->getHousehold(), Response::HTTP_OK, [], ['groups' => ['read']]); } } diff --git a/src/Bundle/ChillPersonBundle/Controller/OpeningApiController.php b/src/Bundle/ChillPersonBundle/Controller/OpeningApiController.php index 3b44162c4..e62c0c123 100644 --- a/src/Bundle/ChillPersonBundle/Controller/OpeningApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/OpeningApiController.php @@ -1,10 +1,19 @@ where($qb->expr()->gt('e.noActiveAfter', ':now')) ->orWhere($qb->expr()->isNull('e.noActiveAfter')); - $qb->setParameter('now', new \DateTime('now')); - } + $qb->setParameter('now', new DateTime('now')); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php b/src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php index 4ec1b6218..d387ff737 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonAddressController.php @@ -1,43 +1,28 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller; +use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Form\Type\AddressType; +use Chill\PersonBundle\Entity\Person; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\SubmitType; -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; use Symfony\Component\Validator\Validator\ValidatorInterface; +use function count; /** * Class PersonAddressController - * Controller for addresses associated with person - * - * @package Chill\PersonBundle\Controller - * @author Julien Fastré - * @author Champs Libres + * Controller for addresses associated with person. */ class PersonAddressController extends AbstractController { @@ -48,78 +33,27 @@ class PersonAddressController extends AbstractController /** * PersonAddressController constructor. - * - * @param ValidatorInterface $validator */ public function __construct(ValidatorInterface $validator) { $this->validator = $validator; } - 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); + ->getRepository('ChillPersonBundle:Person') + ->find($person_id); - if ($person === null) { - throw $this->createNotFoundException("Person with id $person_id not" - . " found "); + if (null === $person) { + throw $this->createNotFoundException("Person with id {$person_id} not" + . ' found '); } $this->denyAccessUnlessGranted( 'CHILL_PERSON_UPDATE', $person, - "You are not allowed to edit this person." + 'You are not allowed to edit this person.' ); $address = new Address(); @@ -137,7 +71,6 @@ class PersonAddressController extends AbstractController $this->addFlash('error', $error->getMessage()); } } elseif ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); $em->flush(); @@ -146,47 +79,96 @@ class PersonAddressController extends AbstractController $this->get('translator')->trans('The new address was created successfully') ); - return $this->redirectToRoute('chill_person_address_list', array( - 'person_id' => $person->getId() - )); + return $this->redirectToRoute('chill_person_address_list', [ + 'person_id' => $person->getId(), + ]); } else { $this->addFlash('error', $this->get('translator') - ->trans('Error! Address not created!')); + ->trans('Error! Address not created!')); } } - return $this->render('ChillPersonBundle:Address:new.html.twig', array( + return $this->render('ChillPersonBundle:Address:new.html.twig', [ 'person' => $person, - 'form' => $form->createView() - )); + 'form' => $form->createView(), + ]); } public function editAction($person_id, $address_id) { $person = $this->getDoctrine()->getManager() - ->getRepository('ChillPersonBundle:Person') - ->find($person_id); + ->getRepository('ChillPersonBundle:Person') + ->find($person_id); - if ($person === null) { - throw $this->createNotFoundException("Person with id $person_id not" - . " found "); + if (null === $person) { + throw $this->createNotFoundException("Person with id {$person_id} not" + . ' found '); } $this->denyAccessUnlessGranted( 'CHILL_PERSON_UPDATE', $person, - "You are not allowed to edit this 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() - )); + return $this->render('ChillPersonBundle:Address:edit.html.twig', [ + 'person' => $person, + 'address' => $address, + 'form' => $form->createView(), + ]); + } + + public function listAction($person_id) + { + $person = $this->getDoctrine()->getManager() + ->getRepository('ChillPersonBundle:Person') + ->find($person_id); + + if (null === $person) { + 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', [ + 'person' => $person, + ]); + } + + public function newAction($person_id) + { + $person = $this->getDoctrine()->getManager() + ->getRepository('ChillPersonBundle:Person') + ->find($person_id); + + if (null === $person) { + 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', [ + 'person' => $person, + 'form' => $form->createView(), + ]); } public function updateAction($person_id, $address_id, Request $request) @@ -195,15 +177,15 @@ class PersonAddressController extends AbstractController ->getRepository('ChillPersonBundle:Person') ->find($person_id); - if ($person === null) { - throw $this->createNotFoundException("Person with id $person_id not" - . " found "); + if (null === $person) { + throw $this->createNotFoundException("Person with id {$person_id} not" + . ' found '); } $this->denyAccessUnlessGranted( 'CHILL_PERSON_UPDATE', $person, - "You are not allowed to edit this person." + 'You are not allowed to edit this person.' ); $address = $this->findAddressById($person, $address_id); @@ -211,7 +193,7 @@ class PersonAddressController extends AbstractController $form = $this->createEditForm($person, $address); $form->handleRequest($request); - if ($form->isSubmitted() and $form->isValid()) { + if ($form->isSubmitted() && $form->isValid()) { $validatePersonErrors = $this->validatePerson($person); if (count($validatePersonErrors) !== 0) { @@ -220,90 +202,84 @@ class PersonAddressController extends AbstractController } } elseif ($form->isValid()) { $this->getDoctrine()->getManager() - ->flush(); + ->flush(); $this->addFlash('success', $this->get('translator')->trans( - "The address has been successfully updated" + 'The address has been successfully updated' )); - return $this->redirectToRoute('chill_person_address_list', array( - 'person_id' => $person->getId() - )); + return $this->redirectToRoute('chill_person_address_list', [ + 'person_id' => $person->getId(), + ]); } else { $this->addFlash('error', $this->get('translator') ->trans('Error when updating the period')); } } - return $this->render('ChillPersonBundle:Address:edit.html.twig', array( - 'person' => $person, - 'address' => $address, - 'form' => $form->createView() - )); + return $this->render('ChillPersonBundle:Address:edit.html.twig', [ + '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() - )), - 'has_no_address' => true - )); - - $form->add('submit', SubmitType::class, array( - 'label' => 'Submit' - )); - - 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() - )), - 'has_no_address' => true - )); + $form = $this->createForm(AddressType::class, $address, [ + 'method' => 'POST', + 'action' => $this->generateUrl('chill_person_address_create', [ + 'person_id' => $person->getId(), + ]), + 'has_no_address' => true, + ]); - $form->add('submit', SubmitType::class, array( - 'label' => 'Submit' - )); + $form->add('submit', SubmitType::class, [ + 'label' => 'Submit', + ]); + + return $form; + } + + /** + * @return \Symfony\Component\Form\Form + */ + protected function createEditForm(Person $person, Address $address) + { + $form = $this->createForm(AddressType::class, $address, [ + 'method' => 'POST', + 'action' => $this->generateUrl('chill_person_address_update', [ + 'person_id' => $person->getId(), + 'address_id' => $address->getId(), + ]), + 'has_no_address' => true, + ]); + + $form->add('submit', SubmitType::class, [ + '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 + * + * @return Address */ protected function findAddressById(Person $person, $address_id) { $address = $this->getDoctrine()->getManager() ->getRepository(Address::class) - ->find($address_id) - ; + ->find($address_id); if (!$person->getAddresses()->contains($address)) { - throw $this->createAccessDeniedException("Not allowed to see this address"); + throw $this->createAccessDeniedException('Not allowed to see this address'); } return $address; @@ -311,16 +287,17 @@ class PersonAddressController extends AbstractController /** * @param Chill\PersonBundle\Entity\Person $person + * * @return \Symfony\Component\Validator\ConstraintViolationListInterface */ private function validatePerson(Person $person) { $errors = $this->validator - ->validate($person, null, array('Default')); + ->validate($person, null, ['Default']); $errors_addresses_consistent = $this->validator - ->validate($person, null, array('addresses_consistent')); + ->validate($person, null, ['addresses_consistent']); - foreach($errors_addresses_consistent as $error) { + foreach ($errors_addresses_consistent as $error) { $errors->add($error); } diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php b/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php index ab39c13bb..23f6ec95d 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonApiController.php @@ -1,70 +1,51 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Controller; -use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Chill\PersonBundle\Entity\Person; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\CRUD\Controller\ApiController; +use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Chill\MainBundle\Entity\Address; use Symfony\Component\Routing\Annotation\Route; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; +use Symfony\Component\Security\Core\Role\Role; +use function array_filter; +use function array_values; class PersonApiController extends ApiController { private AuthorizationHelper $authorizationHelper; - /** - * @param AuthorizationHelper $authorizationHelper - */ public function __construct(AuthorizationHelper $authorizationHelper) { $this->authorizationHelper = $authorizationHelper; } - protected function createEntity(string $action, Request $request): object - { - $person = parent::createEntity($action, $request); - - // TODO temporary hack to allow creation of person with fake center - /* $centers = $this->authorizationHelper->getReachableCenters($this->getUser(), - new Role(PersonVoter::CREATE)); - $person->setCenter($centers[0]); */ - - return $person; - } - public function personAddressApi($id, Request $request, string $_format): Response { - return $this->addRemoveSomething('address', $id, $request, $_format, 'address', Address::class, [ 'groups' => [ 'read' ] ]); + return $this->addRemoveSomething('address', $id, $request, $_format, 'address', Address::class, ['groups' => ['read']]); } /** * @Route("/api/1.0/person/address/suggest/by-person/{person_id}.{_format}", - * name="chill_person_address_suggest_by_person", - * requirements={ - * "_format"="json" + * name="chill_person_address_suggest_by_person", + * requirements={ + * "_format": "json" * } - * ) - * @ParamConverter("person", options={"id" = "person_id"}) + * ) + * @ParamConverter("person", options={"id": "person_id"}) */ public function suggestAddress(Person $person, Request $request, string $_format): Response { @@ -81,10 +62,20 @@ class PersonApiController extends ApiController // remove the actual address $actual = $person->getCurrentHouseholdAddress(); + if (null !== $actual) { - $addresses = \array_filter($addresses, fn($a) => $a !== $actual); + $addresses = array_filter($addresses, static fn ($a) => $a !== $actual); } - return $this->json(\array_values($addresses), Response::HTTP_OK, [], [ 'groups' => [ 'read' ]]); + return $this->json(array_values($addresses), Response::HTTP_OK, [], ['groups' => ['read']]); + } + + protected function createEntity(string $action, Request $request): object + { + return parent::createEntity($action, $request); + // TODO temporary hack to allow creation of person with fake center + /* $centers = $this->authorizationHelper->getReachableCenters($this->getUser(), + new Role(PersonVoter::CREATE)); + $person->setCenter($centers[0]); */ } } diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonController.php b/src/Bundle/ChillPersonBundle/Controller/PersonController.php index f9190857c..1b79ba867 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonController.php @@ -1,65 +1,49 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller; -use Chill\PersonBundle\Privacy\PrivacyEvent; -use Psr\Log\LoggerInterface; +use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Form\PersonType; use Chill\PersonBundle\Form\CreationPersonType; +use Chill\PersonBundle\Form\PersonType; +use Chill\PersonBundle\Privacy\PrivacyEvent; +use Chill\PersonBundle\Repository\PersonRepository; +use Chill\PersonBundle\Search\SimilarPersonMatcher; +use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\Form\Extension\Core\Type\ButtonType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Form; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\Security\Core\Role\Role; -use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Chill\PersonBundle\Search\SimilarPersonMatcher; +use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; use Symfony\Component\Translation\TranslatorInterface; -use Chill\MainBundle\Search\SearchProvider; -use Chill\PersonBundle\Repository\PersonRepository; -use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; -use Chill\PersonBundle\Repository\PersonNotDuplicateRepository; use Symfony\Component\Validator\Validator\ValidatorInterface; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Routing\Annotation\Route; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; +use function count; +use function hash; +use function implode; +use function in_array; +use function is_array; final class PersonController extends AbstractController { /** - * @var SimilarPersonMatcher + * @var ConfigPersonAltNamesHelper */ - protected $similarPersonMatcher; - - /** - * @var TranslatorInterface - */ - protected $translator; + protected $configPersonAltNameHelper; /** * @var EventDispatcherInterface @@ -72,9 +56,14 @@ final class PersonController extends AbstractController protected $personRepository; /** - * @var ConfigPersonAltNamesHelper + * @var SimilarPersonMatcher */ - protected $configPersonAltNameHelper; + protected $similarPersonMatcher; + + /** + * @var TranslatorInterface + */ + protected $translator; /** * @var EntityManagerInterface @@ -111,113 +100,100 @@ final class PersonController extends AbstractController $this->validator = $validator; $this->em = $em; $this->security = $security; - } + } + + public function editAction($person_id, Request $request) + { + $person = $this->_getPerson($person_id); + + if (null === $person) { + throw $this->createNotFoundException(); + } + + $this->denyAccessUnlessGranted( + 'CHILL_PERSON_UPDATE', + $person, + 'You are not allowed to edit this person' + ); + + $form = $this->createForm( + PersonType::class, + $person, + [ + 'cFGroup' => $this->getCFGroup(), + ] + ); + + $form->handleRequest($request); + + if ($form->isSubmitted() && !$form->isValid()) { + $this->get('session') + ->getFlashBag()->add('error', $this->translator + ->trans('This form contains errors')); + } elseif ($form->isSubmitted() && $form->isValid()) { + $this->get('session')->getFlashBag() + ->add( + 'success', + $this->get('translator') + ->trans('The person data has been updated') + ); + + $this->em->flush(); + + return $this->redirectToRoute('chill_person_view', [ + 'person_id' => $person->getId(), + ]); + } + + return $this->render( + 'ChillPersonBundle:Person:edit.html.twig', + ['person' => $person, 'form' => $form->createView()] + ); + } public function getCFGroup() { $cFGroup = null; - $cFDefaultGroup = $this->em->getRepository("ChillCustomFieldsBundle:CustomFieldsDefaultGroup") - ->findOneByEntity("Chill\PersonBundle\Entity\Person"); + $cFDefaultGroup = $this->em->getRepository('ChillCustomFieldsBundle:CustomFieldsDefaultGroup') + ->findOneByEntity('Chill\\PersonBundle\\Entity\\Person'); - if($cFDefaultGroup) { + if ($cFDefaultGroup) { $cFGroup = $cFDefaultGroup->getCustomFieldsGroup(); } return $cFGroup; } - public function viewAction($person_id) + /** + * @Route( + * "/{_locale}/person/household/{person_id}/history", + * name="chill_person_household_person_history", + * methods={"GET", "POST"} + * ) + * @ParamConverter("person", options={"id": "person_id"}) + */ + public function householdHistoryByPerson(Request $request, Person $person): Response { - $person = $this->_getPerson($person_id); - - if ($person === null) { - throw $this->createNotFoundException("Person with id $person_id not" - . " found on this server"); - } - - $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person, - "You are not allowed to see this person."); + $this->denyAccessUnlessGranted( + 'CHILL_PERSON_SEE', + $person, + 'You are not allowed to see this person.' + ); $event = new PrivacyEvent($person); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); - return $this->render('ChillPersonBundle:Person:view.html.twig', - array( - "person" => $person, - "cFGroup" => $this->getCFGroup(), - "alt_names" => $this->configPersonAltNameHelper->getChoices(), - )); - } - - public function editAction($person_id) - { - $person = $this->_getPerson($person_id); - - if ($person === null) { - throw $this->createNotFoundException(); - } - - $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person, - 'You are not allowed to edit this person'); - - $form = $this->createForm(PersonType::class, $person, - array( - "action" => $this->generateUrl('chill_person_general_update', - array("person_id" => $person_id)), - "cFGroup" => $this->getCFGroup() - ) + return $this->render( + '@ChillPerson/Person/household_history.html.twig', + [ + 'person' => $person, + ] ); - - return $this->render('ChillPersonBundle:Person:edit.html.twig', - array('person' => $person, 'form' => $form->createView())); - } - - public function updateAction($person_id, Request $request) - { - $person = $this->_getPerson($person_id); - - if ($person === null) { - return $this->createNotFoundException(); - } - - $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person, - 'You are not allowed to edit this person'); - - $form = $this->createForm(PersonType::class, $person, - array("cFGroup" => $this->getCFGroup())); - - if ($request->getMethod() === 'POST') { - $form->handleRequest($request); - - if ( ! $form->isValid() ) { - $this->get('session') - ->getFlashBag()->add('error', $this->translator - ->trans('This form contains errors')); - - return $this->render('ChillPersonBundle:Person:edit.html.twig', - array('person' => $person, - 'form' => $form->createView())); - } - - $this->get('session')->getFlashBag() - ->add('success', - $this->get('translator') - ->trans('The person data has been updated') - ); - - $this->em->flush(); - - $url = $this->generateUrl('chill_person_view', array( - 'person_id' => $person->getId() - )); - - return $this->redirect($url); - } } /** - * Method for creating a new person + * Method for creating a new person. * *The controller register data from a previous post on the form, and * register it in the session. @@ -225,15 +201,14 @@ final class PersonController extends AbstractController * The next post compare the data with previous one and, if yes, show a * review page if there are "alternate persons". * - * @param Request $request - * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response + * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse */ public function newAction(Request $request) { $person = new Person(); if (1 === count($this->security->getUser() - ->getGroupCenters())) { + ->getGroupCenters())) { $person->setCenter( $this->security->getUser() ->getGroupCenters()[0] @@ -242,14 +217,12 @@ final class PersonController extends AbstractController } $form = $this->createForm(CreationPersonType::class, $person, [ - 'validation_groups' => ['create'] + 'validation_groups' => ['create'], ])->add('editPerson', SubmitType::class, [ - 'label' => 'Add the person' + 'label' => 'Add the person', ])->add('createPeriod', SubmitType::class, [ - 'label' => 'Add the person and create an accompanying period' - ])->add('createHousehold', SubmitType::class, [ - 'label' => 'Add the person and create an household' - ]); // TODO createHousehold form action + 'label' => 'Add the person and create an accompanying period', + ]); $form->handleRequest($request); @@ -257,14 +230,12 @@ final class PersonController extends AbstractController $this->lastPostDataReset(); } elseif ($request->getMethod() === Request::METHOD_POST && $form->isValid()) { - $alternatePersons = $this->similarPersonMatcher ->matchPerson($person); if ( - FALSE === $this->isLastPostDataChanges($form, $request, true) - || - count($alternatePersons) === 0 + false === $this->isLastPostDataChanges($form, $request, true) + || count($alternatePersons) === 0 ) { $this->em->persist($person); @@ -273,84 +244,75 @@ final class PersonController extends AbstractController if ($form->get('createPeriod')->isClicked()) { return $this->redirectToRoute('chill_person_accompanying_course_new', [ - 'person_id' => [ $person->getId() ] + 'person_id' => [$person->getId()], ]); } - return $this->redirectToRoute('chill_person_general_edit', - ['person_id' => $person->getId()]); + return $this->redirectToRoute( + 'chill_person_general_edit', + ['person_id' => $person->getId()] + ); } } elseif ($request->getMethod() === Request::METHOD_POST && !$form->isValid()) { $this->addFlash('error', $this->translator->trans('This form contains errors')); } - return $this->render('@ChillPerson/Person/create.html.twig', + return $this->render( + '@ChillPerson/Person/create.html.twig', [ 'form' => $form->createView(), - 'alternatePersons' => $alternatePersons ?? [] + 'alternatePersons' => $alternatePersons ?? [], ] ); } - private function isLastPostDataChanges(Form $form, Request $request, bool $replace = false): bool + public function viewAction($person_id) { - /** @var SessionInterface $session */ - $session = $this->get('session'); - if (!$session->has('last_person_data')) { - return true; + $person = $this->_getPerson($person_id); + + if (null === $person) { + throw $this->createNotFoundException("Person with id {$person_id} not" + . ' found on this server'); } - $newPost = $this->lastPostDataBuildHash($form, $request); + $this->denyAccessUnlessGranted( + 'CHILL_PERSON_SEE', + $person, + 'You are not allowed to see this person.' + ); - $isChanged = $newPost !== $session->get('last_person_data'); + $event = new PrivacyEvent($person); + $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); - if ($replace) { - $session->set('last_person_data', $newPost); - } - - return $isChanged ; - } - - private function lastPostDataReset(): void - { - $this->get('session')->set('last_person_data', ""); + return $this->render( + 'ChillPersonBundle:Person:view.html.twig', + [ + 'person' => $person, + 'cFGroup' => $this->getCFGroup(), + 'alt_names' => $this->configPersonAltNameHelper->getChoices(), + ] + ); } /** - * build the hash for posted data + * easy getting a person by his id. * - * For privacy reasons, the data are hashed using sha512 + * @param mixed $id * - * @param Form $form - * @param Request $request - * @return string + * @return \Chill\PersonBundle\Entity\Person */ - private function lastPostDataBuildHash(Form $form, Request $request): string + private function _getPerson($id) { - $fields = []; - $ignoredFields = ['form_status', '_token']; - - foreach ($request->request->all()[$form->getName()] as $field => $value) { - if (\in_array($field, $ignoredFields)) { - continue; - } - $fields[$field] = \is_array($value) ? - \implode(",", $value) : $value; - } - ksort($fields); - - return \hash('sha512', \implode("&", $fields)); + return $this->personRepository->find($id); } /** - * - * @param \Chill\PersonBundle\Entity\Person $person * @return \Symfony\Component\Validator\ConstraintViolationListInterface */ private function _validatePersonAndAccompanyingPeriod(Person $person) { $errors = $this->validator - ->validate($person, null, array('creation')); + ->validate($person, null, ['creation']); //validate accompanying periods $periods = $person->getAccompanyingPeriods(); @@ -360,7 +322,7 @@ final class PersonController extends AbstractController ->validate($period); //group errors : - foreach($period_errors as $error) { + foreach ($period_errors as $error) { $errors->add($error); } } @@ -368,39 +330,50 @@ final class PersonController extends AbstractController return $errors; } - /** - * easy getting a person by his id - * @return \Chill\PersonBundle\Entity\Person - */ - private function _getPerson($id) + private function isLastPostDataChanges(Form $form, Request $request, bool $replace = false): bool { - $person = $this->personRepository->find($id); + /** @var SessionInterface $session */ + $session = $this->get('session'); - return $person; + if (!$session->has('last_person_data')) { + return true; + } + + $newPost = $this->lastPostDataBuildHash($form, $request); + + $isChanged = $session->get('last_person_data') !== $newPost; + + if ($replace) { + $session->set('last_person_data', $newPost); + } + + return $isChanged; } /** + * build the hash for posted data. * - * @Route( - * "/{_locale}/person/household/{person_id}/history", - * name="chill_person_household_person_history", - * methods={"GET", "POST"} - * ) - * @ParamConverter("person", options={"id" = "person_id"}) + * For privacy reasons, the data are hashed using sha512 */ - public function householdHistoryByPerson(Request $request, Person $person): Response + private function lastPostDataBuildHash(Form $form, Request $request): string { - $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person, - "You are not allowed to see this person."); + $fields = []; + $ignoredFields = ['form_status', '_token']; - $event = new PrivacyEvent($person); - $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); + foreach ($request->request->all()[$form->getName()] as $field => $value) { + if (in_array($field, $ignoredFields, true)) { + continue; + } + $fields[$field] = is_array($value) ? + implode(',', $value) : $value; + } + ksort($fields); - return $this->render( - '@ChillPerson/Person/household_history.html.twig', - [ - 'person' => $person - ] - ); + return hash('sha512', implode('&', $fields)); + } + + private function lastPostDataReset(): void + { + $this->get('session')->set('last_person_data', ''); } } diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php index d30ddebae..0b3f36b1c 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php @@ -1,29 +1,53 @@ similarPersonMatcher = $similarPersonMatcher; $this->translator = $translator; @@ -63,29 +72,6 @@ class PersonDuplicateController extends Controller $this->eventDispatcher = $eventDispatcher; } - public function viewAction($person_id, PersonNotDuplicateRepository $personNotDuplicateRepository) - { - $person = $this->_getPerson($person_id); - if ($person === null) { - throw $this->createNotFoundException("Person with id $person_id not" - . " found on this server"); - } - - $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person, - "You are not allowed to see this person."); - - $duplicatePersons = $this->similarPersonMatcher-> - matchPerson($person, $personNotDuplicateRepository, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL); - - $notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person); - - return $this->render('ChillPersonBundle:PersonDuplicate:view.html.twig', [ - 'person' => $person, - 'duplicatePersons' => $duplicatePersons, - 'notDuplicatePersons' => $notDuplicatePersons, - ]); - } - public function confirmAction($person1_id, $person2_id, Request $request) { if ($person1_id === $person2_id) { @@ -98,17 +84,20 @@ class PersonDuplicateController extends Controller $person1->counters = $this->_getCounters($person1_id); $person2->counters = $this->_getCounters($person2_id); - if ($person1 === null) { - throw $this->createNotFoundException("Person with id $person1_id not" - . " found on this server"); + if (null === $person1) { + throw $this->createNotFoundException("Person with id {$person1_id} not" + . ' found on this server'); } - $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1, - "You are not allowed to see this person."); + $this->denyAccessUnlessGranted( + 'CHILL_PERSON_DUPLICATE', + $person1, + 'You are not allowed to see this person.' + ); - if ($person2 === null) { - throw $this->createNotFoundException("Person with id $person2_id not" - . " found on this server"); + if (null === $person2) { + throw $this->createNotFoundException("Person with id {$person2_id} not" + . ' found on this server'); } $form = $this->createForm(PersonConfimDuplicateType::class); @@ -116,10 +105,10 @@ class PersonDuplicateController extends Controller $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $event = new PrivacyEvent($person1, array( + $event = new PrivacyEvent($person1, [ 'element_class' => Person::class, - 'action' => 'move' - )); + 'action' => 'move', + ]); $event->addPerson($person2); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); @@ -128,7 +117,8 @@ class PersonDuplicateController extends Controller $connection = $this->getDoctrine()->getConnection(); $connection->beginTransaction(); - foreach($sqls as $sql) { + + foreach ($sqls as $sql) { $connection->executeQuery($sql); } $connection->commit(); @@ -143,15 +133,68 @@ class PersonDuplicateController extends Controller ]); } + public function findManuallyDuplicateAction($person_id, Request $request) + { + $person = $this->_getPerson($person_id); + + if (null === $person) { + throw $this->createNotFoundException("Person with id {$person_id} not" + . ' found on this server'); + } + + $this->denyAccessUnlessGranted( + 'CHILL_PERSON_DUPLICATE', + $person, + 'You are not allowed to see this person.' + ); + + $form = $this->createForm(PersonFindManuallyDuplicateType::class); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $person2 = $form->get('person')->getData(); + + if (null === $person2) { + throw $this->createNotFoundException("Person with id {$person2->getId}() not" + . ' found on this server'); + } + + $direction = $form->get('direction')->getData(); + + if ('starting' === $direction) { + $params = [ + 'person1_id' => $person->getId(), + 'person2_id' => $person2->getId(), + ]; + } else { + $params = [ + 'person1_id' => $person2->getId(), + 'person2_id' => $person->getId(), + ]; + } + + return $this->redirectToRoute('chill_person_duplicate_confirm', $params); + } + + return $this->render('ChillPersonBundle:PersonDuplicate:find_manually.html.twig', [ + 'person' => $person, + 'form' => $form->createView(), + ]); + } + public function notDuplicateAction($person1_id, $person2_id) { [$person1, $person2] = $this->_getPersonsByPriority($person1_id, $person2_id); - $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1, - "You are not allowed to see this person."); + $this->denyAccessUnlessGranted( + 'CHILL_PERSON_DUPLICATE', + $person1, + 'You are not allowed to see this person.' + ); $personNotDuplicate = $this->getDoctrine()->getRepository(PersonNotDuplicate::class) - ->findOneBy(['person1' => $person1, 'person2' => $person2]); + ->findOneBy(['person1' => $person1, 'person2' => $person2]); if (!$personNotDuplicate instanceof PersonNotDuplicate) { $personNotDuplicate = new PersonNotDuplicate(); @@ -170,11 +213,14 @@ class PersonDuplicateController extends Controller { [$person1, $person2] = $this->_getPersonsByPriority($person1_id, $person2_id); - $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1, - "You are not allowed to see this person."); + $this->denyAccessUnlessGranted( + 'CHILL_PERSON_DUPLICATE', + $person1, + 'You are not allowed to see this person.' + ); $personNotDuplicate = $this->getDoctrine()->getRepository(PersonNotDuplicate::class) - ->findOneBy(['person1' => $person1, 'person2' => $person2]); + ->findOneBy(['person1' => $person1, 'person2' => $person2]); if ($personNotDuplicate instanceof PersonNotDuplicate) { $this->getDoctrine()->getManager()->remove($personNotDuplicate); @@ -184,54 +230,56 @@ class PersonDuplicateController extends Controller return $this->redirectToRoute('chill_person_duplicate_view', ['person_id' => $person1->getId()]); } - public function findManuallyDuplicateAction($person_id, Request $request) + public function viewAction($person_id, PersonNotDuplicateRepository $personNotDuplicateRepository) { $person = $this->_getPerson($person_id); - if ($person === null) { - throw $this->createNotFoundException("Person with id $person_id not" - . " found on this server"); + + if (null === $person) { + throw $this->createNotFoundException("Person with id {$person_id} not" + . ' found on this server'); } - $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person, - "You are not allowed to see this person."); + $this->denyAccessUnlessGranted( + 'CHILL_PERSON_DUPLICATE', + $person, + 'You are not allowed to see this person.' + ); - $form = $this->createForm(PersonFindManuallyDuplicateType::class); + $duplicatePersons = $this->similarPersonMatcher-> + matchPerson($person, $personNotDuplicateRepository, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL); - $form->handleRequest($request); + $notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person); - if ($form->isSubmitted() && $form->isValid()) { - $person2 = $form->get('person')->getData(); - - if ($person2 === null) { - throw $this->createNotFoundException("Person with id $person2->getId() not" - . " found on this server"); - } - - $direction = $form->get('direction')->getData(); - - if ($direction === 'starting') { - $params = [ - 'person1_id' => $person->getId(), - 'person2_id' => $person2->getId(), - ]; - } else { - $params = [ - 'person1_id' => $person2->getId(), - 'person2_id' => $person->getId(), - ]; - } - - return $this->redirectToRoute('chill_person_duplicate_confirm', $params); - } - - return $this->render('ChillPersonBundle:PersonDuplicate:find_manually.html.twig', [ + return $this->render('ChillPersonBundle:PersonDuplicate:view.html.twig', [ 'person' => $person, - 'form' => $form->createView(), + 'duplicatePersons' => $duplicatePersons, + 'notDuplicatePersons' => $notDuplicatePersons, ]); } + private function _getCounters($id): ?array + { + $em = $this->getDoctrine()->getManager(); + + $nb_activity = $em->getRepository(Activity::class)->findBy(['person' => $id]); + $nb_document = $em->getRepository(PersonDocument::class)->findBy(['person' => $id]); + $nb_event = $em->getRepository(Participation::class)->findBy(['person' => $id]); + $nb_task = $em->getRepository(SingleTask::class)->countByParameters(['person' => $id]); + $person = $em->getRepository(Person::class)->findOneBy(['id' => $id]); + + return [ + 'nb_activity' => count($nb_activity), + 'nb_document' => count($nb_document), + 'nb_event' => count($nb_event), + 'nb_task' => $nb_task, + 'nb_addresses' => count($person->getAddresses()), + ]; + } + /** - * easy getting a person by his id + * easy getting a person by his id. + * + * @param mixed $id */ private function _getPerson($id): ?Person { @@ -252,35 +300,16 @@ class PersonDuplicateController extends Controller $person2 = $this->_getPerson($person2_id); } - if ($person1 === null) { - throw $this->createNotFoundException("Person with id $person1_id not" - . " found on this server"); + if (null === $person1) { + throw $this->createNotFoundException("Person with id {$person1_id} not" + . ' found on this server'); } - if ($person2 === null) { - throw $this->createNotFoundException("Person with id $person2_id not" - . " found on this server"); + if (null === $person2) { + throw $this->createNotFoundException("Person with id {$person2_id} not" + . ' found on this server'); } return [$person1, $person2]; } - - private function _getCounters($id): ?array - { - $em = $this->getDoctrine()->getManager(); - - $nb_activity = $em->getRepository(Activity::class)->findBy(['person'=>$id]); - $nb_document = $em->getRepository(PersonDocument::class)->findBy(['person'=>$id]); - $nb_event = $em->getRepository(Participation::class)->findBy(['person'=>$id]); - $nb_task = $em->getRepository(SingleTask::class)->countByParameters(['person'=>$id]); - $person = $em->getRepository(Person::class)->findOneBy(['id'=>$id]); - - return [ - 'nb_activity' => count($nb_activity), - 'nb_document' => count($nb_document), - 'nb_event' => count($nb_event), - 'nb_task' => $nb_task, - 'nb_addresses' => count($person->getAddresses()) - ]; - } } diff --git a/src/Bundle/ChillPersonBundle/Controller/RelationshipApiController.php b/src/Bundle/ChillPersonBundle/Controller/RelationshipApiController.php new file mode 100644 index 000000000..271b0cff7 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/RelationshipApiController.php @@ -0,0 +1,45 @@ +validator = $validator; + $this->repository = $repository; + } + + /** + * @ParamConverter("person", options={"id": "person_id"}) + */ + public function getRelationshipsByPerson(Person $person) + { + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person); + + $relationships = $this->repository->findByPerson($person); + + return $this->json($relationships, Response::HTTP_OK, [], ['groups' => ['read']]); + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialIssueApiController.php b/src/Bundle/ChillPersonBundle/Controller/SocialIssueApiController.php index c1d251efb..d154c8e2e 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialIssueApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialIssueApiController.php @@ -1,5 +1,14 @@ orderBy("GET_JSON_FIELD_BY_KEY(e.title, :locale)", 'ASC') + ->orderBy('GET_JSON_FIELD_BY_KEY(e.title, :locale)', 'ASC') ->setParameter(':locale', $request->getLocale()); return null; diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminEvaluationController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminEvaluationController.php index 134cedc74..8105066e9 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminEvaluationController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminEvaluationController.php @@ -1,32 +1,21 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; /** * Class AdminSocialIssueController - * Controller for social issues - * - * @package Chill\PersonBundle\Controller + * Controller for social issues. */ class AdminEvaluationController extends CRUDController { diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminGoalController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminGoalController.php index 18e1c9f3b..149d3c372 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminGoalController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminGoalController.php @@ -1,32 +1,21 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; /** * Class AdminSocialIssueController - * Controller for social issues - * - * @package Chill\PersonBundle\Controller + * Controller for social issues. */ class AdminGoalController extends CRUDController { diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminResultController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminResultController.php index 80d552282..98ea9592a 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminResultController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminResultController.php @@ -1,32 +1,21 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; /** * Class AdminSocialIssueController - * Controller for social issues - * - * @package Chill\PersonBundle\Controller + * Controller for social issues. */ class AdminResultController extends CRUDController { diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminSocialActionController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminSocialActionController.php index 695ec75a1..c254f8178 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminSocialActionController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminSocialActionController.php @@ -1,32 +1,21 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; /** * Class AdminSocialIssueController - * Controller for social issues - * - * @package Chill\PersonBundle\Controller + * Controller for social issues. */ class AdminSocialActionController extends CRUDController { diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminSocialIssueController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminSocialIssueController.php index 35b0df4a2..f783ab182 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminSocialIssueController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/AdminSocialIssueController.php @@ -1,32 +1,21 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; /** * Class AdminSocialIssueController - * Controller for social issues - * - * @package Chill\PersonBundle\Controller + * Controller for social issues. */ class AdminSocialIssueController extends CRUDController { diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWorkEvaluationApiController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWorkEvaluationApiController.php index 24b919978..98b40311d 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWorkEvaluationApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWorkEvaluationApiController.php @@ -1,22 +1,28 @@ paginatorFactory = $paginatorFactory; @@ -26,21 +32,21 @@ class SocialWorkEvaluationApiController extends AbstractController * @Route("/api/1.0/person/social-work/evaluation/by-social-action/{action_id}.json", * name="chill_person_evaluation_index_by_social_action", * requirements={ - * "_format": "json" + * "_format": "json" * } * ) * @ParamConverter("action", options={"id": "action_id"}) - * @param SocialAction $action - * @return Response */ public function listEvaluationBySocialAction(SocialAction $action): Response { $pagination = $this->paginatorFactory->create($action->getEvaluations()->count()); - $evaluations = $action->getEvaluations()->slice($pagination->getCurrentPageFirstItemNumber(), - $pagination->getItemsPerPage()); + $evaluations = $action->getEvaluations()->slice( + $pagination->getCurrentPageFirstItemNumber(), + $pagination->getItemsPerPage() + ); $collection = new Collection($evaluations, $pagination); - return $this->json($collection, Response::HTTP_OK, [], [ 'groups' => [ 'read' ]]); + return $this->json($collection, Response::HTTP_OK, [], ['groups' => ['read']]); } } diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWorkGoalApiController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWorkGoalApiController.php index 2e678a0d0..a9d1238da 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWorkGoalApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWorkGoalApiController.php @@ -1,13 +1,21 @@ goalRepository = $goalRepository; @@ -28,13 +35,16 @@ class SocialWorkGoalApiController extends ApiController { $totalItems = $this->goalRepository->countBySocialActionWithDescendants($action); $paginator = $this->getPaginatorFactory()->create($totalItems); - - $entities = $this->goalRepository->findBySocialActionWithDescendants($action, ["id" => "ASC"], - $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber()); - + + $entities = $this->goalRepository->findBySocialActionWithDescendants( + $action, + ['id' => 'ASC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + $model = new Collection($entities, $paginator); - return $this->json($model, Response::HTTP_OK, [], [ "groups" => [ "read" ]]); + return $this->json($model, Response::HTTP_OK, [], ['groups' => ['read']]); } - } diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWorkResultApiController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWorkResultApiController.php index 24902a163..3ce3848fd 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWorkResultApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWorkResultApiController.php @@ -1,12 +1,21 @@ resultRepository = $resultRepository; } + public function listByGoal(Request $request, Goal $goal): Response + { + $totalItems = $this->resultRepository->countByGoal($goal); + $paginator = $this->getPaginatorFactory()->create($totalItems); + + $entities = $this->resultRepository->findByGoal( + $goal, + ['id' => 'ASC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + $model = new Collection($entities, $paginator); + + return $this->json($model, Response::HTTP_OK, [], ['groups' => ['read']]); + } + public function listBySocialAction(Request $request, SocialAction $action): Response { $totalItems = $this->resultRepository->countBySocialActionWithDescendants($action); $paginator = $this->getPaginatorFactory()->create($totalItems); - - $entities = $this->resultRepository->findBySocialActionWithDescendants($action, ["id" => "ASC"], - $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber()); - + + $entities = $this->resultRepository->findBySocialActionWithDescendants( + $action, + ['id' => 'ASC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + $model = new Collection($entities, $paginator); - return $this->json($model, Response::HTTP_OK, [], [ "groups" => [ "read" ]]); - } - - public function listByGoal(Request $request, Goal $goal): Response - { - $totalItems = $this->resultRepository->countByGoal($goal); - $paginator = $this->getPaginatorFactory()->create($totalItems); - - $entities = $this->resultRepository->findByGoal($goal, ["id" => "ASC"], - $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber()); - - $model = new Collection($entities, $paginator); - - return $this->json($model, Response::HTTP_OK, [], [ "groups" => [ "read" ]]); + return $this->json($model, Response::HTTP_OK, [], ['groups' => ['read']]); } } diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php index de87b380a..24c1c6645 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php @@ -1,36 +1,43 @@ socialIssueRepository = $socialIssueRepository; $this->paginator = $paginator; } - + public function listBySocialIssueApi($id, Request $request) { $socialIssue = $this->socialIssueRepository ->find($id); - if (NULL === $socialIssue) { - throw $this->createNotFoundException("socialIssue not found"); + if (null === $socialIssue) { + throw $this->createNotFoundException('socialIssue not found'); } $socialActions = $socialIssue->getRecursiveSocialActions(); @@ -39,9 +46,7 @@ class SocialWorkSocialActionApiController extends ApiController $pagination->setItemsPerPage(count($socialActions)); $collection = new Collection($socialActions, $pagination); - - - return $this->json($collection, JsonResponse::HTTP_OK, [], [ "groups" => [ "read" ]]); - } + return $this->json($collection, JsonResponse::HTTP_OK, [], ['groups' => ['read']]); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php b/src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php index 4e3d67755..7402698d9 100644 --- a/src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php +++ b/src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php @@ -1,96 +1,78 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Controller; +use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\MainBundle\Timeline\TimelineBuilder; +use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Privacy\PrivacyEvent; +use Chill\PersonBundle\Security\Authorization\PersonVoter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; -use Chill\MainBundle\Timeline\TimelineBuilder; -use Chill\MainBundle\Pagination\PaginatorFactory; -use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Symfony\Component\Security\Core\Role\Role; class TimelinePersonController extends AbstractController { - protected EventDispatcherInterface $eventDispatcher; - - protected TimelineBuilder $timelineBuilder; - + protected PaginatorFactory $paginatorFactory; - - /** - * TimelinePersonController constructor. - * - * @param EventDispatcherInterface $eventDispatcher - */ + + protected TimelineBuilder $timelineBuilder; + public function __construct( EventDispatcherInterface $eventDispatcher, TimelineBuilder $timelineBuilder, - PaginatorFactory $paginatorFactory, - AuthorizationHelper $authorizationHelper + PaginatorFactory $paginatorFactory ) { $this->eventDispatcher = $eventDispatcher; $this->timelineBuilder = $timelineBuilder; $this->paginatorFactory = $paginatorFactory; - $this->authorizationHelper = $authorizationHelper; } - - + public function personAction(Request $request, $person_id) { $person = $this->getDoctrine() - ->getRepository('ChillPersonBundle:Person') - ->find($person_id); + ->getRepository(Person::class) + ->find($person_id); - if ($person === NULL) { + if (null === $person) { throw $this->createNotFoundException(); } - + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person); - - $nbItems = $this->timelineBuilder->countItems('person', - [ 'person' => $person ] - ); - + + $nbItems = $this->timelineBuilder->countItems( + 'person', + ['person' => $person] + ); + $paginator = $this->paginatorFactory->create($nbItems); - - $event = new PrivacyEvent($person, array('action' => 'timeline')); + + $event = new PrivacyEvent($person, ['action' => 'timeline']); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); - - return $this->render('ChillPersonBundle:Timeline:index.html.twig', array - ( + + return $this->render( + 'ChillPersonBundle:Timeline:index.html.twig', + [ 'timeline' => $this->timelineBuilder->getTimelineHTML( - 'person', - array('person' => $person), + 'person', + ['person' => $person], $paginator->getCurrentPage()->getFirstItemNumber(), $paginator->getItemsPerPage() - ), + ), 'person' => $person, 'nb_items' => $nbItems, - 'paginator' => $paginator - ) + 'paginator' => $paginator, + ] ); } - } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/Helper/PersonRandomHelper.php b/src/Bundle/ChillPersonBundle/DataFixtures/Helper/PersonRandomHelper.php new file mode 100644 index 000000000..4f5a51fd4 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/Helper/PersonRandomHelper.php @@ -0,0 +1,50 @@ +countPersons) { + $qb = $em->createQueryBuilder(); + $this->countPersons = $qb->select('count(p)') + ->from(Person::class, 'p') + ->getQuery() + ->getSingleScalarResult(); + } + + if ([] === $this->randPersons) { + $qb = $em->createQueryBuilder(); + $this->randPersons = $qb + ->select('p') + ->from(Person::class, 'p') + ->getQuery() + ->setFirstResult(random_int(0, $this->countPersons - $fetchBy)) + ->setMaxResults($fetchBy) + ->getResult(); + } + + return array_pop($this->randPersons); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/Helper/RandomPersonHelperTrait.php b/src/Bundle/ChillPersonBundle/DataFixtures/Helper/RandomPersonHelperTrait.php new file mode 100644 index 000000000..516914f5f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/Helper/RandomPersonHelperTrait.php @@ -0,0 +1,42 @@ +createQueryBuilder(); + $qb + ->from(Person::class, 'p'); + + if (null === $this->nbOfPersons) { + $this->nbOfPersons = $qb + ->select('COUNT(p)') + ->getQuery() + ->getSingleScalarResult(); + } + + return $qb + ->select('p') + ->setMaxResults(1) + ->setFirstResult(random_int(0, $this->nbOfPersons)) + ->getQuery() + ->getSingleResult(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodClosingMotive.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodClosingMotive.php index fad0596a6..c1d045ac4 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodClosingMotive.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodClosingMotive.php @@ -1,89 +1,70 @@ , - * - * 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 . + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\DataFixtures\ORM; +use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; /** - * Load closing motives into database - * - * @author Julien Fastré + * Load closing motives into database. */ -class LoadAccompanyingPeriodClosingMotive extends AbstractFixture - implements OrderedFixtureInterface +class LoadAccompanyingPeriodClosingMotive extends AbstractFixture implements OrderedFixtureInterface { - - public function getOrder() { + public static $closingMotives = [ + 'nothing_to_do' => [ + 'name' => [ + 'fr' => 'Plus rien à faire', + 'en' => 'Nothing to do', + 'nl' => 'nieks meer te doen', + ], + ], + 'did_not_come_back' => [ + 'name' => [ + 'fr' => "N'est plus revenu", + 'en' => "Did'nt come back", + 'nl' => 'Niet teruggekomen', + ], + ], + 'no_more_money' => [ + 'active' => false, + 'name' => [ + 'fr' => "Plus d'argent", + 'en' => 'No more money', + 'nl' => 'Geen geld', + ], + ], + ]; + + public static $references = []; + + public function getOrder() + { return 9500; } - - public static $closingMotives = array( - 'nothing_to_do' => array( - 'name' => array( - 'fr' => 'Plus rien à faire', - 'en' => 'Nothing to do', - 'nl' => 'nieks meer te doen' - ) - ), - 'did_not_come_back' => array( - 'name' => array( - 'fr' => "N'est plus revenu", - 'en' => "Did'nt come back", - 'nl' => "Niet teruggekomen" - ) - ), - 'no_more_money' => array( - 'active' => false, - 'name' => array( - 'fr' => "Plus d'argent", - 'en' => "No more money", - 'nl' => "Geen geld" - ) - ) - ); - - public static $references = array(); - - public function load(ObjectManager $manager) + + public function load(ObjectManager $manager) { foreach (static::$closingMotives as $ref => $new) { $motive = new ClosingMotive(); $motive->setName($new['name']) - ->setActive((isset($new['active']) ? $new['active'] : true)) - ; - + ->setActive(($new['active'] ?? true)); + $manager->persist($motive); $this->addReference($ref, $motive); - echo "Adding ClosingMotive $ref\n"; + echo "Adding ClosingMotive {$ref}\n"; } - + $manager->flush(); } - - - - } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodNotifications.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodNotifications.php index e96bf44e6..1e392c9f2 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodNotifications.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodNotifications.php @@ -1,14 +1,23 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\DataFixtures\ORM; +use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; + use Doctrine\Persistence\ObjectManager; -use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; - /** - * Description of LoadAccompanyingPeriodOrigin - * - * @author Champs-Libres Coop + * Description of LoadAccompanyingPeriodOrigin. */ class LoadAccompanyingPeriodOrigin extends AbstractFixture implements OrderedFixtureInterface { - public const ACCOMPANYING_PERIOD_ORIGIN = 'accompanying_period_origin'; - public function getOrder() - { - return 10005; - } + public static $references = []; private $phoneCall = ['en' => 'phone call', 'fr' => 'appel téléphonique']; - public static $references = array(); + public function getOrder() + { + return 9000; + } public function load(ObjectManager $manager) { $o = new Origin(); - $o->setLabel(json_encode($this->phoneCall)); + $o->setLabel($this->phoneCall); $manager->persist($o); diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodWork.php index 82e232e8d..64c7d2701 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodWork.php @@ -1,5 +1,14 @@ evaluationRepository = $evaluationRepository; } - /** - * @inheritDoc - */ public function getDependencies() { return [ - LoadPeople::class + LoadPeople::class, ]; } + public function load(ObjectManager $manager) + { + // load all the period which are confirmed + $periods = $this->periodRepository + ->findBy(['step' => AccompanyingPeriod::STEP_CONFIRMED]); + + $i = 0; + + while (null !== $period = array_pop($periods)) { + /** @var AccompanyingPeriod $period */ + ++$i; + + if (0 === $i % 15) { + $manager->flush(); + } + + if (0 === $period->getSocialIssues()->count()) { + continue; + } + $work = new AccompanyingPeriod\AccompanyingPeriodWork(); + $action = $this->getRandomAction($period->getSocialIssues()->first()); + + if (null !== $action) { + $work + ->setAccompanyingPeriod($period) + ->setSocialAction($action) + ->setStartDate(new DateTimeImmutable('today')) + ->addPerson($period->getPersons()->first()) + ->setCreatedAt(new DateTimeImmutable()) + ->setCreatedBy($this->getReference('center a_social')) + ->setUpdatedAt(new DateTimeImmutable()) + ->setUpdatedBy($this->getReference('center a_social')); + $manager->persist($work); + + if ($action->getEvaluations()->count() > 0) { + $ev = $action->getEvaluations()->first(); + $evaluation = new AccompanyingPeriod\AccompanyingPeriodWorkEvaluation(); + $evaluation->setAccompanyingPeriodWork($work) + ->setEvaluation($ev); + $manager->persist($evaluation); + } + } + + // 1 of 10, force an evaluation + if (0 === $i % 10) { + $evaluation = $this->getRandomEvaluation(); + $action = $evaluation->getSocialAction(); + $issue = $action->getIssue(); + + $period->addSocialIssue($issue); + $work + ->setAccompanyingPeriod($period) + ->setSocialAction($action) + ->setStartDate(new DateTimeImmutable('today')) + ->addPerson($period->getPersons()->first()) + ->setCreatedAt(new DateTimeImmutable()) + ->setCreatedBy($this->getReference('center a_social')) + ->setUpdatedAt(new DateTimeImmutable()) + ->setUpdatedBy($this->getReference('center a_social')); + $manager->persist($work); + $ev = new AccompanyingPeriod\AccompanyingPeriodWorkEvaluation(); + $ev->setAccompanyingPeriodWork($work) + ->setEvaluation($evaluation); + $manager->persist($ev); + } + } + + $manager->flush(); + } + private function getRandomAction(SocialIssue $socialIssue): ?SocialAction { $actions = $socialIssue->getRecursiveSocialActions()->toArray(); @@ -52,89 +127,16 @@ class LoadAccompanyingPeriodWork extends \Doctrine\Bundle\FixturesBundle\Fixture return null; } - return $actions[\array_rand($actions)]; + return $actions[array_rand($actions)]; } private function getRandomEvaluation(): Evaluation { - if (0 === count($this->cacheEvaluations)) { - $this->cacheEvaluations = $this->evaluationRepository - ->findAll(); - } - - return $this->cacheEvaluations[\array_rand($this->cacheEvaluations)]; - } - - /** - * @inheritDoc - */ - public function load(ObjectManager $manager) - { - // load all the period which are confirmed - $periods = $this->periodRepository - ->findBy(['step' => AccompanyingPeriod::STEP_CONFIRMED]) - ; - - $i = 0; - while (null !== $period = \array_pop($periods)) { - /** @var AccompanyingPeriod $period */ - $i++; - if (0 === $i % 15) { - $manager->flush(); - } - if (0 === $period->getSocialIssues()->count()) { - continue; - } - $work = new AccompanyingPeriod\AccompanyingPeriodWork(); - $action = $this->getRandomAction($period->getSocialIssues()->first()); - - if (null !== $action) { - $work - ->setAccompanyingPeriod($period) - ->setSocialAction($action) - ->setStartDate(new \DateTimeImmutable('today')) - ->addPerson($period->getPersons()->first()) - ->setCreatedAt(new \DateTimeImmutable()) - ->setCreatedBy($this->getReference('center a_social')) - ->setUpdatedAt(new \DateTimeImmutable()) - ->setUpdatedBy($this->getReference('center a_social')); - $manager->persist($work); - - if ($action->getEvaluations()->count() > 0) { - $ev = $action->getEvaluations()->first(); - $evaluation = new AccompanyingPeriod\AccompanyingPeriodWorkEvaluation(); - $evaluation->setAccompanyingPeriodWork($work) - ->setEvaluation($ev); - $manager->persist($evaluation); - } - } - - // 1 of 10, force an evaluation - if (0 === $i % 10) { - $evaluation = $this->getRandomEvaluation(); - $action = $evaluation->getSocialAction(); - $issue = $action->getIssue(); - - $period->addSocialIssue($issue); - $work - ->setAccompanyingPeriod($period) - ->setSocialAction($action) - ->setStartDate(new \DateTimeImmutable('today')) - ->addPerson($period->getPersons()->first()) - ->setCreatedAt(new \DateTimeImmutable()) - ->setCreatedBy($this->getReference('center a_social')) - ->setUpdatedAt(new \DateTimeImmutable()) - ->setUpdatedBy($this->getReference('center a_social')) - ; - $manager->persist($work); - $ev = new AccompanyingPeriod\AccompanyingPeriodWorkEvaluation(); - $ev->setAccompanyingPeriodWork($work) - ->setEvaluation($evaluation); - $manager->persist($ev); - } - + if (0 === count($this->cacheEvaluations)) { + $this->cacheEvaluations = $this->evaluationRepository + ->findAll(); } - $manager->flush(); + return $this->cacheEvaluations[array_rand($this->cacheEvaluations)]; } } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadCustomFields.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadCustomFields.php index 235cadd95..9541aa76b 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadCustomFields.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadCustomFields.php @@ -1,78 +1,63 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\DataFixtures\ORM; +use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice; +use Chill\CustomFieldsBundle\CustomFields\CustomFieldText; +use Chill\CustomFieldsBundle\CustomFields\CustomFieldTitle; +use Chill\CustomFieldsBundle\Entity\CustomField; +use Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup; +use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup; use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\PersonBundle\Entity\Person; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; +use RuntimeException; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -use Chill\CustomFieldsBundle\Entity\CustomField; -use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup; -use Chill\CustomFieldsBundle\CustomFields\CustomFieldTitle; -use Chill\CustomFieldsBundle\CustomFields\CustomFieldText; -use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice; -use Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup; -use Chill\PersonBundle\Entity\Person; use Symfony\Contracts\Translation\TranslatorInterface; -/** - * - * - * @author Julien Fastré - */ -class LoadCustomFields extends AbstractFixture implements OrderedFixtureInterface, - ContainerAwareInterface +class LoadCustomFields extends AbstractFixture implements + ContainerAwareInterface, + OrderedFixtureInterface { /** - * * @var ContainerInterface */ private $container; - + /** - * - * @var CustomField - */ - private $customFieldText; - - /** - * * @var CustomField */ private $customFieldChoice; - + + /** + * @var CustomField + */ + private $customFieldText; + /** * @var TranslatableStringHelper */ private $translatableStringHelper; - + /** * @var TranslatorInterface */ private $translator; - + /** * LoadCustomFields constructor. - * - * @param TranslatableStringHelper $translatableStringHelper - * @param TranslatorInterface $translator */ public function __construct( TranslatableStringHelper $translatableStringHelper, @@ -81,21 +66,12 @@ class LoadCustomFields extends AbstractFixture implements OrderedFixtureInterfac $this->translatableStringHelper = $translatableStringHelper; $this->translator = $translator; } - + //put your code here public function getOrder() { return 10003; } - - public function setContainer(ContainerInterface $container = null) - { - if ($container === null) { - throw new \RuntimeException("The given container should not be null"); - } - - $this->container = $container; - } public function load(ObjectManager $manager) { @@ -103,45 +79,16 @@ class LoadCustomFields extends AbstractFixture implements OrderedFixtureInterfac $this->loadData($manager); $manager->flush(); } - - private function loadData(ObjectManager $manager) + + public function setContainer(?ContainerInterface $container = null) { - $personIds = $this->container->get('doctrine.orm.entity_manager') - ->createQuery("SELECT person.id FROM ChillPersonBundle:Person person") - ->getScalarResult(); - - // get possible values for cfGroup - $choices = array_map( - function($a) { return $a["slug"]; }, - $this->customFieldChoice->getOptions()["choices"] - ); - // create faker - $faker = \Faker\Factory::create('fr_FR'); - // select a set of people and add data - foreach ($personIds as $id) { - // add info on 1 person on 2 - if (rand(0,1) === 1) { - /* @var $person Person */ - $person = $manager->getRepository(Person::class)->find($id); - $person->setCFData(array( - "remarques" => $this->createCustomFieldText() - ->serialize($faker->text(rand(150, 250)), $this->customFieldText), - "document-d-identite" => $this->createCustomFieldChoice() - ->serialize(array($choices[array_rand($choices)]), $this->customFieldChoice) - )); - } + if (null === $container) { + throw new RuntimeException('The given container should not be null'); } + + $this->container = $container; } - - private function createCustomFieldText() - { - return new CustomFieldText( - $this->container->get('request_stack'), - $this->container->get('templating'), - $this->translatableStringHelper - ); - } - + private function createCustomFieldChoice() { return new CustomFieldChoice( @@ -150,80 +97,113 @@ class LoadCustomFields extends AbstractFixture implements OrderedFixtureInterfac $this->translatableStringHelper ); } - + + private function createCustomFieldText() + { + return new CustomFieldText( + $this->container->get('request_stack'), + $this->container->get('templating'), + $this->translatableStringHelper + ); + } + + private function loadData(ObjectManager $manager) + { + $personIds = $this->container->get('doctrine.orm.entity_manager') + ->createQuery('SELECT person.id FROM ChillPersonBundle:Person person') + ->getScalarResult(); + + // get possible values for cfGroup + $choices = array_map( + static function ($a) { return $a['slug']; }, + $this->customFieldChoice->getOptions()['choices'] + ); + // create faker + $faker = \Faker\Factory::create('fr_FR'); + // select a set of people and add data + foreach ($personIds as $id) { + // add info on 1 person on 2 + if (mt_rand(0, 1) === 1) { + /** @var Person $person */ + $person = $manager->getRepository(Person::class)->find($id); + $person->setCFData([ + 'remarques' => $this->createCustomFieldText() + ->serialize($faker->text(mt_rand(150, 250)), $this->customFieldText), + 'document-d-identite' => $this->createCustomFieldChoice() + ->serialize([$choices[array_rand($choices)]], $this->customFieldChoice), + ]); + } + } + } + private function loadFields(ObjectManager $manager) { $cfGroup = (new CustomFieldsGroup()) ->setEntity(Person::class) - ->setName(array("fr" => "Données")) - ; + ->setName(['fr' => 'Données']); $manager->persist($cfGroup); - + // make this group default for Person::class $manager->persist( (new CustomFieldsDefaultGroup()) ->setCustomFieldsGroup($cfGroup) ->setEntity(Person::class) - ); - + ); + // create title field $customField0 = (new CustomField()) ->setActive(true) - ->setName(array("fr" => "Données personnalisées")) - ->setSlug("personal-data") + ->setName(['fr' => 'Données personnalisées']) + ->setSlug('personal-data') ->setOrdering(10) ->setType('title') - ->setOptions(array(CustomFieldTitle::TYPE => CustomFieldTitle::TYPE_TITLE)) - ->setCustomFieldsGroup($cfGroup) - ; + ->setOptions([CustomFieldTitle::TYPE => CustomFieldTitle::TYPE_TITLE]) + ->setCustomFieldsGroup($cfGroup); $manager->persist($customField0); - + // create text field $this->customFieldText = (new CustomField()) ->setActive(true) - ->setName(array("fr" => "Remarques")) - ->setSlug("remarques") + ->setName(['fr' => 'Remarques']) + ->setSlug('remarques') ->setOrdering(20) ->setType('text') - ->setOptions(array('maxLength' => 5000)) - ->setCustomFieldsGroup($cfGroup) - ; + ->setOptions(['maxLength' => 5000]) + ->setCustomFieldsGroup($cfGroup); $manager->persist($this->customFieldText); - + // create choice field $this->customFieldChoice = (new CustomField()) ->setActive(true) - ->setName(array("fr" => "Document d'identité")) - ->setSlug("document-d-identite") + ->setName(['fr' => "Document d'identité"]) + ->setSlug('document-d-identite') ->setOrdering(30) ->setType('choice') ->setCustomFieldsGroup($cfGroup) - ->setOptions(array( - "multiple" => true, - "other" => false, - "expanded" => true, - "active" => true, - "slug" => "document-d-identite", - "choices" => array( - array( - "name" => array("fr" => "Carte d'identité"), - "active" => true, - "slug" => "carte-d-identite" - ), - array( - "name" => array("fr" => "Passeport"), - "active" => true, - "slug" => "passeport" - ), - array( - "name" => array("fr" => "Titre de séjour"), - "active" => true, - "slug" => "passeport" - ) - ) - )) - ; + ->setOptions([ + 'multiple' => true, + 'other' => false, + 'expanded' => true, + 'active' => true, + 'slug' => 'document-d-identite', + 'choices' => [ + [ + 'name' => ['fr' => "Carte d'identité"], + 'active' => true, + 'slug' => 'carte-d-identite', + ], + [ + 'name' => ['fr' => 'Passeport'], + 'active' => true, + 'slug' => 'passeport', + ], + [ + 'name' => ['fr' => 'Titre de séjour'], + 'active' => true, + 'slug' => 'passeport', + ], + ], + ]); $manager->persist($this->customFieldChoice); } - } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php index 24a5bbb7f..6f9edd768 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php @@ -1,28 +1,46 @@ loader = new NativeLoader(); } + public function getDependencies() + { + return [ + LoadPeople::class, + LoadHouseholdPosition::class, + ]; + } + public function load(ObjectManager $manager) { // generate two times the participation. This will lead to @@ -41,87 +67,44 @@ class LoadHousehold extends Fixture implements DependentFixtureInterface $this->generateHousehold( $manager, - \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') + DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') ); $this->preparePersonIds(); $this->generateHousehold( $manager, - \DateTimeImmutable::createFromFormat('Y-m-d', '2015-01-01') + DateTimeImmutable::createFromFormat('Y-m-d', '2015-01-01') ); $manager->flush(); } - private function generateHousehold(ObjectManager $manager, \DateTimeImmutable $startDate) + private function addAddressToHousehold(Household $household, DateTimeImmutable $date, ObjectManager $manager) { - for ($i=0; $i < self::NUMBER_OF_HOUSEHOLD; $i++) { - $household = new Household(); - $manager->persist($household); - - $this->addAddressToHousehold($household, clone $startDate, $manager); - - $movement = $this->editorFactory->createEditor($household); - - // load adults - $k = 0; - foreach ($this->getRandomPersons(1, 3) as $person) { - $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); - $position = $this->getReference(LoadHouseholdPosition::ADULT); - - $movement->addMovement($date, $person, $position, $k === 0, "self generated"); - $k++; - } - - // load children - foreach ($this->getRandomPersons(0, 3) as $person) { - $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); - $position = $this->getReference(LoadHouseholdPosition::CHILD); - - $movement->addMovement($date, $person, $position, $k === 0, "self generated"); - $k++; - } - - // load children out - foreach ($this->getRandomPersons(0, 2) as $person) { - $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); - $position = $this->getReference(LoadHouseholdPosition::CHILD_OUT); - - $movement->addMovement($date, $person, $position, $k === 0, "self generated"); - $k++; - } - - foreach ($movement->getPersistable() as $obj) { - $manager->persist($obj); - } - } - } - - private function addAddressToHousehold(Household $household, \DateTimeImmutable $date, ObjectManager $manager) - { - if (\random_int(0, 10) > 8) { + if (random_int(0, 10) > 8) { // 20% of household without address return; } - $nb = \random_int(1, 6); + $nb = random_int(1, 6); $i = 0; + while ($i < $nb) { $address = $this->createAddress(); - $address->setValidFrom(\DateTime::createFromImmutable($date)); + $address->setValidFrom(DateTime::createFromImmutable($date)); - if (\random_int(0, 20) < 1) { - $date = $date->add(new \DateInterval('P'.\random_int(8, 52).'W')); - $address->setValidTo(\DateTime::createFromImmutable($date)); + if (random_int(0, 20) < 1) { + $date = $date->add(new DateInterval('P' . random_int(8, 52) . 'W')); + $address->setValidTo(DateTime::createFromImmutable($date)); } $household->addAddress($address); $manager->persist($address); - $date = $date->add(new \DateInterval('P'.\random_int(8, 52).'W')); - $i++; + $date = $date->add(new DateInterval('P' . random_int(8, 52) . 'W')); + ++$i; } } @@ -132,54 +115,92 @@ class LoadHousehold extends Fixture implements DependentFixtureInterface 'address1' => [ 'street' => '', 'streetNumber' => '', - 'postCode' => $this->getPostalCode() - ] - ] + 'postCode' => $this->getPostalCode(), + ], + ], ]); return $objectSet->getObjects()['address1']; } + private function generateHousehold(ObjectManager $manager, DateTimeImmutable $startDate) + { + for ($i = 0; self::NUMBER_OF_HOUSEHOLD > $i; ++$i) { + $household = new Household(); + $manager->persist($household); + + $this->addAddressToHousehold($household, clone $startDate, $manager); + + $movement = $this->editorFactory->createEditor($household); + + // load adults + $k = 0; + + foreach ($this->getRandomPersons(1, 3) as $person) { + $date = $startDate->add(new DateInterval('P' . random_int(1, 200) . 'W')); + $position = $this->getReference(LoadHouseholdPosition::ADULT); + + $movement->addMovement($date, $person, $position, 0 === $k, 'self generated'); + ++$k; + } + + // load children + foreach ($this->getRandomPersons(0, 3) as $person) { + $date = $startDate->add(new DateInterval('P' . random_int(1, 200) . 'W')); + $position = $this->getReference(LoadHouseholdPosition::CHILD); + + $movement->addMovement($date, $person, $position, 0 === $k, 'self generated'); + ++$k; + } + + // load children out + foreach ($this->getRandomPersons(0, 2) as $person) { + $date = $startDate->add(new DateInterval('P' . random_int(1, 200) . 'W')); + $position = $this->getReference(LoadHouseholdPosition::CHILD_OUT); + + $movement->addMovement($date, $person, $position, 0 === $k, 'self generated'); + ++$k; + } + + foreach ($movement->getPersistable() as $obj) { + $manager->persist($obj); + } + } + } + private function getPostalCode(): PostalCode { - $ref = LoadPostalCodes::$refs[\array_rand(LoadPostalCodes::$refs)]; + $ref = LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)]; return $this->getReference($ref); } + private function getRandomPersons(int $min, int $max): array + { + $persons = []; + + $nb = random_int($min, $max); + + for ($i = 0; $i < $nb; ++$i) { + $personId = array_pop($this->personIds)['id']; + $persons[] = $this->em->getRepository(Person::class)->find($personId); + } + + return $persons; + } + private function preparePersonIds() { + // @TODO: Remove this and make this service stateless $this->personIds = $this->em - ->createQuery('SELECT p.id FROM '.Person::class.' p '. - 'JOIN p.center c '. + ->createQuery( + 'SELECT p.id FROM ' . Person::class . ' p ' . + 'JOIN p.center c ' . 'WHERE c.name = :center ' ) ->setParameter('center', 'Center A') - ->getScalarResult() - ; + ->getScalarResult(); - \shuffle($this->personIds); - } - - private function getRandomPersons(int $min, int $max) - { - $nb = \random_int($min, $max); - - for ($i=0; $i < $nb; $i++) { - $personId = \array_pop($this->personIds)['id']; - $persons[] = $this->em->getRepository(Person::class) - ->find($personId) - ; - } - - return $persons ?? []; - } - - public function getDependencies() - { - return [ - LoadPeople::class, - LoadHouseholdPosition::class - ]; + shuffle($this->personIds); } } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php index cf0bf3de1..42d2b7dd8 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php @@ -1,5 +1,14 @@ setLabel([ "fr" => $name ]) + ->setLabel(['fr' => $name]) ->setAllowHolder($allowHolder) ->setShareHousehold($share) - ->setOrdering($ordering) - ; + ->setOrdering($ordering); $manager->persist($position); $this->addReference($ref, $position); diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadMaritalStatus.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadMaritalStatus.php index b0cf57637..45a9f6fd4 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadMaritalStatus.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadMaritalStatus.php @@ -1,66 +1,54 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\DataFixtures\ORM; +use Chill\PersonBundle\Entity\MaritalStatus; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Chill\PersonBundle\Entity\MaritalStatus; /** - * Load marital status into database - * - * @author Marc Ducobu + * Load marital status into database. */ class LoadMaritalStatus extends AbstractFixture implements OrderedFixtureInterface { private $maritalStatuses = [ - ['id' => 'single', 'name' =>['en' => 'single', 'fr' => 'célibataire']], - ['id' => 'married', 'name' =>['en' => 'married', 'fr' => 'marié(e)']], - ['id' => 'widow', 'name' =>['en' => 'widow', 'fr' => 'veuf – veuve ']], - ['id' => 'separat', 'name' =>['en' => 'separated', 'fr' => 'séparé(e)']], - ['id' => 'divorce', 'name' =>['en' => 'divorced', 'fr' => 'divorcé(e)']], - ['id' => 'legalco', 'name' =>['en' => 'legal cohabitant', 'fr' => 'cohabitant(e) légal(e)']], - ['id' => 'unknown', 'name' =>['en' => 'unknown', 'fr' => 'indéterminé']] + ['id' => 'single', 'name' => ['en' => 'single', 'fr' => 'célibataire']], + ['id' => 'married', 'name' => ['en' => 'married', 'fr' => 'marié(e)']], + ['id' => 'widow', 'name' => ['en' => 'widow', 'fr' => 'veuf – veuve ']], + ['id' => 'separat', 'name' => ['en' => 'separated', 'fr' => 'séparé(e)']], + ['id' => 'divorce', 'name' => ['en' => 'divorced', 'fr' => 'divorcé(e)']], + ['id' => 'legalco', 'name' => ['en' => 'legal cohabitant', 'fr' => 'cohabitant(e) légal(e)']], + ['id' => 'unknown', 'name' => ['en' => 'unknown', 'fr' => 'indéterminé']], ]; public function getOrder() { return 9999; } - + public function load(ObjectManager $manager) { echo "loading maritalStatuses... \n"; foreach ($this->maritalStatuses as $ms) { - echo $ms['name']['en'].' '; + echo $ms['name']['en'] . ' '; $new_ms = new MaritalStatus(); $new_ms->setId($ms['id']); $new_ms->setName($ms['name']); - $this->addReference('ms_'.$ms['id'], $new_ms); + $this->addReference('ms_' . $ms['id'], $new_ms); $manager->persist($new_ms); } - + $manager->flush(); } } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php index adc37d5b5..924155955 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php @@ -1,26 +1,19 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\DataFixtures\ORM; +use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes; +use Chill\MainBundle\Doctrine\Model\Point; +use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Entity\PostalCode; @@ -32,380 +25,92 @@ use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Repository\UserRepository; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\MaritalStatus; +use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Repository\MaritalStatusRepository; -use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; +use DateInterval; +use DateTime; +use DateTimeImmutable; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Chill\PersonBundle\Entity\Person; +use Exception; use Faker\Factory; use Faker\Generator; -use Nelmio\Alice\Faker\GeneratorFactory; use Nelmio\Alice\Loader\NativeLoader; use Nelmio\Alice\ObjectSet; use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes; -use Chill\MainBundle\Entity\Address; -use Chill\MainBundle\Doctrine\Model\Point; use Symfony\Component\Workflow\Registry; -use Symfony\Component\Workflow\Workflow; -use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; +use function count; +use function random_int; +use function ucfirst; /** - * Load people into database - * + * Load people into database. */ -class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface +class LoadPeople extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface { use \Symfony\Component\DependencyInjection\ContainerAwareTrait; - protected Generator $faker; - - protected Registry $workflowRegistry; - - protected SocialIssueRepository $socialIssueRepository; - - protected CountryRepository $countryRepository; - - protected NativeLoader $loader; + public const PERSON = 'person'; /** - * @var array|SocialIssue[] + * @var array|Center[] */ - protected array $cacheSocialIssues = []; + protected array $cacheCenters = []; /** * @var array|Country[] */ protected array $cacheCountries = []; - /** - * @var array|Center[] - */ - protected array $cacheCenters = []; - - protected CenterRepository $centerRepository; - /** * @var array|MaritalStatus[] */ protected array $cacheMaritalStatuses = []; - protected MaritalStatusRepository $maritalStatusRepository; - /** * @var array|Scope[] */ protected array $cacheScopes = []; + /** + * @var array|SocialIssue[] + */ + protected array $cacheSocialIssues = []; + + /** + * @var array|User[] + */ + protected array $cacheUsers = []; + + protected CenterRepository $centerRepository; + + protected CountryRepository $countryRepository; + + protected Generator $faker; + + protected NativeLoader $loader; + + protected MaritalStatusRepository $maritalStatusRepository; + protected ScopeRepository $scopeRepository; - /** @var array|User[] */ - protected array $cacheUsers = []; + protected SocialIssueRepository $socialIssueRepository; protected UserRepository $userRepository; - public function __construct( - Registry $workflowRegistry, - SocialIssueRepository $socialIssueRepository, - CenterRepository $centerRepository, - CountryRepository $countryRepository, - MaritalStatusRepository $maritalStatusRepository, - ScopeRepository $scopeRepository, - UserRepository $userRepository - ) { - $this->faker = Factory::create('fr_FR'); - $this->faker->addProvider($this); - $this->workflowRegistry = $workflowRegistry; - $this->socialIssueRepository = $socialIssueRepository; - $this->centerRepository = $centerRepository; - $this->countryRepository = $countryRepository; - $this->maritalStatusRepository = $maritalStatusRepository; - $this->loader = new NativeLoader($this->faker); - $this->scopeRepository = $scopeRepository; - $this->userRepository = $userRepository; - } + protected Registry $workflowRegistry; - public function getOrder() - { - return 10000; - } + private $genders = [Person::MALE_GENDER, Person::FEMALE_GENDER, Person::BOTH_GENDER]; - public function load(ObjectManager $manager) - { - $this->loadExpectedPeople($manager); - $this->loadRandPeople($manager); - - $manager->flush(); - } - - public function loadExpectedPeople(ObjectManager $manager) - { - echo "loading expected people...\n"; - - - foreach ($this->peoples as $personDef) { - $person = $this->createExpectedPerson($personDef); - $this->addAPerson($person, $manager); - } - } - - protected function loadRandPeople(ObjectManager $manager) - { - echo "loading rand people...\n"; - $persons = $this->createRandPerson()->getObjects(); - - foreach ($persons as $person) { - $this->addAPerson($person, $manager); - } - } - - private function createRandPerson(): ObjectSet - { - return $this->loader->loadData([ - Person::class => [ - 'persons{1..300}' => [ - 'firstName' => '', - 'lastName' => '', - 'gender' => '', - 'nationality' => '', - 'center' => '', - 'maritalStatus' => '', - 'birthdate' => '', - 'placeOfBirth' => '', - 'email' => '', - 'countryOfBirth' => '', - ] - ] - ]); - } - - private function createExpectedPerson($default): Person - { - $person = $this->loader->loadData([ - Person::class => [ - "person" => [ - 'firstName' => $default['firstName'] ?? '', - 'lastName' => $default['lastName'] ?? '', - 'gender' => '', - 'nationality' => '', - 'center' => '', - 'maritalStatus' => '', - 'birthdate' => '', - 'placeOfBirth' => '', - 'email' => '', - 'countryOfBirth' => '', - ], - ] - ])->getObjects()['person']; - - // force some values - foreach ($default as $key => $value) { - switch ($key) { - case 'birthdate': - $person->setBirthdate(new \DateTime($value)); - break; - case 'center': - $person->setCenter($this->centerRepository - ->findOneBy(['name' => $value])); - break; - case 'countryOfBirth': - case 'nationality': - $country = $this->countryRepository - ->findOneBy(['countryCode' => $value]); - $person->{'set'.\ucfirst($key)}($country); - break; - case 'maritalStatus': - $person->setMaritalStatus($this->maritalStatusRepository - ->find($value)); - break; - } - } - - return $person; - } - - /** - * create a new person from array data - * - * @throws \Exception - */ - private function addAPerson(Person $person, ObjectManager $manager) - { - $accompanyingPeriod = new AccompanyingPeriod( - (new \DateTime()) - ->sub( - new \DateInterval('P' . \random_int(0, 180) . 'D') - ) - ); - $accompanyingPeriod->setCreatedBy($this->getRandomUser()) - ->setCreatedAt(new \DateTimeImmutable('now')); - $person->addAccompanyingPeriod($accompanyingPeriod); - $accompanyingPeriod->addSocialIssue($this->getRandomSocialIssue()); - - if (\random_int(0, 10) > 3) { - // always add social scope: - $accompanyingPeriod->addScope($this->getReference('scope_social')); - - $accompanyingPeriod->setAddressLocation($this->createAddress()); - $manager->persist($accompanyingPeriod->getAddressLocation()); - $workflow = $this->workflowRegistry->get($accompanyingPeriod); - $workflow->apply($accompanyingPeriod, 'confirm'); - } - - $manager->persist($person); - $manager->persist($accompanyingPeriod); - echo "add person'".$person->__toString()."'\n"; - } - - private function getRandomUser(): User - { - if (0 === count($this->cacheUsers)) { - $this->cacheUsers = $this->userRepository->findAll(); - } - - return $this->cacheUsers[\array_rand($this->cacheUsers)]; - } - - private function createAddress(): Address - { - $objectSet = $this->loader->loadData([ - Address::class => [ - 'address' => [ - 'street' => '', - 'streetNumber' => '', - 'validFrom' => '', - 'postCode' => $this->getPostalCode() - ], - ], - ]); - - return $objectSet->getObjects()['address']; - } - - - - private function getRandomSocialIssue(): SocialIssue - { - if (0 === count($this->cacheSocialIssues)) { - $this->cacheSocialIssues = $this->socialIssueRepository->findAll(); - } - - return $this->cacheSocialIssues[\array_rand($this->cacheSocialIssues)]; - } - - private function getPostalCode(): PostalCode - { - $ref = LoadPostalCodes::$refs[\array_rand(LoadPostalCodes::$refs)]; - - return $this->getReference($ref); - } - - /** - * Create a random point - * - * @return Point - */ - private function getRandomPoint() - { - $lonBrussels = 4.35243; - $latBrussels = 50.84676; - $lon = $lonBrussels + 0.01 * rand(-5, 5); - $lat = $latBrussels + 0.01 * rand(-5, 5); - return Point::fromLonLat($lon, $lat); - } - - /** - * Create a random address - * - * @return Address - */ - private function getRandomAddress() - { - return (new Address()) - ->setStreetAddress1($this->faker->streetAddress) - ->setStreetAddress2( - rand(0,9) > 5 ? $this->faker->streetAddress : '' - ) - ->setPoint( - rand(0,9) > 5 ? $this->getRandomPoint() : NULL - ) - ->setPostcode($this->getReference( - LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] - )) - ->setValidFrom($this->faker->dateTimeBetween('-5 years')) - ; - } - - /** - * @internal This method is public and called by faker as a custom generator - * @return Center - */ - public function getRandomCenter(): Center - { - if (0 === count($this->cacheCenters)) { - $this->cacheCenters = $this->centerRepository->findAll(); - } - - return $this->cacheCenters[\array_rand($this->cacheCenters)]; - } - - /** - * @internal This method is public and called by faker as a custom generator - * @param int $nullPercentage - * @return Country|null - * @throws \Exception - */ - public function getRandomCountry(int $nullPercentage = 20): ?Country - { - if (0 === count($this->cacheCountries)) { - $this->cacheCountries = $this->countryRepository->findAll(); - } - - if ($nullPercentage < \random_int(0, 100)) { - return NULL; - } - - return $this->cacheCountries [\array_rand($this->cacheCountries)]; - } - - /** - * @internal This method is public and called by faker as a custom generator - * @return string - */ - public function getRandomGender(): string - { - return $this->genders[array_rand($this->genders)]; - } - - /** - * @internal This method is public and called by faker as a custom generator - * @param int $nullPercentage - * @return MaritalStatus|null - * @throws \Exception - */ - public function getRandomMaritalStatus(int $nullPercentage = 50): ?MaritalStatus - { - if (0 === count($this->cacheMaritalStatuses)) { - $this->cacheMaritalStatuses = $this->maritalStatusRepository->findAll(); - } - - if ($nullPercentage < \random_int(0, 100)) { - return NULL; - } - - return $this->cacheMaritalStatuses[array_rand($this->cacheMaritalStatuses)]; - } - - private $genders = array(Person::MALE_GENDER, Person::FEMALE_GENDER, Person::BOTH_GENDER); - - private $peoples = array( - array( - 'lastName' => "Depardieu", - 'firstName' => "Gérard", - 'birthdate' => "1948-12-27", - 'placeOfBirth' => "Châteauroux", + private $peoples = [ + [ + 'lastName' => 'Depardieu', + 'firstName' => 'Gérard', + 'birthdate' => '1948-12-27', + 'placeOfBirth' => 'Châteauroux', 'nationality' => 'RU', 'gender' => Person::MALE_GENDER, 'center' => 'Center A', @@ -414,73 +119,73 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con 'from' => '2015-02-01', 'to' => '2015-10-30', 'remark' => 'oops', - ],[ + ], [ 'from' => '2017-06-01', 'to' => '2018-03-30', 'remark' => 'argg', - ],[ + ], [ 'from' => '2019-01-01', 'to' => '2019-12-31', 'remark' => 'blob', - ] - ] - ), - array( + ], + ], + ], + [ //to have a person with same firstname as Gérard Depardieu - 'lastName' => "Depardieu", - 'firstName' => "Jean", - 'birthdate' => "1960-10-12", + 'lastName' => 'Depardieu', + 'firstName' => 'Jean', + 'birthdate' => '1960-10-12', 'countryOfBirth' => 'FR', 'nationality' => 'FR', 'center' => 'Center A', - 'maritalStatus' => 'ms_divorce' - ), - array( + 'maritalStatus' => 'ms_divorce', + ], + [ //to have a person with same birthdate of Gérard Depardieu 'lastName' => 'Van Snick', 'firstName' => 'Bart', 'birthdate' => '1948-12-27', 'center' => 'Center A', - 'maritalStatus' => 'ms_legalco' - ), - array( + 'maritalStatus' => 'ms_legalco', + ], + [ //to have a woman with Depardieu as FirstName 'lastName' => 'Depardieu', 'firstName' => 'Charline', 'gender' => Person::FEMALE_GENDER, 'center' => 'Center A', - 'maritalStatus' => 'ms_legalco' - ), - array( + 'maritalStatus' => 'ms_legalco', + ], + [ //to have a special character in lastName 'lastName' => 'Manço', 'firstName' => 'Étienne', 'center' => 'Center A', - 'maritalStatus' => 'ms_unknown' - ), - array( + 'maritalStatus' => 'ms_unknown', + ], + [ //to have true duplicate person - 'lastName' => "Depardieu", - 'firstName' => "Jean", - 'birthdate' => "1960-10-12", + 'lastName' => 'Depardieu', + 'firstName' => 'Jean', + 'birthdate' => '1960-10-12', 'countryOfBirth' => 'FR', 'nationality' => 'FR', 'center' => 'Center A', - 'maritalStatus' => 'ms_divorce' - ), - array( + 'maritalStatus' => 'ms_divorce', + ], + [ //to have false duplicate person - 'lastName' => "Depardieu", - 'firstName' => "Jeanne", - 'birthdate' => "1966-11-13", + 'lastName' => 'Depardieu', + 'firstName' => 'Jeanne', + 'birthdate' => '1966-11-13', 'countryOfBirth' => 'FR', 'nationality' => 'FR', 'center' => 'Center A', - 'maritalStatus' => 'ms_legalco' - ), + 'maritalStatus' => 'ms_legalco', + ], [ 'lastName' => 'Diallo', - 'firstName' => "Fatoumata Binta" + 'firstName' => 'Fatoumata Binta', ], [ 'lastName' => 'Diallo', @@ -504,7 +209,7 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con ], [ 'lastName' => 'Bah', - 'firstName' => "Fatoumata Binta" + 'firstName' => 'Fatoumata Binta', ], [ 'lastName' => 'Bah', @@ -534,24 +239,320 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con 'lastName' => 'Gaillot', 'firstName' => 'Adèle', ], - ); + ]; -/* - private function addAccompanyingPeriods(Person $person, array $periods, ObjectManager $manager) + public function __construct( + Registry $workflowRegistry, + SocialIssueRepository $socialIssueRepository, + CenterRepository $centerRepository, + CountryRepository $countryRepository, + MaritalStatusRepository $maritalStatusRepository, + ScopeRepository $scopeRepository, + UserRepository $userRepository + ) { + $this->faker = Factory::create('fr_FR'); + $this->faker->addProvider($this); + $this->workflowRegistry = $workflowRegistry; + $this->socialIssueRepository = $socialIssueRepository; + $this->centerRepository = $centerRepository; + $this->countryRepository = $countryRepository; + $this->maritalStatusRepository = $maritalStatusRepository; + $this->loader = new NativeLoader($this->faker); + $this->scopeRepository = $scopeRepository; + $this->userRepository = $userRepository; + } + + public function getOrder() { - foreach ($periods as $period) { + return 10000; + } - echo "adding new past Accompanying Period..\n"; + /** + * @internal This method is public and called by faker as a custom generator + */ + public function getRandomCenter(): Center + { + if (0 === count($this->cacheCenters)) { + $this->cacheCenters = $this->centerRepository->findAll(); + } - /** @var AccompanyingPeriod $accompanyingPeriod - $accompanyingPeriod = new AccompanyingPeriod(new \DateTime($period['from'])); - $accompanyingPeriod - ->setClosingDate(new \DateTime($period['to'])) - ->setRemark($period['remark']) - ; + return $this->cacheCenters[array_rand($this->cacheCenters)]; + } - $person->addAccompanyingPeriod($accompanyingPeriod); + /** + * @internal This method is public and called by faker as a custom generator + * + * @throws Exception + */ + public function getRandomCountry(int $nullPercentage = 20): ?Country + { + if (0 === count($this->cacheCountries)) { + $this->cacheCountries = $this->countryRepository->findAll(); + } + + if (random_int(0, 100) > $nullPercentage) { + return null; + } + + return $this->cacheCountries[array_rand($this->cacheCountries)]; + } + + /** + * @internal This method is public and called by faker as a custom generator + */ + public function getRandomGender(): string + { + return $this->genders[array_rand($this->genders)]; + } + + /** + * @internal This method is public and called by faker as a custom generator + * + * @throws Exception + */ + public function getRandomMaritalStatus(int $nullPercentage = 50): ?MaritalStatus + { + if (0 === count($this->cacheMaritalStatuses)) { + $this->cacheMaritalStatuses = $this->maritalStatusRepository->findAll(); + } + + if (random_int(0, 100) > $nullPercentage) { + return null; + } + + return $this->cacheMaritalStatuses[array_rand($this->cacheMaritalStatuses)]; + } + + public function load(ObjectManager $manager) + { + $this->loadExpectedPeople($manager); + $this->loadRandPeople($manager); + + $manager->flush(); + } + + public function loadExpectedPeople(ObjectManager $manager) + { + echo "loading expected people...\n"; + + foreach ($this->peoples as $personDef) { + $person = $this->createExpectedPerson($personDef); + $this->addAPerson($person, $manager); } } - */ + + protected function loadRandPeople(ObjectManager $manager) + { + echo "loading rand people...\n"; + $persons = $this->createRandPerson()->getObjects(); + + foreach ($persons as $person) { + $this->addAPerson($person, $manager); + } + } + + /** + * create a new person from array data. + * + * @throws Exception + */ + private function addAPerson(Person $person, ObjectManager $manager) + { + $accompanyingPeriod = new AccompanyingPeriod( + (new DateTime()) + ->sub( + new DateInterval('P' . random_int(0, 180) . 'D') + ) + ); + $accompanyingPeriod->setCreatedBy($this->getRandomUser()) + ->setCreatedAt(new DateTimeImmutable('now')); + $person->addAccompanyingPeriod($accompanyingPeriod); + $accompanyingPeriod->addSocialIssue($this->getRandomSocialIssue()); + + if (random_int(0, 10) > 3) { + // always add social scope: + $accompanyingPeriod->addScope($this->getReference('scope_social')); + $origin = $this->getReference(LoadAccompanyingPeriodOrigin::ACCOMPANYING_PERIOD_ORIGIN); + $accompanyingPeriod->setOrigin($origin); + $accompanyingPeriod->setIntensity('regular'); + $accompanyingPeriod->setAddressLocation($this->createAddress()); + $manager->persist($accompanyingPeriod->getAddressLocation()); + $workflow = $this->workflowRegistry->get($accompanyingPeriod); + $workflow->apply($accompanyingPeriod, 'confirm'); + } + + $manager->persist($person); + $manager->persist($accompanyingPeriod); + echo "add person'" . $person->__toString() . "'\n"; + + $this->addReference(self::PERSON . $person->getId(), $person); + } + + private function createAddress(): Address + { + $objectSet = $this->loader->loadData([ + Address::class => [ + 'address' => [ + 'street' => '', + 'streetNumber' => '', + 'validFrom' => '', + 'postCode' => $this->getPostalCode(), + ], + ], + ]); + + return $objectSet->getObjects()['address']; + } + + private function createExpectedPerson($default): Person + { + $person = $this->loader->loadData([ + Person::class => [ + 'person' => [ + 'firstName' => $default['firstName'] ?? '', + 'lastName' => $default['lastName'] ?? '', + 'gender' => '', + 'nationality' => '', + 'center' => '', + 'maritalStatus' => '', + 'birthdate' => '', + 'placeOfBirth' => '', + 'email' => '', + 'countryOfBirth' => '', + ], + ], + ])->getObjects()['person']; + + // force some values + foreach ($default as $key => $value) { + switch ($key) { + case 'birthdate': + $person->setBirthdate(new DateTime($value)); + + break; + + case 'center': + $person->setCenter($this->centerRepository + ->findOneBy(['name' => $value])); + + break; + + case 'countryOfBirth': + case 'nationality': + $country = $this->countryRepository + ->findOneBy(['countryCode' => $value]); + $person->{'set' . ucfirst($key)}($country); + + break; + + case 'maritalStatus': + $person->setMaritalStatus($this->maritalStatusRepository + ->find($value)); + + break; + } + } + + return $person; + } + + private function createRandPerson(): ObjectSet + { + return $this->loader->loadData([ + Person::class => [ + 'persons{1..300}' => [ + 'firstName' => '', + 'lastName' => '', + 'gender' => '', + 'nationality' => '', + 'center' => '', + 'maritalStatus' => '', + 'birthdate' => '', + 'placeOfBirth' => '', + 'email' => '', + 'countryOfBirth' => '', + ], + ], + ]); + } + + private function getPostalCode(): PostalCode + { + $ref = LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)]; + + return $this->getReference($ref); + } + + /** + * Create a random address. + * + * @return Address + */ + private function getRandomAddress() + { + return (new Address()) + ->setStreetAddress1($this->faker->streetAddress) + ->setStreetAddress2( + mt_rand(0, 9) > 5 ? $this->faker->streetAddress : '' + ) + ->setPoint( + mt_rand(0, 9) > 5 ? $this->getRandomPoint() : null + ) + ->setPostcode($this->getReference( + LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] + )) + ->setValidFrom($this->faker->dateTimeBetween('-5 years')); + } + + /** + * Create a random point. + * + * @return Point + */ + private function getRandomPoint() + { + $lonBrussels = 4.35243; + $latBrussels = 50.84676; + $lon = $lonBrussels + 0.01 * mt_rand(-5, 5); + $lat = $latBrussels + 0.01 * mt_rand(-5, 5); + + return Point::fromLonLat($lon, $lat); + } + + private function getRandomSocialIssue(): SocialIssue + { + if (0 === count($this->cacheSocialIssues)) { + $this->cacheSocialIssues = $this->socialIssueRepository->findAll(); + } + + return $this->cacheSocialIssues[array_rand($this->cacheSocialIssues)]; + } + + private function getRandomUser(): User + { + if (0 === count($this->cacheUsers)) { + $this->cacheUsers = $this->userRepository->findAll(); + } + + return $this->cacheUsers[array_rand($this->cacheUsers)]; + } + + /* + private function addAccompanyingPeriods(Person $person, array $periods, ObjectManager $manager) + { + foreach ($periods as $period) { + + echo "adding new past Accompanying Period..\n"; + + /** @var AccompanyingPeriod $accompanyingPeriod + $accompanyingPeriod = new AccompanyingPeriod(new \DateTime($period['from'])); + $accompanyingPeriod + ->setClosingDate(new \DateTime($period['to'])) + ->setRemark($period['remark']) + ; + + $person->addAccompanyingPeriod($accompanyingPeriod); + } + } + */ } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPersonACL.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPersonACL.php index 18f8e5879..7b368bbe8 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPersonACL.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPersonACL.php @@ -1,37 +1,27 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\DataFixtures\ORM; +use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup; +use Chill\MainBundle\Entity\RoleScope; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; +use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Persistence\ObjectManager; -use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup; -use Chill\MainBundle\Entity\RoleScope; -use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; /** * Add a role CHILL_PERSON_UPDATE & CHILL_PERSON_CREATE for all groups except administrative, - * and a role CHILL_PERSON_SEE for administrative - * - * @author Julien Fastré + * and a role CHILL_PERSON_SEE for administrative. */ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface { @@ -40,7 +30,6 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface return 9600; } - public function load(ObjectManager $manager) { foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) { @@ -60,13 +49,13 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface ); $roleScopeUpdate = (new RoleScope()) - ->setRole('CHILL_PERSON_UPDATE') - ->setScope(null); + ->setRole('CHILL_PERSON_UPDATE') + ->setScope(null); $permissionsGroup->addRoleScope($roleScopeUpdate); $roleScopeCreate = (new RoleScope()) - ->setRole('CHILL_PERSON_CREATE') - ->setScope(null); + ->setRole('CHILL_PERSON_CREATE') + ->setScope(null); $permissionsGroup->addRoleScope($roleScopeCreate); $roleScopeDuplicate = (new RoleScope()) @@ -75,13 +64,13 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface $permissionsGroup->addRoleScope($roleScopeDuplicate); $roleScopeList = (new RoleScope()) - ->setRole(PersonVoter::LISTS) - ->setScope(null); + ->setRole(PersonVoter::LISTS) + ->setScope(null); $permissionsGroup->addRoleScope($roleScopeList); $roleScopeStats = (new RoleScope()) - ->setRole(PersonVoter::STATS) - ->setScope(null); + ->setRole(PersonVoter::STATS) + ->setScope(null); $permissionsGroup->addRoleScope($roleScopeStats); $manager->persist($roleScopeUpdate); @@ -89,19 +78,19 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface $manager->persist($roleScopeDuplicate); break; + case 'administrative': printf("Adding CHILL_PERSON_SEE to %s permission group \n", $permissionsGroup->getName()); $roleScopeSee = (new RoleScope()) - ->setRole('CHILL_PERSON_SEE') - ->setScope(null); + ->setRole('CHILL_PERSON_SEE') + ->setScope(null); $permissionsGroup->addRoleScope($roleScopeSee); $manager->persist($roleScopeSee); + break; } - } $manager->flush(); } - } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelations.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelations.php new file mode 100644 index 000000000..e2bb469bc --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelations.php @@ -0,0 +1,62 @@ + ['fr' => 'Mère'], 'reverseTitle' => ['fr' => 'Fille']], + ['title' => ['fr' => 'Mère'], 'reverseTitle' => ['fr' => 'Fils']], + ['title' => ['fr' => 'Père'], 'reverseTitle' => ['fr' => 'Fille']], + ['title' => ['fr' => 'Père'], 'reverseTitle' => ['fr' => 'Fils']], + + ['title' => ['fr' => 'Frère'], 'reverseTitle' => ['fr' => 'Frère']], + ['title' => ['fr' => 'Soeur'], 'reverseTitle' => ['fr' => 'Soeur']], + ['title' => ['fr' => 'Frère'], 'reverseTitle' => ['fr' => 'Soeur']], + + ['title' => ['fr' => 'Demi-frère'], 'reverseTitle' => ['fr' => 'Demi-frère']], + ['title' => ['fr' => 'Demi-soeur'], 'reverseTitle' => ['fr' => 'Demi-soeur']], + ['title' => ['fr' => 'Demi-frère'], 'reverseTitle' => ['fr' => 'Demi-soeur']], + + ['title' => ['fr' => 'Oncle'], 'reverseTitle' => ['fr' => 'Neveu']], + ['title' => ['fr' => 'Oncle'], 'reverseTitle' => ['fr' => 'Nièce']], + ['title' => ['fr' => 'Tante'], 'reverseTitle' => ['fr' => 'Neveu']], + ['title' => ['fr' => 'Tante'], 'reverseTitle' => ['fr' => 'Nièce']], + ]; + + public static function getGroups(): array + { + return ['person_relations']; + } + + public function load(ObjectManager $manager) + { + foreach (self::RELATIONS as $key => $value) { + echo 'Creating a new relation type: relation' . $value['title']['fr'] . 'reverse relation: ' . $value['reverseTitle']['fr'] . "\n"; + $relation = new Relation(); + $relation->setTitle($value['title']) + ->setReverseTitle($value['reverseTitle']); + $manager->persist($relation); + + $this->addReference(self::RELATION_KEY . $key, $relation); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php new file mode 100644 index 000000000..80f3e711d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php @@ -0,0 +1,71 @@ +em = $em; + } + + public function getDependencies() + { + return [ + LoadPeople::class, + LoadRelations::class, + ]; + } + + public function load(ObjectManager $manager) + { + for ($i = 0; 15 > $i; ++$i) { + $user = $this->getRandomUser(); + $date = new DateTimeImmutable(); + $relationship = (new Relationship()) + ->setFromPerson($this->getRandomPerson($this->em)) + ->setToPerson($this->getRandomPerson($this->em)) + ->setRelation($this->getReference(LoadRelations::RELATION_KEY . + random_int(0, count(LoadRelations::RELATIONS) - 1))) + ->setReverse((bool) random_int(0, 1)) + ->setCreatedBy($user) + ->setUpdatedBy($user) + ->setCreatedAt($date) + ->setUpdatedAt($date); + $manager->persist($relationship); + } + + $manager->flush(); + } + + private function getRandomUser(): User + { + $userRef = array_rand(LoadUsers::$refs); + + return $this->getReference($userRef); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialWorkMetadata.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialWorkMetadata.php index f96fb5fc2..4501ef51e 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialWorkMetadata.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialWorkMetadata.php @@ -1,44 +1,48 @@ importer = $importer; } - /** - * @inheritDoc - */ - public function load(ObjectManager $manager) - { - try { - $csv = Reader::createFromPath(__DIR__.'/data/social_work_metadata.csv'); - } catch (Throwable $e) { - throw new Exception('Error while loading CSV.',0, $e); - } - - $csv->setDelimiter(";"); - - $this->importer->import($csv); - } - - /** - * @inheritDoc - */ public function getOrder() { return 9500; } + + public function load(ObjectManager $manager) + { + try { + $csv = Reader::createFromPath(__DIR__ . '/data/social_work_metadata.csv'); + } catch (Throwable $e) { + throw new Exception('Error while loading CSV.', 0, $e); + } + + $csv->setDelimiter(';'); + + $this->importer->import($csv); + } } diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index bc3b798fa..65a45c2e6 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -1,50 +1,40 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\DependencyInjection; -use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\Config\FileLocator; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\DependencyInjection\Loader; -use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Chill\MainBundle\DependencyInjection\MissingBundleException; -use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\PersonBundle\Doctrine\DQL\AddressPart; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Exception; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use function array_key_exists; /** * Class ChillPersonExtension - * Loads and manages your bundle configuration + * Loads and manages your bundle configuration. * * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html} - * @package Chill\PersonBundle\DependencyInjection */ class ChillPersonExtension extends Extension implements PrependExtensionInterface { - /** - * {@inheritDoc} - * @param array $configs - * @param ContainerBuilder $container - * @throws \Exception + * @throws Exception */ public function load(array $configs, ContainerBuilder $container) { @@ -52,19 +42,23 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $config = $this->processConfiguration($configuration, $configs); // set configuration for validation - $container->setParameter('chill_person.validation.birtdate_not_before', - $config['validation']['birthdate_not_after']); + $container->setParameter( + 'chill_person.validation.birtdate_not_before', + $config['validation']['birthdate_not_after'] + ); $this->handlePersonFieldsParameters($container, $config['person_fields']); $this->handleAccompanyingPeriodsFieldsParameters($container, $config['accompanying_periods_fields']); - $container->setParameter('chill_person.allow_multiple_simultaneous_accompanying_periods', - $config['allow_multiple_simultaneous_accompanying_periods']); + $container->setParameter( + 'chill_person.allow_multiple_simultaneous_accompanying_periods', + $config['allow_multiple_simultaneous_accompanying_periods'] + ); // register all configuration in a unique parameter $container->setParameter('chill_person', $config); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); $loader->load('services.yaml'); $loader->load('services/widgets.yaml'); $loader->load('services/exports.yaml'); @@ -85,13 +79,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/serializer.yaml'); $loader->load('services/security.yaml'); $loader->load('services/doctrineEventListener.yaml'); - - // load service advanced search only if configure - if ($config['search']['search_by_phone'] != 'never') { - $loader->load('services/search_by_phone.yaml'); - $container->setParameter('chill_person.search.search_by_phone', - $config['search']['search_by_phone']); - } + $loader->load('services/accompanyingPeriodConsistency.yaml'); if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') { $loader->load('services/exports_accompanying_period.yaml'); @@ -99,70 +87,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac } /** - * @param ContainerBuilder $container - * @param $config - */ - 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) { - switch($key) { - case 'accompanying_period': - $container->setParameter('chill_person.accompanying_period', $value); - break; - default: - $container->setParameter('chill_person.person_fields.'.$key, $value); - break; - } - } - } - - /** - * @param ContainerBuilder $container - * @param $config - */ - private function handleAccompanyingPeriodsFieldsParameters(ContainerBuilder $container, $config) - { - $container->setParameter('chill_person.accompanying_period_fields', $config); - - foreach ($config as $key => $value) { - switch($key) { - case 'enabled': - break; - default: - $container->setParameter('chill_person.accompanying_period_fields.'.$key, $value); - break; - } - } - } - - /** - * @param ContainerBuilder $container - * @throws MissingBundleException - */ - private function declarePersonAsCustomizable (ContainerBuilder $container) - { - $bundles = $container->getParameter('kernel.bundles'); - if (!isset($bundles['ChillCustomFieldsBundle'])) { - throw new MissingBundleException('ChillCustomFieldsBundle'); - } - - $container->prependExtensionConfig('chill_custom_fields', - array('customizables_entities' => - array( - array('class' => 'Chill\PersonBundle\Entity\Person', 'name' => 'PersonEntity') - ) - ) - ); - } - - /** - * @param ContainerBuilder $container * @throws MissingBundleException */ public function prepend(ContainerBuilder $container) @@ -176,147 +100,31 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac //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'] - ), + $twigConfig = [ + 'globals' => [ + 'chill_person' => [ + 'fields' => $config['person_fields'], + ], 'chill_accompanying_periods' => [ - 'fields' => $config['accompanying_periods_fields'] - ] - ), - 'form_themes' => array('ChillPersonBundle:Export:ListPersonFormFields.html.twig') - ); + 'fields' => $config['accompanying_periods_fields'], + ], + ], + 'form_themes' => ['ChillPersonBundle:Export:ListPersonFormFields.html.twig'], + ]; $container->prependExtensionConfig('twig', $twigConfig); - $this-> declarePersonAsCustomizable($container); + $this->declarePersonAsCustomizable($container); //declare routes for person bundle - $container->prependExtensionConfig('chill_main', array( - 'routing' => array( - 'resources' => array( - '@ChillPersonBundle/config/routes.yaml' - ) - ) - )); - } - - protected function prependWorkflows(ContainerBuilder $container) - { - $container->prependExtensionConfig('framework', [ - 'workflows' => [ - 'accompanying_period_lifecycle' => [ - 'type' => 'state_machine', - 'audit_trail' => [ - 'enabled' => true - ], - 'marking_store' => [ - 'type' => 'method', - 'property' => 'step', - ], - 'supports' => [ - 'Chill\PersonBundle\Entity\AccompanyingPeriod' - ], - 'initial_marking' => 'DRAFT', - 'places' => [ - 'DRAFT', - 'CONFIRMED', - ], - 'transitions' => [ - 'confirm' => [ - 'from' => 'DRAFT', - 'to' => 'CONFIRMED' - ], - ], + $container->prependExtensionConfig('chill_main', [ + 'routing' => [ + 'resources' => [ + '@ChillPersonBundle/config/routes.yaml', ], - ] - ]); - - } - - /** - * Add a widget "add a person" on the homepage, automatically - * - * @param \Chill\PersonBundle\DependencyInjection\containerBuilder $container - */ - protected function prependHomepageWidget(containerBuilder $container) - { - $container->prependExtensionConfig('chill_main', array( - 'widgets' => array( - 'homepage' => array( - array( - 'widget_alias' => 'add_person', - 'order' => 2 - ) - ) - ) - )); - } - - /** - * Add role hierarchy. - * - * @param ContainerBuilder $container - */ - protected function prependRoleHierarchy(ContainerBuilder $container) - { - $container->prependExtensionConfig('security', [ - 'role_hierarchy' => [ - PersonVoter::UPDATE => [PersonVoter::SEE], - PersonVoter::CREATE => [PersonVoter::SEE], - PersonVoter::LISTS => [ChillExportVoter::EXPORT], - PersonVoter::STATS => [ChillExportVoter::EXPORT], - // accompanying period - AccompanyingPeriodVoter::SEE_DETAILS => [AccompanyingPeriodVoter::SEE], - AccompanyingPeriodVoter::CREATE => [AccompanyingPeriodVoter::SEE_DETAILS], - AccompanyingPeriodVoter::DELETE => [AccompanyingPeriodVoter::SEE_DETAILS], - AccompanyingPeriodVoter::EDIT => [AccompanyingPeriodVoter::SEE_DETAILS], - // give all ACL for FULL - AccompanyingPeriodVoter::FULL => [ - AccompanyingPeriodVoter::SEE_DETAILS, - AccompanyingPeriodVoter::CREATE, - AccompanyingPeriodVoter::EDIT, - AccompanyingPeriodVoter::DELETE - ] - ] + ], ]); } - /** - * Add DQL function linked with person - * - * @param ContainerBuilder $container - */ - protected function prependDoctrineDQL(ContainerBuilder $container) - { - //add DQL function to ORM (default entity_manager) - - $container->prependExtensionConfig('doctrine', array( - 'orm' => array( - 'dql' => array( - 'string_functions' => array( - 'GET_PERSON_ADDRESS_ADDRESS_ID' => AddressPart\AddressPartAddressId::class, - 'GET_PERSON_ADDRESS_STREET_ADDRESS_1' => AddressPart\AddressPartStreetAddress1::class, - 'GET_PERSON_ADDRESS_STREET_ADDRESS_2' => AddressPart\AddressPartStreetAddress2::class, - 'GET_PERSON_ADDRESS_VALID_FROM' => AddressPart\AddressPartValidFrom::class, - 'GET_PERSON_ADDRESS_POSTCODE_LABEL' => AddressPart\AddressPartPostCodeLabel::class, - 'GET_PERSON_ADDRESS_POSTCODE_CODE' => AddressPart\AddressPartPostCodeCode::class, - 'GET_PERSON_ADDRESS_POSTCODE_ID' => AddressPart\AddressPartPostCodeId::class, - 'GET_PERSON_ADDRESS_COUNTRY_NAME' => AddressPart\AddressPartCountryName::class, - 'GET_PERSON_ADDRESS_COUNTRY_CODE' => AddressPart\AddressPartCountryCode::class, - 'GET_PERSON_ADDRESS_COUNTRY_ID' => AddressPart\AddressPartCountryId::class, - ), - 'numeric_functions' => [ - 'GET_PERSON_ADDRESS_ISNOADDRESS' => AddressPart\AddressPartIsNoAddress::class, - ] - ) - ) - )); - } - - /** - * @param ContainerBuilder $container - */ protected function prependCruds(ContainerBuilder $container) { $container->prependExtensionConfig('chill_main', [ @@ -330,17 +138,17 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'actions' => [ 'index' => [ 'template' => '@ChillPerson/ClosingMotive/index.html.twig', - 'role' => 'ROLE_ADMIN' + 'role' => 'ROLE_ADMIN', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/ClosingMotive/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/ClosingMotive/edit.html.twig', - ] - ] + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\MaritalStatus::class, @@ -353,15 +161,15 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/MaritalStatus/index.html.twig', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/MaritalStatus/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/MaritalStatus/edit.html.twig', - ] - ] + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\SocialIssue::class, @@ -374,15 +182,15 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/SocialIssue/index.html.twig', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/edit.html.twig', - ] - ] + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\SocialAction::class, @@ -395,15 +203,15 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/SocialAction/index.html.twig', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/edit.html.twig', - ] - ] + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\Goal::class, @@ -416,15 +224,15 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/Goal/index.html.twig', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/edit.html.twig', - ] - ] + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\Result::class, @@ -437,15 +245,15 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/Result/index.html.twig', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/edit.html.twig', - ] - ] + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\Evaluation::class, @@ -458,15 +266,15 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/Evaluation/index.html.twig', ], - 'new' => [ + 'new' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/new.html.twig', ], - 'edit' => [ + 'edit' => [ 'role' => 'ROLE_ADMIN', 'template' => '@ChillPerson/SocialWork/edit.html.twig', - ] - ] + ], + ], ], ], 'apis' => [ @@ -486,7 +294,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac Request::METHOD_GET => true, Request::METHOD_PUT => true, Request::METHOD_PATCH => true, - ] + ], ], 'participation' => [ 'methods' => [ @@ -497,8 +305,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ], 'roles' => [ Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, - Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE - ] + Request::METHOD_DELETE => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + ], ], 'resource' => [ 'methods' => [ @@ -509,8 +317,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ], 'roles' => [ Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, - Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE - ] + Request::METHOD_DELETE => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + ], ], 'comment' => [ 'methods' => [ @@ -521,8 +329,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ], 'roles' => [ Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, - Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE - ] + Request::METHOD_DELETE => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + ], ], 'requestor' => [ 'methods' => [ @@ -533,8 +341,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ], 'roles' => [ Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, - Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE - ] + Request::METHOD_DELETE => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + ], ], 'scope' => [ 'methods' => [ @@ -545,8 +353,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ], 'roles' => [ Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, - Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE - ] + Request::METHOD_DELETE => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + ], ], 'socialissue' => [ 'methods' => [ @@ -558,8 +366,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'controller_action' => 'socialIssueApi', 'roles' => [ Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, - Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE - ] + Request::METHOD_DELETE => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + ], ], 'work' => [ 'methods' => [ @@ -572,9 +380,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, Request::METHOD_DELETE => 'ALWAYS_FAILS', - ] + ], ], - 'confirm' => [ 'methods' => [ Request::METHOD_POST => true, @@ -583,9 +390,27 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ], 'roles' => [ Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, - ] + ], ], - ] + 'confidential' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_GET => true, + ], + 'controller_action' => 'toggleConfidentialApi', + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL, + ], + ], + 'findAccompanyingPeriodsByPerson' => [ + 'path' => '/by-person/{person_id}.{_format}', + 'controller_action' => 'getAccompanyingPeriodsByPerson', + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Origin::class, @@ -597,16 +422,16 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true - ] + Request::METHOD_HEAD => true, + ], ], - ] + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\SocialIssue::class, @@ -618,16 +443,16 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true - ] + Request::METHOD_HEAD => true, + ], ], - ] + ], ], [ 'class' => \Chill\PersonBundle\Entity\Person::class, @@ -640,8 +465,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'methods' => [ Request::METHOD_GET => true, Request::METHOD_HEAD => true, - Request::METHOD_POST=> true, - Request::METHOD_PATCH => true + Request::METHOD_POST => true, + Request::METHOD_PATCH => true, ], 'roles' => [ Request::METHOD_GET => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE, @@ -657,9 +482,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac Request::METHOD_GET => false, Request::METHOD_HEAD => false, ], - 'controller_action' => 'personAddressApi' + 'controller_action' => 'personAddressApi', ], - ] + ], ], [ 'class' => \Chill\PersonBundle\Entity\Household\Household::class, @@ -678,7 +503,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'methods' => [ Request::METHOD_GET => true, Request::METHOD_HEAD => true, - ] + ], ], 'address' => [ 'methods' => [ @@ -687,16 +512,16 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac Request::METHOD_GET => false, Request::METHOD_HEAD => false, ], - 'controller_action' => 'householdAddressApi' + 'controller_action' => 'householdAddressApi', ], 'suggestHouseholdByAccompanyingPeriodParticipation' => [ 'path' => '/suggest/by-person/{person_id}/through-accompanying-period-participation.{_format}', 'methods' => [ Request::METHOD_GET => true, Request::METHOD_HEAD => true, - ] - ] - ] + ], + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\SocialAction::class, @@ -714,7 +539,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], '_index' => [ 'methods' => [ @@ -724,7 +549,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], 'listBySocialIssue' => [ 'single-collection' => 'collection', @@ -736,10 +561,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] - - ] - ] + ], + ], + ], ], [ 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork::class, @@ -761,9 +585,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac Request::METHOD_HEAD => 'ROLE_USER', Request::METHOD_PATCH => 'ROLE_USER', Request::METHOD_PUT => 'ROLE_USER', - ] + ], ], - ] + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\Result::class, @@ -780,7 +604,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], '_index' => [ 'methods' => [ @@ -790,7 +614,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], 'by-social-action' => [ 'single-collection' => 'collection', @@ -803,7 +627,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], 'by-goal' => [ 'single-collection' => 'collection', @@ -816,9 +640,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], - ] + ], ], [ 'class' => \Chill\PersonBundle\Entity\SocialWork\Goal::class, @@ -835,7 +659,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], '_index' => [ 'methods' => [ @@ -845,7 +669,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], 'by-social-action' => [ 'single-collection' => 'collection', @@ -858,11 +682,243 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'roles' => [ Request::METHOD_GET => 'ROLE_USER', Request::METHOD_HEAD => 'ROLE_USER', - ] + ], ], - ] + ], ], - ] + [ + 'class' => \Chill\PersonBundle\Entity\Relationships\Relationship::class, + 'controller' => \Chill\PersonBundle\Controller\RelationshipApiController::class, + 'name' => 'relationship_by_person', + 'base_path' => '/api/1.0/relations/relationship', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_entity' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_PATCH => true, + Request::METHOD_DELETE => true, + ], + 'roles' => [ + Request::METHOD_POST => 'ROLE_USER', + Request::METHOD_PATCH => 'ROLE_USER', + Request::METHOD_DELETE => 'ROLE_USER', + ], + ], + 'relationship-by-person' => [ + 'path' => '/by-person/{person_id}.json', + 'controller_action' => 'getRelationshipsByPerson', + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + 'roles' => [ + Request::METHOD_GET => 'ROLE_USER', + Request::METHOD_HEAD => 'ROLE_USER', + ], + ], + ], + ], + [ + 'class' => \Chill\PersonBundle\Entity\Relationships\Relation::class, + 'name' => 'relations', + 'base_path' => '/api/1.0/relations/relation', + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + ], + ], + ], + ], ]); } + + /** + * Add DQL function linked with person. + */ + protected function prependDoctrineDQL(ContainerBuilder $container) + { + //add DQL function to ORM (default entity_manager) + + $container->prependExtensionConfig('doctrine', [ + 'orm' => [ + 'dql' => [ + 'string_functions' => [ + 'GET_PERSON_ADDRESS_ADDRESS_ID' => AddressPart\AddressPartAddressId::class, + 'GET_PERSON_ADDRESS_STREET_ADDRESS_1' => AddressPart\AddressPartStreetAddress1::class, + 'GET_PERSON_ADDRESS_STREET_ADDRESS_2' => AddressPart\AddressPartStreetAddress2::class, + 'GET_PERSON_ADDRESS_VALID_FROM' => AddressPart\AddressPartValidFrom::class, + 'GET_PERSON_ADDRESS_POSTCODE_LABEL' => AddressPart\AddressPartPostCodeLabel::class, + 'GET_PERSON_ADDRESS_POSTCODE_CODE' => AddressPart\AddressPartPostCodeCode::class, + 'GET_PERSON_ADDRESS_POSTCODE_ID' => AddressPart\AddressPartPostCodeId::class, + 'GET_PERSON_ADDRESS_COUNTRY_NAME' => AddressPart\AddressPartCountryName::class, + 'GET_PERSON_ADDRESS_COUNTRY_CODE' => AddressPart\AddressPartCountryCode::class, + 'GET_PERSON_ADDRESS_COUNTRY_ID' => AddressPart\AddressPartCountryId::class, + ], + 'numeric_functions' => [ + 'GET_PERSON_ADDRESS_ISNOADDRESS' => AddressPart\AddressPartIsNoAddress::class, + ], + ], + ], + ]); + } + + /** + * Add a widget "add a person" on the homepage, automatically. + * + * @param \Chill\PersonBundle\DependencyInjection\containerBuilder $container + */ + protected function prependHomepageWidget(containerBuilder $container) + { + $container->prependExtensionConfig('chill_main', [ + 'widgets' => [ + 'homepage' => [ + [ + 'widget_alias' => 'add_person', + 'order' => 2, + ], + ], + ], + ]); + } + + /** + * Add role hierarchy. + */ + protected function prependRoleHierarchy(ContainerBuilder $container) + { + $container->prependExtensionConfig('security', [ + 'role_hierarchy' => [ + PersonVoter::UPDATE => [PersonVoter::SEE], + PersonVoter::CREATE => [PersonVoter::SEE], + PersonVoter::LISTS => [ChillExportVoter::EXPORT], + PersonVoter::STATS => [ChillExportVoter::EXPORT], + // accompanying period + AccompanyingPeriodVoter::SEE_DETAILS => [AccompanyingPeriodVoter::SEE], + AccompanyingPeriodVoter::CREATE => [AccompanyingPeriodVoter::SEE_DETAILS], + AccompanyingPeriodVoter::DELETE => [AccompanyingPeriodVoter::SEE_DETAILS], + AccompanyingPeriodVoter::EDIT => [AccompanyingPeriodVoter::SEE_DETAILS], + // give all ACL for FULL + AccompanyingPeriodVoter::FULL => [ + AccompanyingPeriodVoter::SEE_DETAILS, + AccompanyingPeriodVoter::CREATE, + AccompanyingPeriodVoter::EDIT, + AccompanyingPeriodVoter::DELETE, + ], + ], + ]); + } + + protected function prependWorkflows(ContainerBuilder $container) + { + $container->prependExtensionConfig('framework', [ + 'workflows' => [ + 'accompanying_period_lifecycle' => [ + 'type' => 'state_machine', + 'audit_trail' => [ + 'enabled' => true, + ], + 'marking_store' => [ + 'type' => 'method', + 'property' => 'step', + ], + 'supports' => [ + 'Chill\PersonBundle\Entity\AccompanyingPeriod', + ], + 'initial_marking' => 'DRAFT', + 'places' => [ + 'DRAFT', + 'CONFIRMED', + 'CLOSED', + ], + 'transitions' => [ + 'confirm' => [ + 'from' => 'DRAFT', + 'to' => 'CONFIRMED', + ], + 'close' => [ + 'from' => 'CONFIRMED', + 'to' => 'CLOSED', + ], + ], + ], + ], + ]); + } + + /** + * @throws MissingBundleException + */ + private function declarePersonAsCustomizable(ContainerBuilder $container) + { + $bundles = $container->getParameter('kernel.bundles'); + + if (!isset($bundles['ChillCustomFieldsBundle'])) { + throw new MissingBundleException('ChillCustomFieldsBundle'); + } + + $container->prependExtensionConfig( + 'chill_custom_fields', + ['customizables_entities' => [ + ['class' => 'Chill\PersonBundle\Entity\Person', 'name' => 'PersonEntity'], + ], + ] + ); + } + + /** + * @param $config + */ + private function handleAccompanyingPeriodsFieldsParameters(ContainerBuilder $container, $config) + { + $container->setParameter('chill_person.accompanying_period_fields', $config); + + foreach ($config as $key => $value) { + switch ($key) { + case 'enabled': + break; + + default: + $container->setParameter('chill_person.accompanying_period_fields.' . $key, $value); + + break; + } + } + } + + /** + * @param $config + */ + 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) { + switch ($key) { + case 'accompanying_period': + $container->setParameter('chill_person.accompanying_period', $value); + + break; + + default: + $container->setParameter('chill_person.person_fields.' . $key, $value); + + break; + } + } + } } diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/CompilerPass/AccompanyingPeriodTimelineCompilerPass.php b/src/Bundle/ChillPersonBundle/DependencyInjection/CompilerPass/AccompanyingPeriodTimelineCompilerPass.php index 1ee7782d3..c4f4dbdf3 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/CompilerPass/AccompanyingPeriodTimelineCompilerPass.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/CompilerPass/AccompanyingPeriodTimelineCompilerPass.php @@ -1,29 +1,23 @@ - * - * 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\DependencyInjection\CompilerPass; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; /** - * Remove services which add AccompanyingPeriod to timeline if - * accompanying_periods are set to `hidden` + * Chill is a software for social workers * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use function in_array; + +/** + * Remove services which add AccompanyingPeriod to timeline if + * accompanying_periods are set to `hidden`. */ class AccompanyingPeriodTimelineCompilerPass implements CompilerPassInterface { @@ -33,34 +27,33 @@ class AccompanyingPeriodTimelineCompilerPass implements CompilerPassInterface if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') { return; } - + $definitions = [ 'chill.person.timeline.accompanying_period_opening', - 'chill.person.timeline.accompanying_period_closing' + 'chill.person.timeline.accompanying_period_closing', ]; - - foreach($definitions as $definition) { + + foreach ($definitions as $definition) { $container - ->removeDefinition($definition) - ; + ->removeDefinition($definition); } - + $definition = $container->getDefinition('chill.main.timeline_builder'); - - // we have to remove all methods call, and re-add them if not linked + + // we have to remove all methods call, and re-add them if not linked // to this service $calls = $definition->getMethodCalls(); - - foreach($calls as list($method, $arguments)) { - if ($method !== 'addProvider') { + + foreach ($calls as [$method, $arguments]) { + if ('addProvider' !== $method) { continue; } - + $definition->removeMethodCall('addProvider'); - - if (FALSE === \in_array($arguments[1], $definitions)) { + + if (false === in_array($arguments[1], $definitions, true)) { $definition->addMethodCall($method, $arguments); - } + } } } } diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php index bf2d18922..8a0cab301 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php @@ -1,151 +1,152 @@ getRootNode('cl_chill_person'); $rootNode - ->canBeDisabled() - ->children() - ->arrayNode('search') - ->canBeDisabled() - ->children() - ->enumNode('search_by_phone') - ->values(['always', 'on-domain', 'never']) - ->defaultValue('on-domain') - ->info('enable search by phone. \'always\' show the result ' - . 'on every result. \'on-domain\' will show the result ' - . 'only if the domain is given in the search box. ' - . '\'never\' disable this feature') - ->end() - ->end() //children for 'search', parent = array node 'search' - ->end() // array 'search', parent = children of root - ->arrayNode('validation') - ->canBeDisabled() - ->children() - ->booleanNode('center_required') - ->info('Enable a center for each person entity. If disabled, you must provide your own center provider') - ->defaultValue(true) - ->end() - ->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" ' + ->canBeDisabled() + ->children() + ->arrayNode('validation') + ->canBeDisabled() + ->children() + ->booleanNode('center_required') + ->info('Enable a center for each person entity. If disabled, you must provide your own center provider') + ->defaultValue(true) + ->end() + ->scalarNode('birthdate_not_after') + ->info($this->validationBirthdateNotAfterInfos) + ->defaultValue('P1D') + ->validate() + ->ifTrue(static 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('mobilenumber')) - ->append($this->addFieldNode('contact_info')) - ->append($this->addFieldNode('nationality')) - ->append($this->addFieldNode('country_of_birth')) - ->append($this->addFieldNode('marital_status')) - ->append($this->addFieldNode('spoken_languages')) - ->append($this->addFieldNode('address')) - ->append($this->addFieldNode('accompanying_period')) - ->append($this->addFieldNode('memo')) - ->append($this->addFieldNode('number_of_children')) - ->arrayNode('alt_names') - ->defaultValue([]) - ->arrayPrototype() - ->children() - ->scalarNode('key') - ->isRequired()->cannotBeEmpty() - ->end() - ->arrayNode('labels') - ->children() - ->scalarNode('lang')->isRequired()->cannotBeEmpty() - ->example('fr') - ->end() - ->scalarNode('label')->isRequired()->cannotBeEmpty() - ->example('Nom de jeune fille') - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() //children for 'person_fields', parent = array 'person_fields' - ->end() // person_fields, parent = children of root - ->arrayNode('accompanying_periods_fields') - ->canBeDisabled() - ->children() - ->append($this->addFieldNode('user')) - ->append($this->addFieldNode('createdBy')) - ->append($this->addFieldNode('step')) - ->append($this->addFieldNode('origin')) - ->append($this->addFieldNode('intensity')) - ->append($this->addFieldNode('scopes')) - ->append($this->addFieldNode('requestor')) - ->append($this->addFieldNode('anonymous')) - ->append($this->addFieldNode('emergency')) - ->append($this->addFieldNode('confidential')) - ->end() //children for 'accompanying_person_fields', parent = array 'person_fields' - ->end() // paccompanying_person_fields, parent = children of root - ->booleanNode('allow_multiple_simultaneous_accompanying_periods') - ->info('Can we have more than one simultaneous accompanying period in the same time. Default false.') - ->defaultValue(false) - ->end() - ->end() // children of 'root', parent = root - ; - + ->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('mobilenumber')) + ->append($this->addFieldNode('contact_info')) + ->append($this->addFieldNode('nationality')) + ->append($this->addFieldNode('country_of_birth')) + ->append($this->addFieldNode('marital_status')) + ->append($this->addFieldNode('civility')) + ->append($this->addFieldNode('spoken_languages')) + ->append($this->addFieldNode('address')) + ->append($this->addFieldNode('accompanying_period')) + ->append($this->addFieldNode('memo')) + ->append($this->addFieldNode('number_of_children')) + ->append($this->addFieldNode('acceptEmail')) + ->append($this->addFieldNode('deathdate')) + ->arrayNode('alt_names') + ->defaultValue([]) + ->arrayPrototype() + ->children() + ->scalarNode('key') + ->isRequired()->cannotBeEmpty() + ->end() + ->arrayNode('labels') + ->children() + ->scalarNode('lang')->isRequired()->cannotBeEmpty() + ->example('fr') + ->end() + ->scalarNode('label')->isRequired()->cannotBeEmpty() + ->example('Nom de jeune fille') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() //children for 'person_fields', parent = array 'person_fields' + ->end() // person_fields, parent = children of root + ->arrayNode('accompanying_periods_fields') + ->canBeDisabled() + ->children() + ->append($this->addFieldNode('user')) + ->append($this->addFieldNode('createdBy')) + ->append($this->addFieldNode('step')) + ->append($this->addFieldNode('origin')) + ->append($this->addFieldNode('intensity')) + ->append($this->addFieldNode('scopes')) + ->append($this->addFieldNode('requestor')) + ->append($this->addFieldNode('anonymous')) + ->append($this->addFieldNode('emergency')) + ->append($this->addFieldNode('confidential')) + ->end() //children for 'accompanying_person_fields', parent = array 'person_fields' + ->end() // paccompanying_person_fields, parent = children of root + ->booleanNode('allow_multiple_simultaneous_accompanying_periods') + ->info('Can we have more than one simultaneous accompanying period in the same time. Default false.') + ->defaultValue(false) + ->end() + ->end() // children of 'root', parent = root +; return $treeBuilder; } private function addFieldNode($key) { - $tree = new TreeBuilder($key,'enum'); + $tree = new TreeBuilder($key, 'enum'); $node = $tree->getRootNode($key); - switch($key) { + switch ($key) { case 'accompanying_period': - $info = "If the accompanying periods are shown"; + $info = 'If the accompanying periods are shown'; + break; + default: - $info = "If the field $key must be shown"; + $info = "If the field {$key} must be shown"; + break; } $node - ->values(array('hidden', 'visible')) + ->values(['hidden', 'visible']) ->defaultValue('visible') ->info($info) ->end(); diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart.php index 98e362dfb..14c9fb694 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart.php @@ -1,20 +1,14 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL; use Doctrine\ORM\Query\AST\Functions\FunctionNode; @@ -23,70 +17,64 @@ use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** - * * USAGE GET_ADDRESS_(person.id, :date, 'postcode') where part * should be replace by the part of the address. - * + * * This function return the current address part at the given date, for the * given person (identified by his id) - * - * The aim of this function is to be used within reports * - * @author Julien Fastré + * The aim of this function is to be used within reports */ abstract class AddressPart extends FunctionNode { - public $fields = array( - 'address_id', - 'streetaddress1', - 'streetaddress2', - 'validfrom', - 'postcode_label', - 'postcode_code', - 'postcode_id', - 'country_name', - 'country_code', - 'country_id' - ); - + public $fields = [ + 'address_id', + 'streetaddress1', + 'streetaddress2', + 'validfrom', + 'postcode_label', + 'postcode_code', + 'postcode_id', + 'country_name', + 'country_code', + 'country_id', + ]; + /** - * - * @var \Doctrine\ORM\Query\AST\Node - */ - private $pid; - - /** - * * @var \Doctrine\ORM\Query\AST\Node */ private $date; - + /** - * * @var \Doctrine\ORM\Query\AST\Node */ private $part; - + /** - * return the part of the address - * - * Should be one value of the "public" amongst - * 'address_id', 'streetaddress1', - * 'streetaddress2', 'validfrom', 'postcode_label', 'postcode_code', + * @var \Doctrine\ORM\Query\AST\Node + */ + private $pid; + + /** + * return the part of the address. + * + * Should be one value of the "public" amongst + * 'address_id', 'streetaddress1', + * 'streetaddress2', 'validfrom', 'postcode_label', 'postcode_code', * 'postcode_id', 'country_name', 'country_code', 'country_id', 'isnoaddress' - * + * * @return string */ abstract public function getPart(); - + public function getSql(SqlWalker $sqlWalker) { return sprintf( - 'get_last_address_%s(%s, %s)', + 'get_last_address_%s(%s, %s)', $this->getPart(), $this->pid->dispatch($sqlWalker), $this->date->dispatch($sqlWalker) - ); + ); } public function parse(Parser $parser) diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartAddressId.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartAddressId.php index 2f40796cf..5b9e784f1 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartAddressId.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartAddressId.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartAddressId extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryCode.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryCode.php index e997dbb91..ca130478c 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryCode.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryCode.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartCountryCode extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryId.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryId.php index ab0ab0da2..3672c2a74 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryId.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryId.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartCountryId extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryName.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryName.php index c37902909..b6da85ee4 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryName.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartCountryName.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartCountryName extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartIsNoAddress.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartIsNoAddress.php index bd08bb4df..4390fb6a8 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartIsNoAddress.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartIsNoAddress.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartIsNoAddress extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php index ce792cede..09804b241 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartPostCodeCode extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php index 5400a04a7..bd2536047 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartPostCodeId extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php index ab36a8c46..5edb77e6f 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartPostCodeLabel extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php index c06cf2b07..344b7e3c2 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartStreetAddress1 extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php index 739120e5e..2827016bc 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartStreetAddress2 extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartValidFrom.php b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartValidFrom.php index 4a366bf1a..89ef28606 100644 --- a/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartValidFrom.php +++ b/src/Bundle/ChillPersonBundle/Doctrine/DQL/AddressPart/AddressPartValidFrom.php @@ -1,29 +1,18 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart; -/** - * - * - * @author Julien Fastré - */ class AddressPartValidFrom extends AddressPart { public function getPart() diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 7d05adcc6..5b4293226 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -1,85 +1,97 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Entity; -use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; +use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; +use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\HasCentersInterface; use Chill\MainBundle\Entity\HasScopesInterface; use Chill\MainBundle\Entity\Scope; -use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; +use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\AccompanyingPeriodValidity; +use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ParticipationOverlap; +use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ResourceDuplicateCheck; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use DateTime; +use DateTimeImmutable; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Chill\MainBundle\Entity\User; -use Symfony\Component\Serializer\Annotation\Groups; +use LogicException; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\GroupSequenceProviderInterface; +use UnexpectedValueException; +use function count; +use function in_array; +use const SORT_REGULAR; /** - * AccompanyingPeriod Class + * AccompanyingPeriod Class. * * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period") * @DiscriminatorMap(typeProperty="type", mapping={ - * "accompanying_period"=AccompanyingPeriod::class - * }) + * "accompanying_period": AccompanyingPeriod::class + * }) + * @Assert\GroupSequenceProvider + * @Assert\Expression( + * "this.isConfidential and this.getUser === NULL", + * message="If the accompanying course is confirmed and confidential, a referrer must remain assigned." + * ) + * + * @AccompanyingPeriodValidity(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED}) */ -class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface, - HasScopesInterface, HasCentersInterface +class AccompanyingPeriod implements + GroupSequenceProviderInterface, + HasCentersInterface, + HasScopesInterface, + TrackCreationInterface, + TrackUpdateInterface { + public const INTENSITIES = [self::INTENSITY_OCCASIONAL, self::INTENSITY_REGULAR]; + /** - * Mark an accompanying period as "occasional" + * Mark an accompanying period as "occasional". * * used in INTENSITY */ public const INTENSITY_OCCASIONAL = 'occasional'; /** - * Mark an accompanying period as "regular" + * Mark an accompanying period as "regular". * * used in INTENSITY */ public const INTENSITY_REGULAR = 'regular'; - public const INTENSITIES = [self::INTENSITY_OCCASIONAL, self::INTENSITY_REGULAR]; - /** - * Mark an accompanying period as "draft". + * Mark an accompanying period as "closed". * - * This means that the accompanying period is not yet - * confirmed by the creator + * This means that the accompanying period **is** + * closed by the creator */ - public const STEP_DRAFT = 'DRAFT'; + public const STEP_CLOSED = 'CLOSED'; /** * Mark an accompanying period as "confirmed". @@ -90,173 +102,183 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface public const STEP_CONFIRMED = 'CONFIRMED'; /** - * @var integer + * Mark an accompanying period as "draft". * - * @ORM\Id - * @ORM\Column(name="id", type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - * @Groups({"read"}) + * This means that the accompanying period is not yet + * confirmed by the creator */ - private $id; - - /** - * @var \DateTime - * - * @ORM\Column(type="date") - * @Groups({"read", "write"}) - */ - private $openingDate; - - /** - * @var \DateTime - * - * @ORM\Column(type="date", nullable=true) - * @Groups({"read", "write"}) - */ - private $closingDate = null; - - /** - * @var string - * - * @ORM\Column(type="text") - * @Groups({"read", "write"}) - */ - private $remark = ''; - - /** - * @var Collection - * - * @ORM\OneToMany(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Comment", - * mappedBy="accompanyingPeriod", - * cascade={"persist", "remove"}, - * orphanRemoval=true - * ) - */ - private $comments; + public const STEP_DRAFT = 'DRAFT'; /** * @ORM\ManyToOne( - * targetEntity=Comment::class + * targetEntity=Address::class + * ) + */ + private ?Address $addressLocation = null; + + /** + * @var DateTime + * + * @ORM\Column(type="date", nullable=true) + * @Groups({"read", "write", "docgen:read"}) + * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED}) + * @Assert\GreaterThan(propertyPath="openingDate", groups={AccompanyingPeriod::STEP_CLOSED}) + */ + private ?DateTime $closingDate = null; + + /** + * @var AccompanyingPeriod\ClosingMotive + * + * @ORM\ManyToOne( + * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive") + * @ORM\JoinColumn(nullable=true) + * @Groups({"read", "write"}) + * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED}) + */ + private ?ClosingMotive $closingMotive = null; + + /** + * @ORM\OneToMany(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Comment", + * mappedBy="accompanyingPeriod", + * cascade={"persist", "remove"}, + * orphanRemoval=true + * ) + * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT}) + */ + private Collection $comments; + + /** + * @ORM\Column(type="boolean", options={"default": false}) + * @Groups({"read", "write", "docgen:read"}) + */ + private bool $confidential = false; + + /** + * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) + * @Groups({"docgen:read"}) + */ + private ?DateTimeInterface $createdAt = null; + + /** + * @ORM\ManyToOne(targetEntity=User::class) + * @ORM\JoinColumn(nullable=true) + * @Groups({"read", "docgen:read"}) + */ + private ?User $createdBy = null; + + /** + * @ORM\Column(type="boolean", options={"default": false}) + * @Groups({"read", "write", "docgen:read"}) + */ + private bool $emergency = false; + + /** + * @ORM\Id + * @ORM\Column(name="id", type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + * @Groups({"read", "docgen:read"}) + */ + private ?int $id = null; + + /** + * @ORM\ManyToOne( + * targetEntity=Comment::class * ) * @Groups({"read"}) */ private ?Comment $initialComment = null; - /** - * @var Collection - * - * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, - * mappedBy="accompanyingPeriod", - * cascade={"persist", "refresh", "remove", "merge", "detach"}) - * @Groups({"read"}) - */ - private $participations; - - /** - * @var AccompanyingPeriod\ClosingMotive - * - * @ORM\ManyToOne( - * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive") - * @ORM\JoinColumn(nullable=true) - * @Groups({"read", "write"}) - */ - private $closingMotive = null; - - /** - * @ORM\ManyToOne(targetEntity=User::class) - * @ORM\JoinColumn(nullable=true) - * @Groups({"read", "write"}) - */ - private $user; - - /** - * @ORM\ManyToOne(targetEntity=User::class) - * @ORM\JoinColumn(nullable=true) - * @Groups({"read"}) - */ - private $createdBy; - /** * @var string - * @ORM\Column(type="string", length=32, nullable=true) - * @Groups({"read"}) + * @ORM\Column(type="string", nullable=true) + * @Groups({"read", "write"}) + * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED}) */ - private $step = self::STEP_DRAFT; + private $intensity = self::INTENSITY_OCCASIONAL; + + /** + * @var DateTime + * + * @ORM\Column(type="date") + * @Groups({"read", "write", "docgen:read"}) + */ + private ?DateTime $openingDate = null; /** * @ORM\ManyToOne(targetEntity=Origin::class) * @ORM\JoinColumn(nullable=true) * @Groups({"read", "write"}) + * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED}) */ - private $origin; + private ?Origin $origin = null; /** - * @var string - * @ORM\Column(type="string", nullable=true) + * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, + * mappedBy="accompanyingPeriod", orphanRemoval=true, + * cascade={"persist", "refresh", "remove", "merge", "detach"}) + * @Groups({"read", "docgen:read"}) + * @ParticipationOverlap(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED}) + */ + private Collection $participations; + + /** + * @ORM\ManyToOne( + * targetEntity=Person::class, + * inversedBy="periodLocatedOn" + * ) + */ + private ?Person $personLocation = null; + + /** + * @ORM\Column(type="text") * @Groups({"read", "write"}) */ - private $intensity; + private string $remark = ''; /** - * @var Collection - * @ORM\ManyToMany( - * targetEntity=Scope::class, - * cascade={} - * ) - * @ORM\JoinTable( - * name="accompanying_periods_scopes", - * joinColumns={@ORM\JoinColumn(name="accompanying_period_id", referencedColumnName="id")}, - * inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")} - * ) - * @Groups({"read"}) + * @ORM\Column(type="boolean", options={"default": false}) + * @Groups({"read", "write", "docgen:read"}) */ - private $scopes; + private bool $requestorAnonymous = false; /** * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodRequested") * @ORM\JoinColumn(nullable=true) */ - private $requestorPerson; + private ?Person $requestorPerson = null; /** * @ORM\ManyToOne(targetEntity=ThirdParty::class) * @ORM\JoinColumn(nullable=true) */ - private $requestorThirdParty; + private ?ThirdParty $requestorThirdParty = null; /** - * @var bool - * @ORM\Column(type="boolean", options={"default": false} ) - * @Groups({"read", "write"}) - */ - private $requestorAnonymous = false; - - /** - * @var bool - * @ORM\Column(type="boolean", options={"default": false} ) - * @Groups({"read", "write"}) - */ - private $emergency = false; - - /** - * @var bool - * @ORM\Column(type="boolean", options={"default": false} ) - * @Groups({"read", "write"}) - */ - private $confidential = false; - - /** - * @var Collection - * * @ORM\OneToMany( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Resource", * mappedBy="accompanyingPeriod", * cascade={"persist", "remove"}, * orphanRemoval=true * ) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) + * @ResourceDuplicateCheck(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED, "Default", "default"}) */ - private $resources; + private Collection $resources; + + /** + * @ORM\ManyToMany( + * targetEntity=Scope::class, + * cascade={} + * ) + * @ORM\JoinTable( + * name="accompanying_periods_scopes", + * joinColumns={@ORM\JoinColumn(name="accompanying_period_id", referencedColumnName="id")}, + * inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")} + * ) + * @Groups({"read", "docgen:read"}) + * @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must be associated to at least one scope") + */ + private Collection $scopes; /** * @ORM\ManyToMany( @@ -264,253 +286,62 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * ) * @ORM\JoinTable( * name="chill_person_accompanying_period_social_issues" - * ) - * @Groups({"read"}) + * ) + * @Groups({"read", "docgen:read"}) + * @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must contains at least one social issue") */ private Collection $socialIssues; + /** + * @ORM\Column(type="string", length=32, nullable=true) + * @Groups({"read"}) + */ + private string $step = self::STEP_DRAFT; + /** * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) */ - private \DateTimeInterface $createdAt; + private ?DateTimeInterface $updatedAt = null; /** * @ORM\ManyToOne( - * targetEntity=User::class + * targetEntity=User::class * ) */ - private User $updatedBy; + private ?User $updatedBy = null; /** - * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) + * @ORM\ManyToOne(targetEntity=User::class) + * @ORM\JoinColumn(nullable=true) + * @Groups({"read", "write", "docgen:read"}) */ - private \DateTimeInterface $updatedAt; + private ?User $user = null; /** * @ORM\OneToMany( - * targetEntity=AccompanyingPeriodWork::class, - * mappedBy="accompanyingPeriod" + * targetEntity=AccompanyingPeriodWork::class, + * mappedBy="accompanyingPeriod" * ) * @Assert\Valid(traverse=true) */ private Collection $works; - /** - * @ORM\ManyToOne( - * targetEntity=Person::class, - * inversedBy="periodLocatedOn" - * ) - */ - private ?Person $personLocation = null; - - - /** - * @ORM\ManyToOne( - * targetEntity=Address::class - * ) - */ - private ?Address $addressLocation = null; - /** * AccompanyingPeriod constructor. * - * @param \DateTime $dateOpening + * @param DateTime $dateOpening + * * @uses AccompanyingPeriod::setClosingDate() */ - public function __construct(\DateTime $dateOpening = null) { - $this->setOpeningDate($dateOpening ?? new \DateTime('now')); + public function __construct(?DateTime $dateOpening = null) + { + $this->setOpeningDate($dateOpening ?? new DateTime('now')); $this->participations = new ArrayCollection(); $this->scopes = new ArrayCollection(); $this->socialIssues = new ArrayCollection(); $this->comments = new ArrayCollection(); $this->works = new ArrayCollection(); - } - - /** - * Get id - * - * @return integer - */ - public function getId() - { - return $this->id; - } - - /** - * Set openingDate - * - * @param \DateTime $dateOpening - * @return AccompanyingPeriod - */ - public function setOpeningDate($openingDate) - { - $this->openingDate = $openingDate; - - return $this; - } - - /** - * Get openingDate - * - * @return \DateTime - */ - public function getOpeningDate() - { - return $this->openingDate; - } - - /** - * Set closingDate - * - * For closing a Person file, you should use Person::setClosed instead. - * - * @param \DateTime $dateClosing - * @return AccompanyingPeriod - * - */ - public function setClosingDate($closingDate) - { - $this->closingDate = $closingDate; - - return $this; - } - - /** - * Get closingDate - * - * @return \DateTime - */ - public function getClosingDate(): ?\DateTime - { - return $this->closingDate; - } - - /** - * @return boolean - */ - public function isOpen(): bool - { - if ($this->getOpeningDate() > new \DateTimeImmutable('now')) { - return false; - } - - if ($this->getClosingDate() === null) { - return true; - } - - return false; - } - - public function setRemark(string $remark = null): self - { - $this->remark = (string) $remark; - - return $this; - } - - public function getRemark(): string - { - return $this->remark; - } - - /** - * @Groups({"read"}) - */ - public function getComments(): Collection - { - return $this->comments->filter(function (Comment $c) { - return $c !== $this->initialComment; - }); - } - - public function addComment(Comment $comment): self - { - $this->comments[] = $comment; - $comment->setAccompanyingPeriod($this); - - return $this; - } - - public function removeComment(Comment $comment): void - { - $comment->setAccompanyingPeriod(null); - $this->comments->removeElement($comment); - } - - /** - * @Groups({"write"}) - */ - public function setInitialComment(?Comment $comment = null): self - { - if (NULL !== $this->initialComment) { - $this->removeComment($this->initialComment); - } - if ($comment instanceof Comment) { - $this->addComment($comment); - } - - $this->initialComment = $comment; - - return $this; - } - - /** - * @Groups({"read"}) - */ - public function getInitialComment(): ?Comment - { - return $this->initialComment; - } - - /** - * Get Participations Collection - */ - public function getParticipations(): Collection - { - return $this->participations; - } - - /** - * Get the participation containing a person - */ - public function getParticipationsContainsPerson(Person $person): Collection - { - return $this - ->getParticipations() - ->filter( - static function(AccompanyingPeriodParticipation $participation) use ($person): bool { - return $person === $participation->getPerson(); - } - ); - } - - /** - * Get the opened participation containing a person - * - * "Open" means that the closed date is NULL - */ - public function getOpenParticipationContainsPerson(Person $person): ?AccompanyingPeriodParticipation - { - $collection = $this - ->getParticipationsContainsPerson($person) - ->filter( - static function(AccompanyingPeriodParticipation $participation): bool { - return null === $participation->getEndDate(); - } - ); - - return $collection->count() > 0 ? $collection->first() : NULL; - } - - public function getOPenParticipations(): Collection - { - return $this - ->getParticipations() - ->filter( - static function(AccompanyingPeriodParticipation $participation): bool { - return null === $participation->getEndDate(); - } - ); + $this->resources = new ArrayCollection(); } /** @@ -522,104 +353,84 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * Participation y , Participation z, ... * ] * ], - * ] - * + * ]. */ public function actualParticipationsByHousehold(): array { - $participations = $this->getOPenParticipations()->toArray(); + $participations = $this->getOpenParticipations()->toArray(); $households = []; + foreach ($participations as $p) { $households[] = $p->getPerson()->getCurrentHousehold(); } $households = array_unique($households, SORT_REGULAR); $array = []; + foreach ($households as $household) { $members = []; + foreach ($participations as $p) { - if ($household === $p->getPerson()->getCurrentHousehold()) { + if ($p->getPerson()->getCurrentHousehold() === $household) { $members[] = array_shift($participations); } else { $participations[] = array_shift($participations); } } - $array[] = [ 'household' => $household, 'members' => $members ]; + $array[] = ['household' => $household, 'members' => $members]; } return $array; } - /** - * Return true if the accompanying period contains a person. - * - * **Note**: this participation can be opened or not. - */ - public function containsPerson(Person $person): bool + public function addComment(Comment $comment): self { - return $this->getParticipationsContainsPerson($person)->count() > 0; + $this->comments[] = $comment; + $comment->setAccompanyingPeriod($this); + + return $this; } - /** - * Open a new participation for a person - */ - public function createParticipationFor(Person $person): AccompanyingPeriodParticipation + public function addPerson(?Person $person = null): self { - $participation = new AccompanyingPeriodParticipation($this, $person); - $this->participations[] = $participation; - - return $participation; - } - - public function addPerson(Person $person = null): self - { - if (NULL !== $person) { + if (null !== $person) { $this->createParticipationFor($person); } return $this; } - /** - * Close a participation for a person - * - * Search for the person's participation and set the end date at - * 'now'. - * - * @return void - */ - public function closeParticipationFor($person): ?AccompanyingPeriodParticipation + public function addResource(Resource $resource): self { - $participation = $this->getOpenParticipationContainsPerson($person); - - if ($participation instanceof AccompanyingPeriodParticipation) { - $participation->setEndDate(new \DateTimeImmutable('now')); - } - - return $participation; - } - - - /** - * Remove Person - */ - public function removePerson(Person $person): self - { - $this->closeParticipationFor($person); + $resource->setAccompanyingPeriod($this); + $this->resources[] = $resource; return $this; } - - public function getClosingMotive(): ?ClosingMotive + public function addScope(Scope $scope): self { - return $this->closingMotive; + if (!$this->scopes->contains($scope)) { + $this->scopes[] = $scope; + } + + return $this; } - public function setClosingMotive(ClosingMotive $closingMotive = null): self + public function addSocialIssue(SocialIssue $socialIssue): self { - $this->closingMotive = $closingMotive; + if (!$this->socialIssues->contains($socialIssue)) { + $this->socialIssues[] = $socialIssue; + } + + return $this; + } + + public function addWork(AccompanyingPeriodWork $work): self + { + $this->works[] = $work; + $work->setAccompanyingPeriod($this); return $this; } @@ -638,8 +449,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface $participation = $this->getOpenParticipationContainsPerson($person); - if (null === $participation) - { + if (null === $participation) { return false; } @@ -649,57 +459,232 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface } /** - */ - public function reOpen(): void - { - $this->setClosingDate(null); - $this->setClosingMotive(null); - } - - /** - * Validation function - */ - public function isDateConsistent(ExecutionContextInterface $context) - { - if ($this->isOpen()) { - return; - } - - if (! $this->isClosingAfterOpening()) { - $context->buildViolation('The date of closing is before the date of opening') - ->atPath('dateClosing') - ->addViolation(); - } - } - - /** - * Returns true if the closing date is after the opening date. + * Close a participation for a person. * - * @return boolean + * Search for the person's participation and set the end date at + * 'now'. + * + * @param mixed $person + * + * @return void */ - public function isClosingAfterOpening(): bool + public function closeParticipationFor($person): ?AccompanyingPeriodParticipation { - if (null === $this->getClosingDate()) { - return false; - } - $diff = $this->getOpeningDate()->diff($this->getClosingDate()); + $participation = $this->getOpenParticipationContainsPerson($person); - if ($diff->invert === 0) { - return true; + if ($participation instanceof AccompanyingPeriodParticipation) { + $participation->setEndDate(new DateTime('now')); } - return false; + + return $participation; } - function getUser(): ?User + /** + * Return true if the accompanying period contains a person. + * + * **Note**: this participation can be opened or not. + */ + public function containsPerson(Person $person): bool { - return $this->user; + return $this->getParticipationsContainsPerson($person)->count() > 0; } - function setUser(User $user): self + /** + * Open a new participation for a person. + */ + public function createParticipationFor(Person $person): AccompanyingPeriodParticipation { - $this->user = $user; + $participation = new AccompanyingPeriodParticipation($this, $person); + $this->participations[] = $participation; - return $this; + return $participation; + } + + public function getAddressLocation(): ?Address + { + return $this->addressLocation; + } + + /** + * Get a list of person which have an adresse available for a valid location. + * + * @return Collection|Person[] + */ + public function getAvailablePersonLocation(): Collection + { + return $this->getOpenParticipations() + ->filter(static function (AccompanyingPeriodParticipation $p) { + return $p->getPerson()->hasCurrentHouseholdAddress(); + }) + ->map(static function (AccompanyingPeriodParticipation $p) { + return $p->getPerson(); + }); + } + + public function getCenter(): ?Center + { + if (count($this->getPersons()) === 0) { + return null; + } + + return $this->getPersons()->first()->getCenter(); + } + + public function getCenters(): ?iterable + { + foreach ($this->getPersons() as $person) { + if (!in_array($person->getCenter(), $centers ?? [], true) + && null !== $person->getCenter()) { + $centers[] = $person->getCenter(); + } + } + + return $centers ?? null; + } + + /** + * Get closingDate. + * + * @return DateTime + */ + public function getClosingDate(): ?DateTime + { + return $this->closingDate; + } + + public function getClosingMotive(): ?ClosingMotive + { + return $this->closingMotive; + } + + /** + * @Groups({"read"}) + */ + public function getComments(): Collection + { + return $this->comments->filter(function (Comment $c) { + return $c !== $this->initialComment; + }); + } + + public function getCreatedAt(): ?DateTime + { + return $this->createdAt; + } + + public function getCreatedBy(): ?User + { + return $this->createdBy; + } + + /** + * @Groups({"docgen:read"}) + */ + public function getCurrentParticipations(): Collection + { + return $this->getOpenParticipations(); + } + + public function getGroupSequence() + { + if ($this->getStep() === self::STEP_DRAFT) { + return [[self::STEP_DRAFT]]; + } + + if ($this->getStep() === self::STEP_CONFIRMED) { + return [[self::STEP_DRAFT, self::STEP_CONFIRMED]]; + } + + throw new LogicException('no validation group permitted with this step'); + } + + public function getId(): ?int + { + return $this->id; + } + + /** + * @Groups({"read"}) + */ + public function getInitialComment(): ?Comment + { + return $this->initialComment; + } + + public function getIntensity(): ?string + { + return $this->intensity; + } + + /** + * Get the location, taking precedence into account. + * + * @Groups({"read"}) + */ + public function getLocation(?DateTimeImmutable $at = null): ?Address + { + if ($this->getPersonLocation() instanceof Person) { + return $this->getPersonLocation()->getCurrentPersonAddress(); + } + + return $this->getAddressLocation(); + } + + /** + * Get where the location is. + * + * @Groups({"read"}) + */ + public function getLocationStatus(): string + { + if ($this->getPersonLocation() instanceof Person) { + return 'person'; + } + + if ($this->getAddressLocation() instanceof Address) { + return 'address'; + } + + return 'none'; + } + + /** + * Get openingDate. + * + * @return DateTime + */ + public function getOpeningDate(): ?DateTime + { + return $this->openingDate; + } + + /** + * Get the opened participation containing a person. + * + * "Open" means that the closed date is NULL + */ + public function getOpenParticipationContainsPerson(Person $person): ?AccompanyingPeriodParticipation + { + $collection = $this + ->getParticipationsContainsPerson($person) + ->filter( + static function (AccompanyingPeriodParticipation $participation): bool { + return null === $participation->getEndDate(); + } + ); + + return $collection->count() > 0 ? $collection->first() : null; + } + + public function getOpenParticipations(): Collection + { + return $this + ->getParticipations() + ->filter( + static function (AccompanyingPeriodParticipation $participation): bool { + return null === $participation->getEndDate(); + } + ); } public function getOrigin(): ?Origin @@ -707,35 +692,92 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $this->origin; } - public function setOrigin(Origin $origin): self + /** + * Get Participations Collection. + */ + public function getParticipations(): Collection { - $this->origin = $origin; - - return $this; + return $this->participations; } - public function getRequestorPerson(): ?Person + /** + * Get the participation containing a person. + */ + public function getParticipationsContainsPerson(Person $person): Collection { - return $this->requestorPerson; + return $this + ->getParticipations() + ->filter( + static function (AccompanyingPeriodParticipation $participation) use ($person): bool { + return $participation->getPerson() === $person; + } + ); } - private function setRequestorPerson(Person $requestorPerson = null): self + /** + * @Groups({"read"}) + */ + public function getPersonLocation(): ?Person { - $this->requestorPerson = $requestorPerson; - - return $this; + return $this->personLocation; } - public function getRequestorThirdParty(): ?ThirdParty + /** + * Get a list of all persons which are participating to this course. + * + * @psalm-return Collection + */ + public function getPersons(): Collection { - return $this->requestorThirdParty; + return $this + ->participations + ->map( + static function (AccompanyingPeriodParticipation $participation): Person { + return $participation->getPerson(); + } + ); } - private function setRequestorThirdParty(ThirdParty $requestorThirdParty = null): self + /** + * @return Collection|SocialAction[] All the descendant social actions of all + * the descendants of the entity + */ + public function getRecursiveSocialActions(): Collection { - $this->requestorThirdParty = $requestorThirdParty; + $recursiveSocialActions = new ArrayCollection(); - return $this; + foreach ($this->socialIssues as $socialIssue) { + foreach ($socialIssue->getRecursiveSocialActions() as $descendant) { + if (!$recursiveSocialActions->contains($descendant)) { + $recursiveSocialActions->add($descendant); + } + } + } + + return $recursiveSocialActions; + } + + /** + * @return Collection|SocialIssues[] All social issues and their descendants + */ + public function getRecursiveSocialIssues(): Collection + { + $recursiveSocialIssues = new ArrayCollection(); + + foreach ($this->socialIssues as $socialIssue) { + foreach ($socialIssue->getDescendantsWithThis() as $descendant) { + if (!$recursiveSocialIssues->contains($descendant)) { + $recursiveSocialIssues->add($descendant); + } + } + } + + return $recursiveSocialIssues; + } + + public function getRemark(): string + { + return $this->remark; } /** @@ -747,131 +789,30 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $this->requestorPerson ?? $this->requestorThirdParty; } - /** - * Set a requestor - * - * The requestor is either an instance of ThirdParty, or an - * instance of Person - * - * @param $requestor Person|ThirdParty - * @return self - * @throw UnexpectedValueException if the requestor is not a Person or ThirdParty - * @Groups({"write"}) + * @return string 'person' if requestor is an instanceof @see{Person::class}, 'thirdparty' if this is an instanceof @see{ThirdParty::class}, or 'none' */ - public function setRequestor($requestor): self + public function getRequestorKind(): string { - if ($requestor instanceof Person) { - $this->setRequestorThirdParty(NULL); - $this->setRequestorPerson($requestor); - } elseif ($requestor instanceof ThirdParty) { - $this->setRequestorThirdParty($requestor); - $this->setRequestorPerson(NULL); - } elseif (NULL === $requestor) { - $this->setRequestorPerson(NULL); - $this->setRequestorThirdParty(NULL); - } else { - throw new \UnexpectedValueException("requestor is not an instance of Person or ThirdParty"); + if ($this->getRequestor() instanceof ThirdParty) { + return 'thirdparty'; } - return $this; - } - - - public function isRequestorAnonymous(): bool - { - return $this->requestorAnonymous; - } - - public function setRequestorAnonymous(bool $requestorAnonymous): self - { - $this->requestorAnonymous = $requestorAnonymous; - - return $this; - } - - public function isEmergency(): bool - { - return $this->emergency; - } - - public function setEmergency(bool $emergency): self - { - $this->emergency = $emergency; - - return $this; - } - - public function isConfidential(): bool - { - return $this->confidential; - } - - public function setConfidential(bool $confidential): self - { - $this->confidential = $confidential; - - return $this; - } - - public function getCreatedBy(): ?User - { - return $this->createdBy; - } - - public function setCreatedBy(User $createdBy): self - { - $this->createdBy = $createdBy; - - return $this; - } - - public function getStep(): ?string - { - return $this->step; - } - - public function setStep(string $step): self - { - $this->step = $step; - - return $this; - } - - public function getIntensity(): ?string - { - return $this->intensity; - } - - public function setIntensity(string $intensity): self - { - $this->intensity = $intensity; - - return $this; - } - - /** - * @return iterable|Collection - */ - public function getScopes(): Collection - { - return $this->scopes; - } - - - - public function addScope(Scope $scope): self - { - if (!$this->scopes->contains($scope)) { - $this->scopes[] = $scope; + if ($this->getRequestor() instanceof Person) { + return 'person'; } - return $this; + return 'none'; } - public function removeScope(Scope $scope): void + public function getRequestorPerson(): ?Person { - $this->scopes->removeElement($scope); + return $this->requestorPerson; + } + + public function getRequestorThirdParty(): ?ThirdParty + { + return $this->requestorThirdParty; } public function getResources(): Collection @@ -879,18 +820,12 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $this->resources; } - public function addResource(Resource $resource): self + /** + * @return Collection|iterable + */ + public function getScopes(): Collection { - $resource->setAccompanyingPeriod($this); - $this->resources[] = $resource; - - return $this; - } - - public function removeResource(Resource $resource): void - { - $resource->setAccompanyingPeriod(null); - $this->resources->removeElement($resource); + return $this->scopes; } public function getSocialIssues(): Collection @@ -898,92 +833,14 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $this->socialIssues; } - public function addSocialIssue(SocialIssue $socialIssue): self + public function getStep(): ?string { - if (!$this->socialIssues->contains($socialIssue)) { - $this->socialIssues[] = $socialIssue; - } - - return $this; + return $this->step; } - public function removeSocialIssue(SocialIssue $socialIssue): void + public function getUser(): ?User { - $this->socialIssues->removeElement($socialIssue); - } - - /** - * @return Collection|SocialIssues[] All social issues and their descendants - */ - public function getRecursiveSocialIssues(): Collection - { - $recursiveSocialIssues = new ArrayCollection(); - - foreach( $this->socialIssues as $socialIssue) { - foreach ($socialIssue->getDescendantsWithThis() as $descendant) { - if(! $recursiveSocialIssues->contains($descendant)) { - $recursiveSocialIssues->add($descendant); - } - } - } - return $recursiveSocialIssues; - } - - - /** - * @return Collection|SocialAction[] All the descendant social actions of all - * the descendants of the entity - */ - public function getRecursiveSocialActions(): Collection - { - $recursiveSocialActions = new ArrayCollection(); - - foreach( $this->socialIssues as $socialIssue) { - foreach ($socialIssue->getRecursiveSocialActions() as $descendant) { - if(! $recursiveSocialActions->contains($descendant)) { - $recursiveSocialActions->add($descendant); - } - } - } - - return $recursiveSocialActions; - } - - /** - * Get a list of all persons which are participating to this course - * - * @psalm-return Collection - */ - public function getPersons(): Collection - { - return $this - ->participations - ->map( - static function(AccompanyingPeriodParticipation $participation): Person { - return $participation->getPerson(); - } - ); - } - - public function setCreatedAt(\DateTimeInterface $datetime): self - { - $this->createdAt = $datetime; - - return $this; - } - - public function setUpdatedBy(User $user): self - { - $this->updatedBy = $user; - - return $this; - } - - public function setUpdatedAt(\DateTimeInterface $datetime): self - { - $this->updatedAt = $datetime; - - return $this; + return $this->user; } /** @@ -994,14 +851,107 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $this->works; } - public function addWork(AccompanyingPeriodWork $work): self + /** + * Returns true if the closing date is after the opening date. + */ + public function isClosingAfterOpening(): bool { - $this->works[] = $work; - $work->setAccompanyingPeriod($this); + if (null === $this->getClosingDate()) { + return false; + } + $diff = $this->getOpeningDate()->diff($this->getClosingDate()); + + if (0 === $diff->invert) { + return true; + } + + return false; + } + + public function isConfidential(): bool + { + return $this->confidential; + } + + /** + * Validation function. + */ + public function isDateConsistent(ExecutionContextInterface $context) + { + if ($this->isOpen()) { + return; + } + + if (!$this->isClosingAfterOpening()) { + $context->buildViolation('The date of closing is before the date of opening') + ->atPath('dateClosing') + ->addViolation(); + } + } + + public function isEmergency(): bool + { + return $this->emergency; + } + + public function isOpen(): bool + { + if ($this->getOpeningDate() > new DateTimeImmutable('now')) { + return false; + } + + if ($this->getClosingDate() === null) { + return true; + } + + return false; + } + + public function isRequestorAnonymous(): bool + { + return $this->requestorAnonymous; + } + + public function removeComment(Comment $comment): void + { + $comment->setAccompanyingPeriod(null); + $this->comments->removeElement($comment); + } + + /** + * Remove Participation. + */ + public function removeParticipation(AccompanyingPeriodParticipation $participation) + { + $participation->setAccompanyingPeriod(null); + } + + /** + * Remove Person. + */ + public function removePerson(Person $person): self + { + $this->closeParticipationFor($person); return $this; } + public function removeResource(Resource $resource): void + { + $resource->setAccompanyingPeriod(null); + $this->resources->removeElement($resource); + } + + public function removeScope(Scope $scope): void + { + $this->scopes->removeElement($scope); + } + + public function removeSocialIssue(SocialIssue $socialIssue): void + { + $this->socialIssues->removeElement($socialIssue); + } + public function removeWork(AccompanyingPeriodWork $work): self { $this->work->removeElement($work); @@ -1010,9 +960,10 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function getAddressLocation(): ?Address + public function reOpen(): void { - return $this->addressLocation; + $this->setClosingDate(null); + $this->setClosingMotive(null); } /** @@ -1026,27 +977,100 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface } /** - * @Groups({"read"}) + * Set closingDate. + * + * For closing a Person file, you should use Person::setClosed instead. + * + * @param mixed $closingDate + * + * @return AccompanyingPeriod */ - public function getPersonLocation(): ?Person + public function setClosingDate($closingDate) { - return $this->personLocation; + $this->closingDate = $closingDate; + + return $this; + } + + public function setClosingMotive(?ClosingMotive $closingMotive = null): self + { + $this->closingMotive = $closingMotive; + + return $this; + } + + public function setConfidential(bool $confidential): self + { + $this->confidential = $confidential; + + return $this; + } + + public function setCreatedAt(DateTimeInterface $datetime): self + { + $this->createdAt = $datetime; + + return $this; + } + + public function setCreatedBy(User $createdBy): self + { + $this->createdBy = $createdBy; + + return $this; + } + + public function setEmergency(bool $emergency): self + { + $this->emergency = $emergency; + + return $this; } /** - * Get a list of person which have an adresse available for a valid location - * - * @return Collection|Person[] + * @Groups({"write"}) */ - public function getAvailablePersonLocation(): Collection + public function setInitialComment(?Comment $comment = null): self { - return $this->getOPenParticipations() - ->filter(function(AccompanyingPeriodParticipation $p) { - return $p->getPerson()->hasCurrentHouseholdAddress(); - }) - ->map(function(AccompanyingPeriodParticipation $p) { - return $p->getPerson(); - }); + if (null !== $this->initialComment) { + $this->removeComment($this->initialComment); + } + + if ($comment instanceof Comment) { + $this->addComment($comment); + } + + $this->initialComment = $comment; + + return $this; + } + + public function setIntensity(string $intensity): self + { + $this->intensity = $intensity; + + return $this; + } + + /** + * Set openingDate. + * + * @param mixed $openingDate + * + * @return AccompanyingPeriod + */ + public function setOpeningDate($openingDate) + { + $this->openingDate = $openingDate; + + return $this; + } + + public function setOrigin(Origin $origin): self + { + $this->origin = $origin; + + return $this; } /** @@ -1059,45 +1083,87 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $this; } - /** - * Get the location, taking precedence into account - * - * @Groups({"read"}) - */ - public function getLocation(\DateTimeImmutable $at = null): ?Address + public function setRemark(?string $remark = null): self { - if ($this->getPersonLocation() instanceof Person) { - return $this->getPersonLocation()->getCurrentHouseholdAddress($at); - } + $this->remark = (string) $remark; - return $this->getAddressLocation(); + return $this; } /** - * Get where the location is + * Set a requestor. * - * @Groups({"read"}) + * The requestor is either an instance of ThirdParty, or an + * instance of Person + * + * @param $requestor Person|ThirdParty + * @throw UnexpectedValueException if the requestor is not a Person or ThirdParty + * @Groups({"write"}) */ - public function getLocationStatus(): string + public function setRequestor($requestor): self { - if ($this->getPersonLocation() instanceof Person) { - return 'person'; - } elseif ($this->getAddressLocation() instanceof Address) { - return 'address'; + if ($requestor instanceof Person) { + $this->setRequestorThirdParty(null); + $this->setRequestorPerson($requestor); + } elseif ($requestor instanceof ThirdParty) { + $this->setRequestorThirdParty($requestor); + $this->setRequestorPerson(null); + } elseif (null === $requestor) { + $this->setRequestorPerson(null); + $this->setRequestorThirdParty(null); } else { - return 'none'; + throw new UnexpectedValueException('requestor is not an instance of Person or ThirdParty'); } + + return $this; } - public function getCenters(): ?iterable + public function setRequestorAnonymous(bool $requestorAnonymous): self { - foreach ($this->getPersons() as $person) { - if (!in_array($person->getCenter(), $centers ?? []) - && NULL !== $person->getCenter()) { - $centers[] = $person->getCenter(); - } - } + $this->requestorAnonymous = $requestorAnonymous; - return $centers ?? null; + return $this; + } + + public function setStep(string $step): self + { + $this->step = $step; + + return $this; + } + + public function setUpdatedAt(DateTimeInterface $datetime): self + { + $this->updatedAt = $datetime; + + return $this; + } + + public function setUpdatedBy(User $user): self + { + $this->updatedBy = $user; + + return $this; + } + + public function setUser(User $user): self + { + $this->user = $user; + + return $this; + } + + private function setRequestorPerson(?Person $requestorPerson = null): self + { + $this->requestorPerson = $requestorPerson; + + return $this; + } + + private function setRequestorThirdParty(?ThirdParty $requestorThirdParty = null): self + { + $this->requestorThirdParty = $requestorThirdParty; + + return $this; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index 39aaf309b..df4a5f5de 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -1,50 +1,47 @@ accompanyingPeriodWorkEvaluations = new ArrayCollection(); } - public function getId(): ?int + public function addAccompanyingPeriodWorkEvaluation(AccompanyingPeriodWorkEvaluation $evaluation): self { - return $this->id; - } - - public function getNote(): ?string - { - return $this->note; - } - - public function setNote(string $note): self - { - $this->note = $note; - - return $this; - } - - public function getAccompanyingPeriod(): ?AccompanyingPeriod - { - return $this->accompanyingPeriod; - } - - /** - * Internal: you should use `$accompanyingPeriod->removeWork($work);` or - * `$accompanyingPeriod->addWork($work);` - */ - public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self - { - if ($this->accompanyingPeriod instanceof AccompanyingPeriod && - $accompanyingPeriod !== $this->accompanyingPeriod) { - throw new \LogicException("A work cannot change accompanyingPeriod"); + if (!$this->accompanyingPeriodWorkEvaluations->contains($evaluation)) { + $this->accompanyingPeriodWorkEvaluations[] = $evaluation; + $evaluation->setAccompanyingPeriodWork($this); } - $this->accompanyingPeriod = $accompanyingPeriod; - return $this; } - public function getSocialAction(): ?SocialAction - { - return $this->socialAction; - } - - public function setSocialAction(?SocialAction $socialAction): self - { - $this->socialAction = $socialAction; - - return $this; - } - - public function getCreatedAt(): ?\DateTimeImmutable - { - return $this->createdAt; - } - - public function setCreatedAt(\DateTimeInterface $createdAt): self - { - $this->createdAt = $createdAt; - - return $this; - } - - public function getCreatedBy(): ?User - { - return $this->createdBy; - } - - public function setCreatedBy(?User $createdBy): self - { - $this->createdBy = $createdBy; - - return $this; - } - - public function setUpdatedBy(User $user): TrackUpdateInterface - { - $this->updatedBy = $user; - - return $this; - } - - public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface - { - $this->updatedAt = $datetime; - - return $this; - } - - public function getUpdatedAt(): ?\DateTimeImmutable - { - return $this->updatedAt; - } - - public function getUpdatedBy(): ?User - { - return $this->updatedBy; - } - - public function getStartDate(): ?\DateTimeInterface - { - return $this->startDate; - } - - public function setStartDate(\DateTimeInterface $startDate): self - { - $this->startDate = $startDate; - - return $this; - } - - public function getEndDate(): ?\DateTimeInterface - { - return $this->endDate; - } - - public function setEndDate(?\DateTimeInterface $endDate = null): self - { - $this->endDate = $endDate; - - return $this; - } - - public function getHandlingThierParty(): ?ThirdParty - { - return $this->handlingThierParty; - } - - public function setHandlingThierParty(?ThirdParty $handlingThierParty): self - { - $this->handlingThierParty = $handlingThierParty; - - return $this; - } - - public function getCreatedAutomatically(): ?bool - { - return $this->createdAutomatically; - } - - public function setCreatedAutomatically(bool $createdAutomatically): self - { - $this->createdAutomatically = $createdAutomatically; - - return $this; - } - - public function getCreatedAutomaticallyReason(): ?string - { - return $this->createdAutomaticallyReason; - } - - public function setCreatedAutomaticallyReason(string $createdAutomaticallyReason): self - { - $this->createdAutomaticallyReason = $createdAutomaticallyReason; - - return $this; - } - - /** - * @return Collection|AccompanyingPeriodWorkGoal[] - */ - public function getGoals(): Collection - { - return $this->goals; - } - public function addGoal(AccompanyingPeriodWorkGoal $goal): self { if (!$this->goals->contains($goal)) { @@ -360,26 +216,15 @@ use Symfony\Component\Validator\Constraints as Assert; return $this; } - public function removeGoal(AccompanyingPeriodWorkGoal $goal): self + public function addPerson(Person $person): self { - if ($this->goals->removeElement($goal)) { - // set the owning side to null (unless already changed) - if ($goal->getAccompanyingPeriodWork() === $this) { - $goal->setAccompanyingPeriodWork(null); - } + if (!$this->persons->contains($person)) { + $this->persons[] = $person; } return $this; } - /** - * @return Collection|Result[] - */ - public function getResults(): Collection - { - return $this->results; - } - public function addResult(Result $result): self { if (!$this->results->contains($result)) { @@ -389,13 +234,104 @@ use Symfony\Component\Validator\Constraints as Assert; return $this; } - public function removeResult(Result $result): self + public function addThirdParty(ThirdParty $thirdParty): self { - $this->results->removeElement($result); + if (!$this->thirdParties->contains($thirdParty)) { + $this->thirdParties[] = $thirdParty; + } return $this; } + public function getAccompanyingPeriod(): ?AccompanyingPeriod + { + return $this->accompanyingPeriod; + } + + /** + * @return Collection + */ + public function getAccompanyingPeriodWorkEvaluations() + { + return $this->accompanyingPeriodWorkEvaluations; + } + + public function getCreatedAt(): ?DateTimeImmutable + { + return $this->createdAt; + } + + public function getCreatedAutomatically(): ?bool + { + return $this->createdAutomatically; + } + + public function getCreatedAutomaticallyReason(): ?string + { + return $this->createdAutomaticallyReason; + } + + public function getCreatedBy(): ?User + { + return $this->createdBy; + } + + public function getEndDate(): ?DateTimeInterface + { + return $this->endDate; + } + + /** + * @return AccompanyingPeriodWorkGoal[]|Collection + */ + public function getGoals(): Collection + { + return $this->goals; + } + + public function getHandlingThierParty(): ?ThirdParty + { + return $this->handlingThierParty; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getNote(): ?string + { + return $this->note; + } + + public function getPersons(): Collection + { + return $this->persons; + } + + /** + * @return Collection|Result[] + */ + public function getResults(): Collection + { + return $this->results; + } + + public function getSocialAction(): ?SocialAction + { + return $this->socialAction; + } + + public function getSocialIssues(): Collection + { + return new ArrayCollection([$this->getSocialAction()->getIssue()]); + } + + public function getStartDate(): ?DateTimeInterface + { + return $this->startDate; + } + /** * @return Collection|ThirdParty[] */ @@ -409,31 +345,32 @@ use Symfony\Component\Validator\Constraints as Assert; return $this->getThirdParties(); } - public function addThirdParty(ThirdParty $thirdParty): self + public function getUpdatedAt(): ?DateTimeImmutable { - if (!$this->thirdParties->contains($thirdParty)) { - $this->thirdParties[] = $thirdParty; - } + return $this->updatedAt; + } + + public function getUpdatedBy(): ?User + { + return $this->updatedBy; + } + + public function removeAccompanyingPeriodWorkEvaluation(AccompanyingPeriodWorkEvaluation $evaluation): self + { + $this->accompanyingPeriodWorkEvaluations + ->removeElement($evaluation); + $evaluation->setAccompanyingPeriodWork(null); return $this; } - public function removeThirdParty(ThirdParty $thirdParty): self + public function removeGoal(AccompanyingPeriodWorkGoal $goal): self { - $this->thirdParties->removeElement($thirdParty); - - return $this; - } - - public function getPersons(): Collection - { - return $this->persons; - } - - public function addPerson(Person $person): self - { - if (!$this->persons->contains($person)) { - $this->persons[] = $person; + if ($this->goals->removeElement($goal)) { + // set the owning side to null (unless already changed) + if ($goal->getAccompanyingPeriodWork() === $this) { + $goal->setAccompanyingPeriodWork(null); + } } return $this; @@ -446,30 +383,117 @@ use Symfony\Component\Validator\Constraints as Assert; return $this; } - /** - * @return Collection - */ - public function getAccompanyingPeriodWorkEvaluations() - { - return $this->accompanyingPeriodWorkEvaluations; - } + public function removeResult(Result $result): self + { + $this->results->removeElement($result); - public function addAccompanyingPeriodWorkEvaluation(AccompanyingPeriodWorkEvaluation $evaluation): self - { - if (!$this->accompanyingPeriodWorkEvaluations->contains($evaluation)) { - $this->accompanyingPeriodWorkEvaluations[] = $evaluation; - $evaluation->setAccompanyingPeriodWork($this); - } + return $this; + } - return $this; - } + public function removeSocialIssue(SocialIssue $issue): AccompanyingPeriodLinkedWithSocialIssuesEntityInterface + { + $this->getSocialIssues()->removeElement($issue); - public function removeAccompanyingPeriodWorkEvaluation(AccompanyingPeriodWorkEvaluation $evaluation): self - { - $this->accompanyingPeriodWorkEvaluations - ->removeElement($evaluation); - $evaluation->setAccompanyingPeriodWork(null); + return $this; + } - return $this; - } - } + public function removeThirdParty(ThirdParty $thirdParty): self + { + $this->thirdParties->removeElement($thirdParty); + + return $this; + } + + /** + * Internal: you should use `$accompanyingPeriod->removeWork($work);` or + * `$accompanyingPeriod->addWork($work);`. + */ + public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self + { + if ($this->accompanyingPeriod instanceof AccompanyingPeriod + && $accompanyingPeriod !== $this->accompanyingPeriod) { + throw new LogicException('A work cannot change accompanyingPeriod'); + } + + $this->accompanyingPeriod = $accompanyingPeriod; + + return $this; + } + + public function setCreatedAt(DateTimeInterface $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function setCreatedAutomatically(bool $createdAutomatically): self + { + $this->createdAutomatically = $createdAutomatically; + + return $this; + } + + public function setCreatedAutomaticallyReason(string $createdAutomaticallyReason): self + { + $this->createdAutomaticallyReason = $createdAutomaticallyReason; + + return $this; + } + + public function setCreatedBy(?User $createdBy): self + { + $this->createdBy = $createdBy; + + return $this; + } + + public function setEndDate(?DateTimeInterface $endDate = null): self + { + $this->endDate = $endDate; + + return $this; + } + + public function setHandlingThierParty(?ThirdParty $handlingThierParty): self + { + $this->handlingThierParty = $handlingThierParty; + + return $this; + } + + public function setNote(string $note): self + { + $this->note = $note; + + return $this; + } + + public function setSocialAction(?SocialAction $socialAction): self + { + $this->socialAction = $socialAction; + + return $this; + } + + public function setStartDate(DateTimeInterface $startDate): self + { + $this->startDate = $startDate; + + return $this; + } + + public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface + { + $this->updatedAt = $datetime; + + return $this; + } + + public function setUpdatedBy(User $user): TrackUpdateInterface + { + $this->updatedBy = $user; + + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php index 996007e96..479bbfb87 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php @@ -1,36 +1,39 @@ documents = new ArrayCollection(); } - /** - * @return int|null - */ - public function getId(): ?int - { - return $this->id; - } - - /** - * @return AccompanyingPeriodWork|null - */ - public function getAccompanyingPeriodWork(): ?AccompanyingPeriodWork - { - return $this->accompanyingPeriodWork; - } - - /** - * @param AccompanyingPeriodWork|null $accompanyingPeriodWork - * @return AccompanyingPeriodWorkEvaluation - */ - public function setAccompanyingPeriodWork(?AccompanyingPeriodWork $accompanyingPeriodWork): AccompanyingPeriodWorkEvaluation - { - if ( - $accompanyingPeriodWork instanceof AccompanyingPeriodWork - && $this->accompanyingPeriodWork instanceof AccompanyingPeriodWork - && $this->accompanyingPeriodWork->getId() !== $accompanyingPeriodWork->getId()) { - throw new \RuntimeException("Changing the ". - "accompanyingPeriodWork is not allowed"); - } - - $this->accompanyingPeriodWork = $accompanyingPeriodWork; - return $this; - } - - /** - * @return Evaluation|null - */ - public function getEvaluation(): ?Evaluation - { - return $this->evaluation; - } - - /** - * @param Evaluation|null $evaluation - * @return AccompanyingPeriodWorkEvaluation - */ - public function setEvaluation(?Evaluation $evaluation): AccompanyingPeriodWorkEvaluation - { - if ( - ($evaluation instanceof Evaluation - && $this->evaluation instanceof Evaluation - && $evaluation->getId() !== $this->evaluation->getId()) - || - ($this->evaluation instanceof Evaluation - && null === $evaluation) - ) { - $cl = AccompanyingPeriodWorkEvaluation::class; - throw new \LogicException("once set, an $cl cannot - change or remove the linked Evaluation::class"); - } - - $this->evaluation = $evaluation; - - return $this; - } - - /** - * @return DateTimeImmutable|null - */ - public function getStartDate(): ?DateTimeImmutable - { - return $this->startDate; - } - - /** - * @param DateTimeImmutable|null $startDate - * @return AccompanyingPeriodWorkEvaluation - */ - public function setStartDate(?DateTimeImmutable $startDate): AccompanyingPeriodWorkEvaluation - { - $this->startDate = $startDate; - return $this; - } - - /** - * @return DateTimeImmutable|null - */ - public function getEndDate(): ?DateTimeImmutable - { - return $this->endDate; - } - - /** - * @param DateTimeImmutable|null $endDate - * @return AccompanyingPeriodWorkEvaluation - */ - public function setEndDate(?DateTimeImmutable $endDate): AccompanyingPeriodWorkEvaluation - { - $this->endDate = $endDate; - return $this; - } - - /** - * @return DateTimeImmutable|null - */ - public function getMaxDate(): ?DateTimeImmutable - { - return $this->maxDate; - } - - /** - * @param DateTimeImmutable|null $maxDate - * @return AccompanyingPeriodWorkEvaluation - */ - public function setMaxDate(?DateTimeImmutable $maxDate): AccompanyingPeriodWorkEvaluation - { - $this->maxDate = $maxDate; - return $this; - } - - /** - * @return DateInterval|null - */ - public function getWarningInterval(): ?DateInterval - { - return $this->warningInterval; - } - - /** - * @param DateInterval|null $warningInterval - * @return AccompanyingPeriodWorkEvaluation - */ - public function setWarningInterval(?DateInterval $warningInterval): AccompanyingPeriodWorkEvaluation - { - $this->warningInterval = $warningInterval; - return $this; - } - - /** - * @return string - */ - public function getComment(): string - { - return $this->comment; - } - - /** - * @param string $comment - * @return AccompanyingPeriodWorkEvaluation - */ - public function setComment(string $comment): AccompanyingPeriodWorkEvaluation - { - $this->comment = $comment; - return $this; - } - - /** - * @return User|null - */ - public function getCreatedBy(): ?User - { - return $this->createdBy; - } - - /** - * @param User|null $createdBy - * @return AccompanyingPeriodWorkEvaluation - */ - public function setCreatedBy(?User $createdBy): AccompanyingPeriodWorkEvaluation - { - $this->createdBy = $createdBy; - return $this; - } - - /** - * @return DateTimeImmutable|null - */ - public function getCreatedAt(): ?DateTimeImmutable - { - return $this->createdAt; - } - - /** - * @param DateTimeImmutable|null $createdAt - * @return AccompanyingPeriodWorkEvaluation - */ - public function setCreatedAt(\DateTimeInterface $createdAt): self - { - $this->createdAt = $createdAt; - return $this; - } - - /** - * @return User|null - */ - public function getUpdatedBy(): ?User - { - return $this->updatedBy; - } - - /** - * @param User|null $updatedBy - * @return AccompanyingPeriodWorkEvaluation - */ - public function setUpdatedBy(?User $updatedBy): AccompanyingPeriodWorkEvaluation - { - $this->updatedBy = $updatedBy; - return $this; - } - - /** - * @return DateTimeImmutable|null - */ - public function getUpdatedAt(): ?DateTimeImmutable - { - return $this->updatedAt; - } - - /** - * @param DateTimeImmutable|null $updatedAt - * @return AccompanyingPeriodWorkEvaluation - */ - public function setUpdatedAt(\DateTimeInterface $updatedAt): self - { - $this->updatedAt = $updatedAt; - return $this; - } - - /** - * @return Collection - */ - public function getDocuments() - { - return $this->documents; - } - public function addDocument(AccompanyingPeriodWorkEvaluationDocument $document): self { if (!$this->documents->contains($document)) { @@ -394,14 +166,51 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre return $this; } - public function removeDocument(AccompanyingPeriodWorkEvaluationDocument $document): self + public function getAccompanyingPeriodWork(): ?AccompanyingPeriodWork { - $this->documents->removeElement($document); - return $this; + return $this->accompanyingPeriodWork; + } + + public function getComment(): string + { + return $this->comment; + } + + public function getCreatedAt(): ?DateTimeImmutable + { + return $this->createdAt; + } + + public function getCreatedBy(): ?User + { + return $this->createdBy; } /** - * Arbitrary data, used for client + * @return Collection + */ + public function getDocuments() + { + return $this->documents; + } + + public function getEndDate(): ?DateTimeImmutable + { + return $this->endDate; + } + + public function getEvaluation(): ?Evaluation + { + return $this->evaluation; + } + + public function getId(): ?int + { + return $this->id; + } + + /** + * Arbitrary data, used for client. * * @return mixed */ @@ -410,10 +219,123 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre return $this->key; } + public function getMaxDate(): ?DateTimeImmutable + { + return $this->maxDate; + } + + public function getStartDate(): ?DateTimeImmutable + { + return $this->startDate; + } + + public function getUpdatedAt(): ?DateTimeImmutable + { + return $this->updatedAt; + } + + public function getUpdatedBy(): ?User + { + return $this->updatedBy; + } + /** - * Arbitrary data, used for client + * @Serializer\Groups({"docgen:read"}) + */ + public function getWarningDate(): ?DateTimeImmutable + { + if (null === $this->getEndDate() || null === $this->getWarningInterval()) { + return null; + } + + return $this->getEndDate()->sub($this->getWarningInterval()); + } + + public function getWarningInterval(): ?DateInterval + { + return $this->warningInterval; + } + + public function removeDocument(AccompanyingPeriodWorkEvaluationDocument $document): self + { + $this->documents->removeElement($document); + + return $this; + } + + public function setAccompanyingPeriodWork(?AccompanyingPeriodWork $accompanyingPeriodWork): AccompanyingPeriodWorkEvaluation + { + if ( + $accompanyingPeriodWork instanceof AccompanyingPeriodWork + && $this->accompanyingPeriodWork instanceof AccompanyingPeriodWork + && $this->accompanyingPeriodWork->getId() !== $accompanyingPeriodWork->getId()) { + throw new RuntimeException('Changing the ' . + 'accompanyingPeriodWork is not allowed'); + } + + $this->accompanyingPeriodWork = $accompanyingPeriodWork; + + return $this; + } + + public function setComment(string $comment): AccompanyingPeriodWorkEvaluation + { + $this->comment = $comment; + + return $this; + } + + /** + * @param DateTimeImmutable|null $createdAt + * + * @return AccompanyingPeriodWorkEvaluation + */ + public function setCreatedAt(DateTimeInterface $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function setCreatedBy(?User $createdBy): AccompanyingPeriodWorkEvaluation + { + $this->createdBy = $createdBy; + + return $this; + } + + public function setEndDate(?DateTimeImmutable $endDate): AccompanyingPeriodWorkEvaluation + { + $this->endDate = $endDate; + + return $this; + } + + public function setEvaluation(?Evaluation $evaluation): AccompanyingPeriodWorkEvaluation + { + if ( + ($evaluation instanceof Evaluation + && $this->evaluation instanceof Evaluation + && $evaluation->getId() !== $this->evaluation->getId()) + || ($this->evaluation instanceof Evaluation + && null === $evaluation) + ) { + $cl = AccompanyingPeriodWorkEvaluation::class; + + throw new LogicException("once set, an {$cl} cannot + change or remove the linked Evaluation::class"); + } + + $this->evaluation = $evaluation; + + return $this; + } + + /** + * Arbitrary data, used for client. * * @param mixed $key + * * @return AccompanyingPeriodWorkEvaluation */ public function setKey($key): self @@ -422,4 +344,44 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre return $this; } + + public function setMaxDate(?DateTimeImmutable $maxDate): AccompanyingPeriodWorkEvaluation + { + $this->maxDate = $maxDate; + + return $this; + } + + public function setStartDate(?DateTimeImmutable $startDate): AccompanyingPeriodWorkEvaluation + { + $this->startDate = $startDate; + + return $this; + } + + /** + * @param DateTimeImmutable|null $updatedAt + * + * @return AccompanyingPeriodWorkEvaluation + */ + public function setUpdatedAt(DateTimeInterface $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } + + public function setUpdatedBy(?User $updatedBy): AccompanyingPeriodWorkEvaluation + { + $this->updatedBy = $updatedBy; + + return $this; + } + + public function setWarningInterval(?DateInterval $warningInterval): AccompanyingPeriodWorkEvaluation + { + $this->warningInterval = $warningInterval; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocument.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocument.php index eb07cd4d8..538cffc36 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocument.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocument.php @@ -1,5 +1,14 @@ accompanyingPeriodWorkEvaluation; } /** - * @param AccompanyingPeriodWorkEvaluation|null $accompanyingPeriodWorkEvaluation - * @return AccompanyingPeriodWorkEvaluationDocument + * @return \DateTimeImmutable|null */ + public function getCreatedAt(): ?DateTimeInterface + { + return $this->createdAt; + } + + public function getCreatedBy(): ?User + { + return $this->createdBy; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getStoredObject(): ?StoredObject + { + return $this->storedObject; + } + + public function getTemplate(): ?DocGeneratorTemplate + { + return $this->template; + } + + /** + * @return DateTimeImmutable|null + */ + public function getUpdatedAt(): ?DateTimeInterface + { + return $this->updatedAt; + } + + public function getUpdatedBy(): ?User + { + return $this->updatedBy; + } + public function setAccompanyingPeriodWorkEvaluation(?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation): AccompanyingPeriodWorkEvaluationDocument { // if an evaluation is already associated, we cannot change the association (removing the association, @@ -103,111 +147,53 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct if ($this->accompanyingPeriodWorkEvaluation instanceof AccompanyingPeriodWorkEvaluation && $accompanyingPeriodWorkEvaluation instanceof AccompanyingPeriodWorkEvaluation) { if ($this->accompanyingPeriodWorkEvaluation !== $accompanyingPeriodWorkEvaluation) { - throw new \RuntimeException("It is not allowed to change the evaluation for a document"); + throw new RuntimeException('It is not allowed to change the evaluation for a document'); } } $this->accompanyingPeriodWorkEvaluation = $accompanyingPeriodWorkEvaluation; + return $this; } - /** - * @return StoredObject|null - */ - public function getStoredObject(): ?StoredObject + public function setCreatedAt(DateTimeInterface $datetime): TrackCreationInterface { - return $this->storedObject; + $this->createdAt = $datetime; + + return $this; + } + + public function setCreatedBy(User $user): TrackCreationInterface + { + $this->createdBy = $user; + + return $this; } - /** - * @param StoredObject|null $storedObject - * @return AccompanyingPeriodWorkEvaluationDocument - */ public function setStoredObject(?StoredObject $storedObject): AccompanyingPeriodWorkEvaluationDocument { $this->storedObject = $storedObject; + return $this; } - public function setCreatedBy(User $user): \Chill\MainBundle\Doctrine\Model\TrackCreationInterface - { - $this->createdBy = $user; - return $this; - } - - public function setCreatedAt(\DateTimeInterface $datetime): \Chill\MainBundle\Doctrine\Model\TrackCreationInterface - { - $this->createdAt = $datetime; - return $this; - } - - public function setUpdatedBy(User $user): \Chill\MainBundle\Doctrine\Model\TrackUpdateInterface - { - $this->updatedBy = $user; - return $this; - } - - public function setUpdatedAt(\DateTimeInterface $datetime): \Chill\MainBundle\Doctrine\Model\TrackUpdateInterface - { - $this->updatedAt = $datetime; - return $this; - } - - /** - * @return int|null - */ - public function getId(): ?int - { - return $this->id; - } - - /** - * @return User|null - */ - public function getCreatedBy(): ?User - { - return $this->createdBy; - } - - /** - * @return \DateTimeImmutable|null - */ - public function getCreatedAt(): ?\DateTimeInterface - { - return $this->createdAt; - } - - /** - * @return User|null - */ - public function getUpdatedBy(): ?User - { - return $this->updatedBy; - } - - /** - * @return DateTimeImmutable|null - */ - public function getUpdatedAt(): ?\DateTimeInterface - { - return $this->updatedAt; - } - - /** - * @return DocGeneratorTemplate|null - */ - public function getTemplate(): ?DocGeneratorTemplate - { - return $this->template; - } - - /** - * @param DocGeneratorTemplate|null $template - * @return AccompanyingPeriodWorkEvaluationDocument - */ public function setTemplate(?DocGeneratorTemplate $template): AccompanyingPeriodWorkEvaluationDocument { $this->template = $template; + return $this; } + public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface + { + $this->updatedAt = $datetime; + + return $this; + } + + public function setUpdatedBy(User $user): TrackUpdateInterface + { + $this->updatedBy = $user; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php index d8cfb4875..c31dfaa83 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php @@ -1,5 +1,14 @@ results = new ArrayCollection(); } + public function addResult(Result $result): self + { + if (!$this->results->contains($result)) { + $this->results[] = $result; + } + + return $this; + } + + public function getAccompanyingPeriodWork(): ?AccompanyingPeriodWork + { + return $this->accompanyingPeriodWork; + } + + public function getGoal(): ?Goal + { + return $this->goal; + } + public function getId(): ?int { return $this->id; @@ -71,25 +100,28 @@ class AccompanyingPeriodWorkGoal return $this->note; } - public function setNote(string $note): self + /** + * @return Collection|Result[] + */ + public function getResults(): Collection { - $this->note = $note; - - return $this; + return $this->results; } - public function getAccompanyingPeriodWork(): ?AccompanyingPeriodWork + public function removeResult(Result $result): self { - return $this->accompanyingPeriodWork; + $this->results->removeElement($result); + + return $this; } public function setAccompanyingPeriodWork(?AccompanyingPeriodWork $accompanyingPeriodWork): self { if ($this->accompanyingPeriodWork instanceof AccompanyingPeriodWork && $accompanyingPeriodWork !== $this->accompanyingPeriodWork - && $accompanyingPeriodWork !== null + && null !== $accompanyingPeriodWork ) { - throw new \LogicException("Change accompanying period work is not allowed"); + throw new LogicException('Change accompanying period work is not allowed'); } $this->accompanyingPeriodWork = $accompanyingPeriodWork; @@ -97,11 +129,6 @@ class AccompanyingPeriodWorkGoal return $this; } - public function getGoal(): ?Goal - { - return $this->goal; - } - public function setGoal(?Goal $goal): self { $this->goal = $goal; @@ -109,26 +136,9 @@ class AccompanyingPeriodWorkGoal return $this; } - /** - * @return Collection|Result[] - */ - public function getResults(): Collection + public function setNote(string $note): self { - return $this->results; - } - - public function addResult(Result $result): self - { - if (!$this->results->contains($result)) { - $this->results[] = $result; - } - - return $this; - } - - public function removeResult(Result $result): self - { - $this->results->removeElement($result); + $this->note = $note; return $this; } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php index 29b2a44aa..aa12f0416 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php @@ -1,33 +1,23 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Entity\AccompanyingPeriod; -use Doctrine\ORM\Mapping as ORM; -use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation as Serializer; /** - * ClosingMotive give an explanation why we closed the Accompanying period + * ClosingMotive give an explanation why we closed the Accompanying period. * * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_closingmotive") @@ -35,55 +25,50 @@ use Doctrine\Common\Collections\ArrayCollection; class ClosingMotive { /** - * @var integer + * @ORM\Column(type="boolean") + */ + private bool $active = true; + + /** + * Child Accompanying periods. + * + * @ORM\OneToMany( + * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive", + * mappedBy="parent") + */ + private Collection $children; + + /** + * @var int * * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") + * @Serializer\Groups({"docgen:read"}) */ - private $id; + private ?int $id = null; /** - * @var array - * * @ORM\Column(type="json") + * @Serializer\Groups({"docgen:read"}) + * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) */ - private $name; - + private array $name = []; + /** - * @var boolean - * - * @ORM\Column(type="boolean") + * @ORM\Column(type="float") */ - private $active = true; - + private float $ordering = 0.0; + /** * @var self * * @ORM\ManyToOne( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive", - * inversedBy="children") + * inversedBy="children") */ - private $parent = null; - - /** - * Child Accompanying periods - * @var Collection - * - * @ORM\OneToMany( - * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive", - * mappedBy="parent") - */ - private $children; - - /** - * @var float - * - * @ORM\Column(type="float") - */ - private $ordering = 0.0; - - + private ?ClosingMotive $parent = null; + /** * ClosingMotive constructor. */ @@ -91,11 +76,28 @@ class ClosingMotive { $this->children = new ArrayCollection(); } - + + public function addChildren(ClosingMotive $child): ClosingMotive + { + if ($this->children->contains($child)) { + return $this; + } + + $this->children->add($child); + $child->setParent($this); + + return $this; + } + + public function getChildren(): Collection + { + return $this->children; + } + /** - * Get id + * Get id. * - * @return integer + * @return int */ public function getId() { @@ -103,7 +105,87 @@ class ClosingMotive } /** - * Set name + * Get name. + * + * @return array + */ + public function getName() + { + return $this->name; + } + + public function getOrdering(): float + { + return $this->ordering; + } + + /** + * @return ClosingMotive + */ + public function getParent() + { + return $this->parent; + } + + public function hasParent(): bool + { + return null !== $this->parent; + } + + public function isActive(): bool + { + return $this->active; + } + + public function isChild(): bool + { + return null !== $this->parent; + } + + public function isLeaf(): bool + { + return $this->children->count() === 0; + } + + public function isParent(): bool + { + return $this->children->count() > 0; + } + + public function removeChildren(ClosingMotive $child): ClosingMotive + { + if ($this->children->removeElement($child)) { + $child->setParent(null); + } + + return $this; + } + + /** + * @return $this + */ + public function setActive(bool $active) + { + $this->active = $active; + + if (false === $this->active) { + foreach ($this->getChildren() as $child) { + $child->setActive(false); + } + } + + return $this; + } + + public function setChildren(Collection $children): ClosingMotive + { + $this->children = $children; + + return $this; + } + + /** + * Set name. * * @param array $name * @@ -117,161 +199,23 @@ class ClosingMotive } /** - * Get name - * - * @return array - */ - public function getName() - { - return $this->name; - } - - /** - * @return bool - */ - public function isActive(): bool - { - return $this->active; - } - - /** - * @param bool $active - * @return $this - */ - public function setActive(bool $active) - { - $this->active = $active; - - if ($this->active === FALSE) { - foreach ($this->getChildren() as $child) { - $child->setActive(FALSE); - } - } - - return $this; - } - - /** - * @return ClosingMotive - */ - public function getParent() - { - return $this->parent; - } - - /** - * @return Collection - */ - public function getChildren(): Collection - { - return $this->children; - } - - /** - * @param ClosingMotive|null $parent - * @return ClosingMotive - */ - public function setParent(?ClosingMotive $parent): ClosingMotive - { - $this->parent = $parent; - - if (NULL !== $parent) { - //$parent->addChildren($this); - } - - return $this; - } - - /** - * @param Collection $children - * @return ClosingMotive - */ - public function setChildren(Collection $children): ClosingMotive - { - $this->children = $children; - - return $this; - } - - /** - * @param ClosingMotive $child - * @return ClosingMotive - */ - public function addChildren(ClosingMotive $child): ClosingMotive - { - if ($this->children->contains($child)) { - return $this; - } - - $this->children->add($child); - $child->setParent($this); - - return $this; - } - - /** - * @param ClosingMotive $child - * @return ClosingMotive - */ - public function removeChildren(ClosingMotive $child): ClosingMotive - { - if ($this->children->removeElement($child)) { - $child->setParent(null); - } - - return $this; - } - - /** - * @return float - */ - public function getOrdering(): float - { - return $this->ordering; - } - - /** - * @param float $ordering * @return $this */ public function setOrdering(float $ordering) { $this->ordering = $ordering; - + return $this; } - - /** - * @return bool - */ - public function isChild(): bool - { - return $this->parent !== null; - } - - /** - * @return bool - */ - public function isParent(): bool - { - return $this->children->count() > 0; - } - - /** - * @return bool - */ - public function isLeaf(): bool - { - return $this->children->count() === 0; - } - - /** - * @return bool - */ - public function hasParent(): bool - { - return $this->parent !== null; - } + public function setParent(?ClosingMotive $parent): ClosingMotive + { + $this->parent = $parent; + + if (null !== $parent) { + //$parent->addChildren($this); + } + + return $this; + } } - diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php index 18b5bd38d..75d1b2857 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php @@ -1,60 +1,54 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\MainBundle\Entity\User; -use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; +use Chill\MainBundle\Entity\User; +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use DateTimeInterface; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Annotation\Groups; /** * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_comment") * @DiscriminatorMap(typeProperty="type", mapping={ - * "accompanying_period_comment"=Comment::class + * "accompanying_period_comment": Comment::class * }) */ class Comment implements TrackCreationInterface, TrackUpdateInterface { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @Groups({"read"}) - */ - private $id; - /** * @ORM\ManyToOne( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod", - * inversedBy="comments") + * inversedBy="comments") * @ORM\JoinColumn(nullable=false, onDelete="CASCADE") */ private $accompanyingPeriod; + /** + * @ORM\Column(type="text") + * @Groups({"read", "write"}) + */ + private $content; + + /** + * @ORM\Column(type="datetime") + * @Groups({"read"}) + */ + private $createdAt; + /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=false) @@ -63,10 +57,12 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface private $creator; /** - * @ORM\Column(type="datetime") + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") * @Groups({"read"}) */ - private $createdAt; + private $id; /** * @ORM\Column(type="datetime") @@ -81,20 +77,39 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface */ private $updatedBy; - /** - * @ORM\Column(type="text") - * @Groups({"read", "write"}) - */ - private $content; + public function getAccompanyingPeriod(): ?AccompanyingPeriod + { + return $this->accompanyingPeriod; + } + + public function getContent(): ?string + { + return $this->content; + } + + public function getCreatedAt(): ?DateTimeInterface + { + return $this->createdAt; + } + + public function getCreator(): ?User + { + return $this->creator; + } public function getId(): ?int { return $this->id; } - public function getAccompanyingPeriod(): ?AccompanyingPeriod + public function getUpdatedAt(): ?DateTimeInterface { - return $this->accompanyingPeriod; + return $this->updatedAt; + } + + public function getUpdatedBy(): ?User + { + return $this->updatedBy; } public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self @@ -104,14 +119,16 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function getCreator(): ?User + public function setContent(string $content): self { - return $this->creator; + $this->content = $content; + + return $this; } - public function setCreator(?User $creator): self + public function setCreatedAt(DateTimeInterface $createdAt): self { - $this->creator = $creator; + $this->createdAt = $createdAt; return $this; } @@ -121,51 +138,24 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface return $this->setCreator($user); } - public function getCreatedAt(): ?\DateTimeInterface + public function setCreator(?User $creator): self { - return $this->createdAt; - } - - public function setCreatedAt(\DateTimeInterface $createdAt): self - { - $this->createdAt = $createdAt; + $this->creator = $creator; return $this; } - public function getUpdatedAt(): ?\DateTimeInterface - { - return $this->updatedAt; - } - - public function setUpdatedAt(\DateTimeInterface $updatedAt): self + public function setUpdatedAt(DateTimeInterface $updatedAt): self { $this->updatedAt = $updatedAt; return $this; } - public function getUpdatedBy(): ?User - { - return $this->updatedBy; - } - public function setUpdatedBy(User $updatedBy): self { $this->updatedBy = $updatedBy; return $this; } - - public function getContent(): ?string - { - return $this->content; - } - - public function setContent(string $content): self - { - $this->content = $content; - - return $this; - } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php index aaf2bec94..7a1114b3e 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php @@ -1,40 +1,29 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Entity\AccompanyingPeriod; +use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation as Serializer; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Annotation\Groups; /** * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_origin") * @Serializer\DiscriminatorMap( - * typeProperty="type", - * mapping={ - * "origin"=Origin::class - * }) + * typeProperty="type", + * mapping={ + * "origin": Origin::class + * }) */ class Origin { @@ -42,45 +31,46 @@ class Origin * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $id; + private ?int $id = null; /** * @ORM\Column(type="json") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) + * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) */ - private $label; + private array $label = []; /** * @ORM\Column(type="date_immutable", nullable=true) * @Groups({"read"}) */ - private $noActiveAfter; + private ?DateTimeImmutable $noActiveAfter = null; public function getId(): ?int { return $this->id; } - public function getLabel() + public function getLabel(): array { return $this->label; } - public function setLabel(string $label): self + public function getNoActiveAfter(): ?DateTimeImmutable + { + return $this->noActiveAfter; + } + + public function setLabel(array $label): self { $this->label = $label; return $this; } - public function getNoActiveAfter(): ?\DateTimeImmutable - { - return $this->noActiveAfter; - } - - public function setNoActiveAfter(?\DateTimeImmutable $noActiveAfter): self + public function setNoActiveAfter(?DateTimeImmutable $noActiveAfter): self { $this->noActiveAfter = $noActiveAfter; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php index a50e28621..a11dc9b3d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php @@ -1,53 +1,39 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Repository\AccompanyingPeriod\ResourceRepository; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; +use UnexpectedValueException; /** * @ORM\Entity - * @ORM\Table(name="chill_person_accompanying_period_resource") + * @ORM\Table( + * name="chill_person_accompanying_period_resource", + * uniqueConstraints={ + * @ORM\UniqueConstraint(name="person_unique", columns={"person_id", "accompanyingperiod_id"}), + * @ORM\UniqueConstraint(name="thirdparty_unique", columns={"thirdparty_id", "accompanyingperiod_id"}) + * } + * ) * @DiscriminatorMap(typeProperty="type", mapping={ - * "accompanying_period_resource"=Resource::class - * }) + * "accompanying_period_resource": Resource::class + * }) */ class Resource { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @Groups({"read"}) - */ - private $id; - /** * @ORM\ManyToOne( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod", @@ -55,53 +41,48 @@ class Resource * ) * @ORM\JoinColumn(nullable=false) */ - private $accompanyingPeriod; + private ?AccompanyingPeriod $accompanyingPeriod = null; /** - * @ORM\ManyToOne(targetEntity=ThirdParty::class) - * @ORM\JoinColumn(nullable=true) + * @ORM\ManyToOne(targetEntity=Comment::class) */ - private $thirdParty; + private $comment; + + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + * @Groups({"read", "docgen:read"}) + */ + private ?int $id = null; /** * @ORM\ManyToOne(targetEntity=Person::class) * @ORM\JoinColumn(nullable=true) + * @Groups({"docgen:read"}) */ - private $person; + private ?Person $person = null; /** - * @ORM\ManyToOne(targetEntity=Comment::class) + * @ORM\ManyToOne(targetEntity=ThirdParty::class) * @ORM\JoinColumn(nullable=true) + * @Groups({"docgen:read"}) */ - private $comment; - - public function getId(): ?int - { - return $this->id; - } + private ?ThirdParty $thirdParty = null; public function getAccompanyingPeriod(): ?AccompanyingPeriod { return $this->accompanyingPeriod; } - public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self + public function getComment(): ?Comment { - $this->accompanyingPeriod = $accompanyingPeriod; - - return $this; + return $this->comment; } - public function getThirdParty(): ?ThirdParty + public function getId(): ?int { - return $this->thirdParty; - } - - private function setThirdParty(?ThirdParty $thirdParty): self - { - $this->thirdParty = $thirdParty; - - return $this; + return $this->id; } public function getPerson(): ?Person @@ -109,16 +90,25 @@ class Resource return $this->person; } - private function setPerson(?Person $person): self + /** + * @return Person|ThirdParty + * @Groups({"read", "write"}) + */ + public function getResource() { - $this->person = $person; - - return $this; + return $this->person ?? $this->thirdParty; } - public function getComment(): ?Comment + public function getThirdParty(): ?ThirdParty { - return $this->comment; + return $this->thirdParty; + } + + public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self + { + $this->accompanyingPeriod = $accompanyingPeriod; + + return $this; } public function setComment(?Comment $comment): self @@ -129,36 +119,42 @@ class Resource } /** - * * @param $resource Person|ThirdParty */ public function setResource($resource): self { if ($resource instanceof ThirdParty) { $this->setThirdParty($resource); - $this->setPerson(NULL); + $this->setPerson(null); } elseif ($resource instanceof Person) { $this->setPerson($resource); - $this->setThirdParty(NULL); - } elseif (NULL === $resource) { - $this->setPerson(NULL); - $this->setThirdParty(NULL); + $this->setThirdParty(null); + } elseif (null === $resource) { + $this->setPerson(null); + $this->setThirdParty(null); } else { - throw new \UnexpectedValueException(sprintf("the resource ". - "should be an instance of %s or %s", Person::class, - ThirdParty::class)); + throw new UnexpectedValueException(sprintf( + 'the resource ' . + 'should be an instance of %s or %s', + Person::class, + ThirdParty::class + )); } return $this; } - - - /** - * @return ThirdParty|Person - * @Groups({"read", "write"}) - */ - public function getResource() + + private function setPerson(?Person $person): self { - return $this->person ?? $this->thirdParty; + $this->person = $person; + + return $this; + } + + private function setThirdParty(?ThirdParty $thirdParty): self + { + $this->thirdParty = $thirdParty; + + return $this; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php index c873e527a..42e57185e 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php @@ -1,85 +1,83 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Entity; -use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Entity\Person; +use DateTime; +use DateTimeInterface; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Annotation\Groups; /** - * AccompanyingPeriodParticipation Class + * AccompanyingPeriodParticipation Class. * - * @package Chill\PersonBundle\Entity * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_participation") * @DiscriminatorMap(typeProperty="type", mapping={ - * "accompanying_period_participation"=AccompanyingPeriodParticipation::class - * }) + * "accompanying_period_participation": AccompanyingPeriodParticipation::class + * }) */ class AccompanyingPeriodParticipation { + /** + * @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class, inversedBy="participations", cascade={"persist"}) + * @ORM\JoinColumn(name="accompanyingperiod_id", referencedColumnName="id", nullable=false) + */ + private ?AccompanyingPeriod $accompanyingPeriod = null; + + /** + * @ORM\Column(type="date", nullable=true) + * @Groups({"read", "docgen:read"}) + */ + private ?DateTime $endDate = null; + /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $id; + private ?int $id = null; /** * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodParticipations") * @ORM\JoinColumn(name="person_id", referencedColumnName="id", nullable=false) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $person; - - /** - * @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class, inversedBy="participations", cascade={"persist"}) - * @ORM\JoinColumn(name="accompanyingperiod_id", referencedColumnName="id", nullable=false) - */ - private $accompanyingPeriod; + private ?Person $person = null; /** * @ORM\Column(type="date", nullable=false) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $startDate; - - /** - * @ORM\Column(type="date", nullable=true) - * @Groups({"read"}) - */ - private $endDate = null; + private ?DateTime $startDate = null; public function __construct(AccompanyingPeriod $accompanyingPeriod, Person $person) { - $this->startDate = new \DateTimeImmutable('now'); + $this->startDate = new DateTime('now'); $this->accompanyingPeriod = $accompanyingPeriod; $this->person = $person; } + public function getAccompanyingPeriod(): ?AccompanyingPeriod + { + return $this->accompanyingPeriod; + } + + public function getEndDate(): ?DateTimeInterface + { + return $this->endDate; + } + public function getId(): ?int { return $this->id; @@ -90,16 +88,14 @@ class AccompanyingPeriodParticipation return $this->person; } - public function setPerson(?Person $person): self + public function getStartDate(): ?DateTimeInterface { - $this->person = $person; - - return $this; + return $this->startDate; } - public function getAccompanyingPeriod(): ?AccompanyingPeriod + public function isOpen(): bool { - return $this->accompanyingPeriod; + return null === $this->endDate; } public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self @@ -109,29 +105,24 @@ class AccompanyingPeriodParticipation return $this; } - public function getStartDate(): ?\DateTimeInterface - { - return $this->startDate; - } - - /* - * public function setStartDate(\DateTimeInterface $startDate): self { $this->startDate = $startDate; return $this; } - */ - - public function getEndDate(): ?\DateTimeInterface - { - return $this->endDate; - } - - public function setEndDate(?\DateTimeInterface $endDate): self + public function setEndDate(?DateTime $endDate): self { $this->endDate = $endDate; return $this; } - public function isOpen(): bool + public function setPerson(?Person $person): self { - return $this->endDate === null; + $this->person = $person; + + return $this; + } + + private function checkSameStartEnd() + { + if ($this->endDate === $this->startDate) { + $this->accompanyingPeriod->removeParticipation($this); + } } } diff --git a/src/Bundle/ChillPersonBundle/Entity/HasPerson.php b/src/Bundle/ChillPersonBundle/Entity/HasPerson.php index e200182bc..1bee7127c 100644 --- a/src/Bundle/ChillPersonBundle/Entity/HasPerson.php +++ b/src/Bundle/ChillPersonBundle/Entity/HasPerson.php @@ -1,34 +1,22 @@ , - * - * 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\Entity; - -use Chill\PersonBundle\Entity\Person; /** - * Interface which applies to entities which are associated to a single person - * + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\Entity; + +/** + * Interface which applies to entities which are associated to a single person. */ interface HasPerson { - public function setPerson(Person $person = null): HasPerson; - public function getPerson(): ?Person; + + public function setPerson(?Person $person = null): HasPerson; } diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index 46ae02ce0..95aa83872 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -1,74 +1,88 @@ commentMembers = new CommentEmbeddable(); } - public function getId(): ?int - { - return $this->id; - } - /** - * @param Address $address * @return $this */ public function addAddress(Address $address) { foreach ($this->getAddresses() as $a) { - if ($a->getValidFrom() < $address->getValidFrom() && $a->getValidTo() === NULL) { + if ($a->getValidFrom() <= $address->getValidFrom() && $a->getValidTo() === null) { $a->setValidTo($address->getValidFrom()); } } @@ -99,35 +107,22 @@ class Household return $this; } - /** - * Force an address starting at the current day - * on the Household. - * - * This will force the startDate's address on today. - * - * Used on household creation. - * - * @Serializer\Groups({"create"}) - */ - public function setForceAddress(Address $address) + public function addMember(HouseholdMember $member): self { - $address->setValidFrom(new \DateTime('today')); - $this->addAddress($address); - } + if (!$this->members->contains($member)) { + $this->members[] = $member; + $member->setHousehold($this); + } - /** - * @param Address $address - */ - public function removeAddress(Address $address) - { - $this->addresses->removeElement($address); + return $this; } /** * By default, the addresses are ordered by date, descending (the most - * recent first) + * recent first). * * @Assert\Callback(methods={"validate"}) + * * @return \Chill\MainBundle\Entity\Address[] */ public function getAddresses() @@ -135,25 +130,136 @@ class Household return $this->addresses; } + public function getCommentMembers(): CommentEmbeddable + { + return $this->commentMembers; + } + /** - * @Serializer\Groups({ "read" }) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\SerializedName("current_address") */ - public function getCurrentAddress(\DateTime $at = null): ?Address + public function getCurrentAddress(?DateTime $at = null): ?Address { - $at = $at === null ? new \DateTime('today') : $at; + $at = null === $at ? new DateTime('today') : $at; - $addrs = $this->getAddresses()->filter(function (Address $a) use ($at) { + $addrs = $this->getAddresses()->filter(static function (Address $a) use ($at) { return $a->getValidFrom() <= $at && ( - NULL === $a->getValidTo() || $at < $a->getValidTo() + null === $a->getValidTo() || $a->getValidTo() > $at ); }); if ($addrs->count() > 0) { return $addrs->first(); - } else { - return null; } + + return null; + } + + /** + * @Serializer\Groups({"docgen:read"}) + */ + public function getCurrentMembers(?DateTimeImmutable $now = null): Collection + { + return $this->getMembers()->matching($this->buildCriteriaCurrentMembers($now)); + } + + public function getCurrentMembersByPosition(Position $position, ?DateTimeInterface $now = null) + { + $criteria = new Criteria(); + $expr = Criteria::expr(); + + $criteria->where($expr->eq('position', $position)); + + return $this->getCurrentMembers($now)->matching($criteria); + } + + /** + * get current members ids. + * + * Used in serialization + * + * @Serializer\Groups({"read"}) + * @Serializer\SerializedName("current_members_id") + */ + public function getCurrentMembersIds(?DateTimeImmutable $now = null): Collection + { + return $this->getCurrentMembers($now)->map( + static fn (HouseholdMember $m) => $m->getId() + ); + } + + /** + * @return HouseholdMember[] + */ + public function getCurrentMembersOrdered(?DateTimeImmutable $now = null): Collection + { + $members = $this->getCurrentMembers($now); + + $members->getIterator() + ->uasort( + static function (HouseholdMember $a, HouseholdMember $b) { + if ($a->getPosition() === null) { + if ($b->getPosition() === null) { + return 0; + } + + return -1; + } + + if ($b->getPosition() === null) { + return 1; + } + + if ($a->getPosition()->getOrdering() < $b->getPosition()->getOrdering()) { + return -1; + } + + if ($a->getPosition()->getOrdering() > $b->getPosition()->getOrdering()) { + return 1; + } + + if ($a->isHolder() && !$b->isHolder()) { + return 1; + } + + if (!$a->isHolder() && $b->isHolder()) { + return -1; + } + + return 0; + } + ); + + return $members; + } + + public function getCurrentMembersWithoutPosition(?DateTimeInterface $now = null) + { + $criteria = new Criteria(); + $expr = Criteria::expr(); + + $criteria->where($expr->isNull('position')); + + return $this->getCurrentMembers($now)->matching($criteria); + } + + /** + * Get the persons currently associated to the household. + * + * Return a list of Person, instead of a list of HouseholdMembers + * + * @return Person[] + */ + public function getCurrentPersons(?DateTimeImmutable $now = null): Collection + { + return $this->getCurrentMembers($now) + ->map(static function (HouseholdMember $m) { return $m->getPerson(); }); + } + + public function getId(): ?int + { + return $this->id; } /** @@ -164,36 +270,13 @@ class Household return $this->members; } - public function getMembersOnRange(\DateTimeImmutable $from, ?\DateTimeImmutable $to): Collection - { - $criteria = new Criteria(); - $expr = Criteria::expr(); - - $criteria->where( - $expr->gte('startDate', $from) - ); - - if (NULL !== $to) { - $criteria->andWhere( - $expr->orX( - $expr->lte('endDate', $to), - $expr->eq('endDate', NULL) - ), - ); - } - - return $this->getMembers() - ->matching($criteria) - ; - } - public function getMembersDuringMembership(HouseholdMember $membership) { return $this->getMembersOnRange( $membership->getStartDate(), $membership->getEndDate() )->filter( - function(HouseholdMember $m) use ($membership) { + static function (HouseholdMember $m) use ($membership) { return $m !== $membership; } ); @@ -211,94 +294,33 @@ class Household return $this->getMembers()->matching($criteria); } - public function getCurrentMembers(?\DateTimeImmutable $now = null): Collection - { - return $this->getMembers()->matching($this->buildCriteriaCurrentMembers($now)); - } - - private function buildCriteriaCurrentMembers(?\DateTimeImmutable $now = null): Criteria + public function getMembersOnRange(DateTimeImmutable $from, ?DateTimeImmutable $to): Collection { $criteria = new Criteria(); $expr = Criteria::expr(); - $date = $now === null ? (new \DateTimeImmutable('today')) : $now; - $criteria - ->where($expr->orX( - $expr->isNull('startDate'), - $expr->lte('startDate', $date) - )) - ->andWhere($expr->orX( - $expr->isNull('endDate'), - $expr->gt('endDate', $date) - )); - - return $criteria; - } - - /** - * @return HouseholdMember[] - */ - public function getCurrentMembersOrdered(?\DateTimeImmutable $now = null): Collection - { - $members = $this->getCurrentMembers($now); - - $members->getIterator() - ->uasort( - function (HouseholdMember $a, HouseholdMember $b) { - if ($a->getPosition()->getOrdering() < $b->getPosition()->getOrdering()) { - return -1; - } - if ($a->getPosition()->getOrdering() > $b->getPosition()->getOrdering()) { - return 1; - } - if ($a->isHolder() && !$b->isHolder()) { - return 1; - } - if (!$a->isHolder() && $b->isHolder()) { - return -1; - } - - return 0; - } + $criteria->where( + $expr->gte('startDate', $from) ); - return $members; + if (null !== $to) { + $criteria->andWhere( + $expr->orX( + $expr->lte('endDate', $to), + $expr->eq('endDate', null) + ), + ); + } + + return $this->getMembers() + ->matching($criteria); } - /** - * get current members ids - * - * Used in serialization - * - * @Serializer\Groups({"read"}) - * @Serializer\SerializedName("current_members_id") - * - */ - public function getCurrentMembersIds(?\DateTimeImmutable $now = null): Collection - { - return $this->getCurrentMembers($now)->map( - fn (HouseholdMember $m) => $m->getId() - ); - } - - /** - * Get the persons currently associated to the household. - * - * Return a list of Person, instead of a list of HouseholdMembers - * - * @return Person[] - */ - public function getCurrentPersons(?\DateTimeImmutable $now = null): Collection - { - return $this->getCurrentMembers($now) - ->map(function(HouseholdMember $m) { return $m->getPerson(); }); - } - - public function getNonCurrentMembers(\DateTimeImmutable $now = null): Collection + public function getNonCurrentMembers(?DateTimeImmutable $now = null): Collection { $criteria = new Criteria(); $expr = Criteria::expr(); - $date = $now === null ? (new \DateTimeImmutable('today')) : $now; + $date = null === $now ? (new DateTimeImmutable('today')) : $now; $criteria ->where( @@ -314,17 +336,7 @@ class Household return $this->getMembers()->matching($criteria); } - public function getCurrentMembersByPosition(Position $position, \DateTimeInterface $now = null) - { - $criteria = new Criteria(); - $expr = Criteria::expr(); - - $criteria->where($expr->eq('position', $position)); - - return $this->getCurrentMembers($now)->matching($criteria); - } - - public function getNonCurrentMembersByPosition(Position $position, \DateTimeInterface $now = null) + public function getNonCurrentMembersByPosition(Position $position, ?DateTimeInterface $now = null) { $criteria = new Criteria(); $expr = Criteria::expr(); @@ -334,14 +346,29 @@ class Household return $this->getNonCurrentMembers($now)->matching($criteria); } - public function addMember(HouseholdMember $member): self + public function getNonCurrentMembersWithoutPosition(?DateTimeInterface $now = null) { - if (!$this->members->contains($member)) { - $this->members[] = $member; - $member->setHousehold($this); - } + $criteria = new Criteria(); + $expr = Criteria::expr(); - return $this; + $criteria->where($expr->isNull('position')); + + return $this->getNonCurrentMembers($now)->matching($criteria); + } + + public function getWaitingForBirth(): bool + { + return $this->waitingForBirth; + } + + public function getWaitingForBirthDate(): ?DateTimeImmutable + { + return $this->waitingForBirthDate; + } + + public function removeAddress(Address $address) + { + $this->addresses->removeElement($address); } public function removeMember(HouseholdMember $member): self @@ -356,11 +383,6 @@ class Household return $this; } - public function getCommentMembers(): CommentEmbeddable - { - return $this->commentMembers; - } - public function setCommentMembers(CommentEmbeddable $commentMembers): self { $this->commentMembers = $commentMembers; @@ -368,9 +390,20 @@ class Household return $this; } - public function getWaitingForBirth(): bool + /** + * Force an address starting at the current day + * on the Household. + * + * This will force the startDate's address on today. + * + * Used on household creation. + * + * @Serializer\Groups({"create"}) + */ + public function setForceAddress(Address $address) { - return $this->waitingForBirth; + $address->setValidFrom(new DateTime('today')); + $this->addAddress($address); } public function setWaitingForBirth(bool $waitingForBirth): self @@ -380,12 +413,7 @@ class Household return $this; } - public function getWaitingForBirthDate(): ?\DateTimeImmutable - { - return $this->waitingForBirthDate; - } - - public function setWaitingForBirthDate(?\DateTimeImmutable $waitingForBirthDate): self + public function setWaitingForBirthDate(?DateTimeImmutable $waitingForBirthDate): self { $this->waitingForBirthDate = $waitingForBirthDate; @@ -395,15 +423,34 @@ class Household public function validate(ExecutionContextInterface $context, $payload) { $addresses = $this->getAddresses(); - $cond = True; - for ($i=0; $i < count($addresses) - 1; $i++) { - if ($addresses[$i]->getValidFrom() != $addresses[$i + 1]->getValidTo()) { - $cond = False; + $cond = true; + + for ($i = 0; count($addresses) - 1 > $i; ++$i) { + if ($addresses[$i]->getValidFrom() !== $addresses[$i + 1]->getValidTo()) { + $cond = false; $context->buildViolation('The address are not sequentials. The validFrom date of one address should be equal to the validTo date of the previous address.') ->atPath('addresses') ->addViolation(); } } - dump($cond); + } + + private function buildCriteriaCurrentMembers(?DateTimeImmutable $now = null): Criteria + { + $criteria = new Criteria(); + $expr = Criteria::expr(); + $date = null === $now ? (new DateTimeImmutable('today')) : $now; + + $criteria + ->where($expr->orX( + $expr->isNull('startDate'), + $expr->lte('startDate', $date) + )) + ->andWhere($expr->orX( + $expr->isNull('endDate'), + $expr->gt('endDate', $date) + )); + + return $criteria; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php index 985084623..22c33f85f 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php @@ -1,61 +1,90 @@ id; - } - - public function getPosition(): ?Position - { - return $this->position; - } - - public function setPosition(Position $position): self - { - if ($this->position instanceof Position) { - throw new \LogicException("The position is already set. You cannot change ". - "a position of a membership"); - } - - $this->position = $position; - $this->shareHousehold = $position->getShareHousehold(); - - return $this; - } - - public function getStartDate(): ?\DateTimeImmutable - { - return $this->startDate; - } - - public function setStartDate(\DateTimeImmutable $startDate): self - { - $this->startDate = $startDate; - - return $this; - } - - public function getEndDate(): ?\DateTimeImmutable - { - return $this->endDate; - } - - public function setEndDate(?\DateTimeImmutable $endDate = null): self - { - $this->endDate = $endDate; - - return $this; - } + private ?DateTimeImmutable $startDate = null; public function getComment(): ?string { return $this->comment; } - public function setComment(?string $comment): self + public function getEndDate(): ?DateTimeImmutable { - $this->comment = $comment; + return $this->endDate; + } - return $this; + public function getHousehold(): ?Household + { + return $this->household; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function getPosition(): ?Position + { + return $this->position; } /** @@ -159,38 +136,35 @@ class HouseholdMember return $this->shareHousehold; } - - public function getPerson(): ?Person + public function getStartDate(): ?DateTimeImmutable { - return $this->person; + return $this->startDate; } - public function setPerson(?Person $person): self + public function isCurrent(?DateTimeImmutable $at = null): bool { - if ($this->person instanceof Person) { - throw new \LogicException("You cannot change person ". - "on a membership"); - } + $at = null === $at ? new DateTimeImmutable('now') : $at; - $this->person = $person; - $this->person->addHouseholdParticipation($this); + return $this->getStartDate() < $at && ( + null === $this->getEndDate() || $this->getEndDate() > $at + ); + } + + public function isHolder(): bool + { + return $this->holder; + } + + public function setComment(?string $comment): self + { + $this->comment = $comment; return $this; } - public function getHousehold(): ?Household + public function setEndDate(?DateTimeImmutable $endDate = null): self { - return $this->household; - } - - public function setHousehold(?Household $household): self - { - if ($this->household instanceof Household) { - throw new \LogicException("You cannot change household ". - "on a membership"); - } - - $this->household = $household; + $this->endDate = $endDate; return $this; } @@ -202,17 +176,55 @@ class HouseholdMember return $this; } - public function isHolder(): bool + public function setHousehold(?Household $household): self { - return $this->holder; + if ($this->household instanceof Household) { + throw new LogicException('You cannot change household ' . + 'on a membership'); + } + + $this->household = $household; + + return $this; } - public function isCurrent(\DateTimeImmutable $at = null): bool + public function setPerson(?Person $person): self { - $at = NULL === $at ? new \DateTimeImmutable('now'): $at; + if ($this->person instanceof Person) { + throw new LogicException('You cannot change person ' . + 'on a membership'); + } - return $this->getStartDate() < $at && ( - NULL === $this->getEndDate() || $at < $this->getEndDate() - ); + $this->person = $person; + $this->person->addHouseholdParticipation($this); + + return $this; + } + + public function setPosition(Position $position): self + { + if ($this->position instanceof Position) { + throw new LogicException('The position is already set. You cannot change ' . + 'a position of a membership'); + } + + $this->position = $position; + $this->shareHousehold = $position->getShareHousehold(); + + return $this; + } + + public function setShareHousehold(bool $shareHousehold): self + { + $this->shareHousehold = $shareHousehold; + + return $this; + } + + public function setStartDate(DateTimeImmutable $startDate): self + { + $this->startDate = $startDate; + + return $this; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php b/src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php index 28235b521..3bb204c6f 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php @@ -1,10 +1,20 @@ validFrom; - } - - /** - * The end date of the intersection address/household - * - * (this is not the enddate of the household, not - * the enddate of the address) - */ - public function getValidTo(): ?\DateTimeImmutable - { - return $this->validTo; - } - - public function getPerson(): ?Person - { - return $this->person; + return $this->address; } public function getHousehold(): ?Household @@ -100,8 +87,30 @@ class PersonHouseholdAddress return $this->relation; } - public function getAddress(): ?Address + public function getPerson(): ?Person { - return $this->address; + return $this->person; + } + + /** + * The start date of the intersection address/household. + * + * (this is not the startdate of the household, not + * the startdate of the address) + */ + public function getValidFrom(): ?DateTimeInterface + { + return $this->validFrom; + } + + /** + * The end date of the intersection address/household. + * + * (this is not the enddate of the household, not + * the enddate of the address) + */ + public function getValidTo(): ?DateTimeImmutable + { + return $this->validTo; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php index b1bfef173..378bc0d9f 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php @@ -1,8 +1,16 @@ allowHolder; + } + public function getId(): ?int { return $this->id; @@ -57,11 +71,9 @@ class Position return $this->label; } - public function setLabel(array $label): self + public function getOrdering(): float { - $this->label = $label; - - return $this; + return $this->ordering; } public function getShareHousehold(): bool @@ -69,18 +81,6 @@ class Position return $this->shareHouseHold; } - public function setShareHousehold(bool $shareHouseHold): self - { - $this->shareHouseHold = $shareHouseHold; - - return $this; - } - - public function getAllowHolder(): bool - { - return $this->allowHolder; - } - public function isAllowHolder(): bool { return $this->getAllowHolder(); @@ -93,9 +93,11 @@ class Position return $this; } - public function getOrdering(): float + public function setLabel(array $label): self { - return $this->ordering; + $this->label = $label; + + return $this; } public function setOrdering(float $ordering): self @@ -104,4 +106,11 @@ class Position return $this; } + + public function setShareHousehold(bool $shareHouseHold): self + { + $this->shareHouseHold = $shareHouseHold; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php index 43ae1b09f..56c9368dc 100644 --- a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php +++ b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php @@ -1,92 +1,82 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** - * MaritalStatus + * MaritalStatus. * * @ORM\Entity * @ORM\Table(name="chill_person_marital_status") - * @ORM\HasLifecycleCallbacks() + * @ORM\HasLifecycleCallbacks */ class MaritalStatus { /** * @var string * - * @ORM\Id() + * @ORM\Id * @ORM\Column(type="string", length=7) */ - private $id; + private ?string $id; /** * @var string array - * @ORM\Column(type="json_array") + * @ORM\Column(type="json") */ - private $name; + private array $name; /** - * Get id - * - * @return string + * Get id. */ - public function getId() + public function getId(): string { return $this->id; } /** - * Set id - * - * @param string $id - * @return MaritalStatus - */ - public function setId($id) - { - $this->id = $id; - return $this; - } - - /** - * Set name + * Get name. * - * @param string array $name - * @return MaritalStatus + * @return string array */ - public function setName($name) - { - $this->name = $name; - - return $this; - } - - /** - * Get name - * - * @return string array - */ - public function getName() + public function getName(): array { return $this->name; - } + } + + /** + * Set id. + * + * @return MaritalStatus + */ + public function setId(string $id): self + { + $this->id = $id; + + return $this; + } + + /** + * Set name. + * + * @param string array $name + * + * @return MaritalStatus + */ + public function setName(array $name): self + { + $this->name = $name; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 9aaad8c04..a3524d306 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -1,89 +1,270 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + +namespace Chill\PersonBundle\Entity; + use ArrayIterator; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; -use Chill\MainBundle\Entity\Center; -use Chill\MainBundle\Entity\Country; -use Chill\MainBundle\Entity\User; -use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Entity\Household\Household; -use Chill\PersonBundle\Entity\MaritalStatus; -use Chill\PersonBundle\Entity\Household\HouseholdMember; -use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\Civility; +use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; -use Chill\PersonBundle\Entity\Person\PersonCurrentAddress; -use DateTime; -use Doctrine\ORM\Mapping as ORM; -use Doctrine\Common\Collections\Collection; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Criteria; -use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; -use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress; -use Symfony\Component\Validator\Constraints as Assert; -use Chill\PersonBundle\Validator\Constraints\Person\Birthdate; +use Chill\MainBundle\Entity\HasCenterInterface; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint; -use Chill\PersonBundle\Validator\Constraints\Person\PersonHasCenter; +use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Entity\Household\HouseholdMember; +use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress; +use Chill\PersonBundle\Entity\Person\PersonCurrentAddress; use Chill\PersonBundle\Validator\Constraints\Household\HouseholdMembershipSequential; +use Chill\PersonBundle\Validator\Constraints\Person\Birthdate; +use Chill\PersonBundle\Validator\Constraints\Person\PersonHasCenter; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; +use Doctrine\ORM\Mapping as ORM; +use Exception; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use function count; +use function in_array; /** - * Person Class + * Person Class. * * @ORM\Entity * @ORM\Table(name="chill_person_person", - * indexes={@ORM\Index( - * name="person_names", - * columns={"firstName", "lastName"} - * )}) - * @ORM\HasLifecycleCallbacks() - * @DiscriminatorMap(typeProperty="type", mapping={ - * "person"=Person::class - * }) - * @PersonHasCenter( - * groups={"general", "creation"} - * ) - * @HouseholdMembershipSequential( - * groups={"household_memberships"} - * ) + * indexes={ + * @ORM\Index( + * name="person_names", + * columns={"firstName", "lastName"} + * ), + * @ORM\Index( + * name="person_birthdate", + * columns={"birthdate"} + * ) + * }) + * @ORM\HasLifecycleCallbacks + * @DiscriminatorMap(typeProperty="type", mapping={ + * "person": Person::class + * }) + * @PersonHasCenter( + * groups={"general", "creation"} + * ) + * @HouseholdMembershipSequential( + * groups={"household_memberships"} + * ) */ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateInterface { - /** - * The person's id - * @var integer - * - * @ORM\Id - * @ORM\Column(name="id", type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ - private $id; + public const BOTH_GENDER = 'both'; + + // have days in commun + public const ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD = 2; // where there exist + + public const ERROR_PERIODS_ARE_COLLAPSING = 1; // when two different periods + + public const FEMALE_GENDER = 'woman'; + + public const MALE_GENDER = 'man'; + + public const NO_INFORMATION = 'unknown'; /** - * The person's first name + * Accept receiving email. + * + * @var bool + * + * @ORM\Column(type="boolean", options={"default": false}) + */ + private ?bool $acceptEmail = false; + + /** + * Accept short text message (aka SMS). + * + * @var bool + * + * @ORM\Column(type="boolean", options={"default": false}) + */ + private ?bool $acceptSMS = false; + + /** + * The person's accompanying periods (when the person was accompanied by the center). + * + * @var Collection + * + * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, + * mappedBy="person", + * cascade={"persist", "remove", "merge", "detach"}) + */ + private $accompanyingPeriodParticipations; + + /** + * The accompanying period requested by the Person. + * + * @ORM\OneToMany(targetEntity=AccompanyingPeriod::class, + * mappedBy="requestorPerson") + * + * @var AccompanyingPeriod[]|Collection + */ + private Collection $accompanyingPeriodRequested; + + /** + * Addresses. + * + * @var Collection + * + * @ORM\ManyToMany( + * targetEntity="Chill\MainBundle\Entity\Address", + * cascade={"persist", "remove", "merge", "detach"}) + * @ORM\JoinTable(name="chill_person_persons_to_addresses") + * @ORM\OrderBy({"validFrom": "DESC"}) + */ + private $addresses; + + /** + * @var Collection + * + * @ORM\OneToMany( + * targetEntity="Chill\PersonBundle\Entity\PersonAltName", + * mappedBy="person", + * cascade={"persist", "remove", "merge", "detach"}, + * orphanRemoval=true) + */ + private $altNames; + + /** + * The person's birthdate. + * + * @var DateTime + * + * @ORM\Column(type="date", nullable=true) + * @Assert\Date( + * groups={"general", "creation"} + * ) + * @Birthdate( + * groups={"general", "creation"} + * ) + */ + private $birthdate; + + /** + * The person's center. + * + * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center") + */ + private ?Center $center = null; + + /** + * Array where customfield's data are stored. + * + * @var array + * + * @ORM\Column(type="json") + */ + private $cFData; + + /** + * The marital status of the person. + * + * @var Civility + * + * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Civility") + * @ORM\JoinColumn(nullable=true) + */ + private $civility; + + /** + * Contact information for contacting the person. + * + * @var string + * + * @ORM\Column(type="text", nullable=true) + */ + private $contactInfo = ''; + + /** + * The person's country of birth. + * + * @var Country + * + * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country") + * + * sf4 check: option inversedBy="birthsIn" return error mapping !! + * + * @ORM\JoinColumn(nullable=true) + */ + private $countryOfBirth; + + /** + * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) + */ + private $createdAt; + + /** + * @ORM\ManyToOne(targetEntity=User::class) + * @ORM\JoinColumn(nullable=true) + */ + private $createdBy; + + /** + * Cache the computation of household. + */ + private array $currentHouseholdAt = []; + + /** + * The current person address. + * + * This is computed through database and is optimized on database side. + * + * @ORM\OneToOne(targetEntity=PersonCurrentAddress::class, mappedBy="person") + */ + private ?PersonCurrentAddress $currentPersonAddress = null; + + /** + * The person's deathdate. + * + * @var DateTimeImmutable + * + * @ORM\Column(type="date_immutable", nullable=true) + * @Assert\Date( + * groups={"general", "creation"} + * ) + * @Assert\GreaterThan(propertyPath="birthDate") + * @Assert\LessThanOrEqual("today") + */ + private ?DateTimeImmutable $deathdate = null; + + /** + * The person's email. + * + * @var string + * + * @ORM\Column(type="text", nullable=true) + * @Assert\Email( + * checkMX=true, + * groups={"general", "creation"} + * ) + */ + private $email = ''; + + /** + * The person's first name. + * * @var string * * @ORM\Column(type="string", length=255) @@ -98,7 +279,64 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI private $firstName; /** - * The person's last name + * fullname canonical. Read-only field, which is calculated by + * the database. + * + * @var string + * + * @ORM\Column(type="text", nullable=true) + */ + private $fullnameCanonical; + + /** + * The person's gender. + * + * @var string + * + * @ORM\Column(type="string", length=9, nullable=true) + * @Assert\NotNull( + * groups={"general", "creation"} + * ) + */ + private $gender; + + /** + * Comment on gender. + * + * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="genderComment_") + */ + private CommentEmbeddable $genderComment; + + /** + * Read-only field, computed by the database. + * + * @ORM\OneToMany( + * targetEntity=PersonHouseholdAddress::class, + * mappedBy="person" + * ) + */ + private Collection $householdAddresses; + + /** + * @ORM\OneToMany( + * targetEntity=HouseholdMember::class, + * mappedBy="person" + * ) + */ + private Collection $householdParticipations; + + /** + * The person's id. + * + * @ORM\Id + * @ORM\Column(name="id", type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + */ + private ?int $id = null; + + /** + * The person's last name. + * * @var string * * @ORM\Column(type="string", length=255) @@ -113,63 +351,61 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI private $lastName; /** - * @var Collection + * The marital status of the person. * - * @ORM\OneToMany( - * targetEntity="Chill\PersonBundle\Entity\PersonAltName", - * mappedBy="person", - * cascade={"persist", "remove", "merge", "detach"}, - * orphanRemoval=true) + * @var MaritalStatus + * + * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\MaritalStatus") + * @ORM\JoinColumn(nullable=true) */ - private $altNames; + private $maritalStatus; /** - * The person's birthdate - * @var \DateTime + * Comment on marital status. + * + * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="maritalStatusComment_") + */ + private CommentEmbeddable $maritalStatusComment; + + /** + * The date of the last marital status change of the person. + * + * @var DateTime * * @ORM\Column(type="date", nullable=true) * @Assert\Date( * groups={"general", "creation"} * ) - * @Birthdate( - * groups={"general", "creation"} - * ) */ - private $birthdate; + private ?DateTime $maritalStatusDate = null; /** - * The person's deathdate - * @var \DateTimeImmutable + * A remark over the person. * - * @ORM\Column(type="date_immutable", nullable=true) - * @Assert\Date( - * groups={"general", "creation"} - * ) - */ - private ?\DateTimeImmutable $deathdate = null; - - /** - * The person's place of birth * @var string * - * @ORM\Column(type="string", length=255, name="place_of_birth") + * @ORM\Column(type="text") */ - private $placeOfBirth = ''; + private $memo = ''; // TO-CHANGE in remark /** - * The person's country of birth - * @var Country + * The person's mobile phone number. * - * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country") - * - * sf4 check: option inversedBy="birthsIn" return error mapping !! - * - * @ORM\JoinColumn(nullable=true) + * @ORM\Column(type="text") + * @Assert\Regex( + * pattern="/^([\+{1}])([0-9\s*]{4,20})$/", + * groups={"general", "creation"} + * ) + * @PhonenumberConstraint( + * type="mobile", + * groups={"general", "creation"} + * ) */ - private $countryOfBirth; + private string $mobilenumber = ''; /** - * The person's nationality + * The person's nationality. + * * @var Country * * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country") @@ -181,106 +417,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI private $nationality; /** - * The person's gender - * @var string + * Number of children. * - * @ORM\Column(type="string", length=9, nullable=true) - * @Assert\NotNull( - * groups={"general", "creation"} - * ) - */ - private $gender; - - const MALE_GENDER = 'man'; - const FEMALE_GENDER = 'woman'; - const BOTH_GENDER = 'both'; - const NO_INFORMATION = 'unknown'; - - /** - * Comment on gender - * @var CommentEmbeddable - * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="genderComment_") - */ - private CommentEmbeddable $genderComment; - - /** - * The marital status of the person - * @var MaritalStatus + * @var int * - * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\MaritalStatus") - * @ORM\JoinColumn(nullable=true) + * @ORM\Column(type="integer", nullable=true) */ - private $maritalStatus; - - /** - * The date of the last marital status change of the person - * @var \DateTime - * - * @ORM\Column(type="date", nullable=true) - * @Assert\Date( - * groups={"general", "creation"} - * ) - */ - private ?\DateTime $maritalStatusDate; - - /** - * Comment on marital status - * @var CommentEmbeddable - * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="maritalStatusComment_") - */ - private CommentEmbeddable $maritalStatusComment; - - /** - * Contact information for contacting the person - * @var string - * - * @ORM\Column(type="text", nullable=true) - */ - private $contactInfo = ''; - - /** - * The person's email - * @var string - * - * @ORM\Column(type="text", nullable=true) - * @Assert\Email( - * checkMX=true, - * groups={"general", "creation"} - * ) - */ - private $email = ''; - - /** - * The person's phonenumber - * @var string - * - * @ORM\Column(type="text", length=40, nullable=true) - * @Assert\Regex( - * pattern="/^([\+{1}])([0-9\s*]{4,20})$/", - * groups={"general", "creation"} - * ) - * @PhonenumberConstraint( - * type="landline", - * groups={"general", "creation"} - * ) - */ - private $phonenumber = ''; - - /** - * The person's mobile phone number - * @var string - * - * @ORM\Column(type="text", length=40, nullable=true) - * @Assert\Regex( - * pattern="/^([\+{1}])([0-9\s*]{4,20})$/", - * groups={"general", "creation"} - * ) - * @PhonenumberConstraint( - * type="mobile", - * groups={"general", "creation"} - * ) - */ - private $mobilenumber = ''; + private ?int $numberOfChildren = null; /** * @var Collection @@ -299,7 +442,49 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI private $otherPhoneNumbers; /** - * The person's spoken languages + * @ORM\OneToMany( + * targetEntity=AccompanyingPeriod::class, + * mappedBy="personLocation" + * ) + */ + private Collection $periodLocatedOn; + + /** + * The person's phonenumber. + * + * @ORM\Column(type="text") + * @Assert\Regex( + * pattern="/^([\+{1}])([0-9\s*]{4,20})$/", + * groups={"general", "creation"} + * ) + * @PhonenumberConstraint( + * type="landline", + * groups={"general", "creation"} + * ) + */ + private string $phonenumber = ''; + + /** + * The person's place of birth. + * + * @var string + * + * @ORM\Column(type="string", length=255, name="place_of_birth") + */ + private $placeOfBirth = ''; + + /** + * @var bool + * + * @deprecated + * + * @ORM\Column(type="boolean") + */ + private $proxyAccompanyingPeriodOpenState = false; //TO-DELETE ? + + /** + * The person's spoken languages. + * * @var ArrayCollection * * @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\Language") @@ -311,166 +496,17 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI */ private $spokenLanguages; - /** - * The person's center - * @var Center - * - * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center") - */ - private $center; - - /** - * The person's accompanying periods (when the person was accompanied by the center) - * @var Collection - * - * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, - * mappedBy="person", - * cascade={"persist", "remove", "merge", "detach"}) - */ - private $accompanyingPeriodParticipations; - - /** - * The accompanying period requested by the Person - * - * @ORM\OneToMany(targetEntity=AccompanyingPeriod::class, - * mappedBy="requestorPerson") - * @var Collection|AccompanyingPeriod[] - */ - private Collection $accompanyingPeriodRequested; - - /** - * A remark over the person - * @var string - * - * @ORM\Column(type="text") - */ - private $memo = ''; // TO-CHANGE in remark - - - /** - * Accept short text message (aka SMS) - * @var boolean - * - * @ORM\Column(type="boolean", options={"default" : false}) - */ - private ?bool $acceptSMS = false; - - /** - * Accept receiving email - * @var boolean - * - * @ORM\Column(type="boolean", options={"default" : false}) - */ - private ?bool $acceptEmail = false; - - /** - * Number of children - * @var int - * - * @ORM\Column(type="integer", nullable=true) - */ - private ?int $numberOfChildren = null; - - /** - * @ORM\ManyToOne(targetEntity=User::class) - * @ORM\JoinColumn(nullable=true) - */ - private $createdBy; - /** * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) */ - private \DateTimeInterface $createdAt; + private $updatedAt; /** * @ORM\ManyToOne( - * targetEntity=User::class + * targetEntity=User::class * ) */ - private User $updatedBy; - - /** - * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) - */ - private \DateTimeInterface $updatedAt; - - /** - * @var boolean - * @deprecated - * - * @ORM\Column(type="boolean") - */ - private $proxyAccompanyingPeriodOpenState = false; //TO-DELETE ? - - /** - * Array where customfield's data are stored - * @var array - * - * @ORM\Column(type="json_array") - */ - private $cFData; - - /** - * Addresses - * @var Collection - * - * @ORM\ManyToMany( - * targetEntity="Chill\MainBundle\Entity\Address", - * cascade={"persist", "remove", "merge", "detach"}) - * @ORM\JoinTable(name="chill_person_persons_to_addresses") - * @ORM\OrderBy({"validFrom" = "DESC"}) - */ - private $addresses; - - /** - * The current person address. - * - * This is computed through database and is optimized on database side. - * - * @var PersonCurrentAddress|null - * @ORM\OneToOne(targetEntity=PersonCurrentAddress::class, mappedBy="person") - */ - private ?PersonCurrentAddress $currentPersonAddress = null; - - /** - * fullname canonical. Read-only field, which is calculated by - * the database. - * @var string - * - * @ORM\Column(type="text", nullable=true) - */ - private $fullnameCanonical; - - /** - * @ORM\OneToMany( - * targetEntity=HouseholdMember::class, - * mappedBy="person" - * ) - */ - private Collection $householdParticipations; - - /** - * Cache the computation of household - */ - private array $currentHouseholdAt = []; - - /** - * Read-only field, computed by the database - * - * @ORM\OneToMany( - * targetEntity=PersonHouseholdAddress::class, - * mappedBy="person" - * ) - */ - private Collection $householdAddresses; - - /** - * @ORM\OneToMany( - * targetEntity=AccompanyingPeriod::class, - * mappedBy="personLocation" - * ) - */ - private Collection $periodLocatedOn; + private $updatedBy; /** * Person constructor. @@ -491,30 +527,15 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI } /** - * This private function scan accompanyingPeriodParticipations Collection, - * searching for a given AccompanyingPeriod + * @return string */ - private function participationsContainAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): ?AccompanyingPeriodParticipation + public function __toString() { - foreach ($this->accompanyingPeriodParticipations as $participation) { - /** @var AccompanyingPeriodParticipation $participation */ - if ($accompanyingPeriod === $participation->getAccompanyingPeriod()) { - return $participation; - }} - - return null; + return $this->getLabel(); } /** - * This public function is the same but return only true or false - */ - public function containsAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool - { - return ($this->participationsContainAccompanyingPeriod($accompanyingPeriod)) ? false : true; - } - - /** - * Add AccompanyingPeriodParticipation + * Add AccompanyingPeriodParticipation. * * @uses AccompanyingPeriod::addPerson */ @@ -527,262 +548,21 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI } /** - * Remove AccompanyingPeriod - */ - public function removeAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod) : void - { - $participation = $this->participationsContainAccompanyingPeriod($accompanyingPeriod); - - if (! null === $participation) { - $participation->setEndDate(\DateTimeImmutable::class); - $this->accompanyingPeriodParticipations->removeElement($participation); - } - } - - /** - * set the Person file as open at the given date. - * - * For updating a opening's date, you should update AccompanyingPeriod instance - * directly. - * - * For closing a file, @see this::close - * - * To check if the Person and its accompanying period is consistent, use validation. - */ - public function open(AccompanyingPeriod $accompanyingPeriod) : void - { - $this->proxyAccompanyingPeriodOpenState = true; - $this->addAccompanyingPeriod($accompanyingPeriod); - } - - /** - * Set the Person file as closed at the given date. - * - * For update a closing date, you should update AccompanyingPeriod instance - * directly. - * - * To check if the Person and its accompanying period are consistent, use validation. - * - * @throws \Exception if two lines of the accompanying period are open. - */ - public function close(AccompanyingPeriod $accompanyingPeriod = null) : void - { - $this->proxyAccompanyingPeriodOpenState = false; - } - - /** - * Return the opened accompanying period. - */ - public function getOpenedAccompanyingPeriod() : ?AccompanyingPeriod - { - if ($this->isOpen() === false) { - return null; - } - - foreach ($this->accompanyingPeriodParticipations as $participation) { - /** @var AccompanyingPeriodParticipation $participation */ - if ($participation->getAccompanyingPeriod()->isOpen()) { - return $participation->getAccompanyingPeriod(); - } - } - - return null; - } - - /** - * Returns the opened accompanying period. - * - * @deprecated since 1.1 use `getOpenedAccompanyingPeriod instead - */ - public function getCurrentAccompanyingPeriod() : ?AccompanyingPeriod - { - return $this->getOpenedAccompanyingPeriod(); - } - - /** - * Get AccompanyingPeriods array - */ - public function getAccompanyingPeriods(): array - { - $accompanyingPeriods = []; - foreach ($this->accompanyingPeriodParticipations as $participation) - { - /** @var AccompanyingPeriodParticipation $participation */ - $accompanyingPeriods[] = $participation->getAccompanyingPeriod(); - } - return $accompanyingPeriods; - } - - /** - * Get AccompanyingPeriodParticipations Collection - * - * @return AccompanyingPeriodParticipation[]|Collection - */ - public function getAccompanyingPeriodParticipations(): Collection - { - return $this->accompanyingPeriodParticipations; - } - - /** - * Return a collection of participation, where the participation - * is still opened, not a draft, and the period is still opened - * - * @return AccompanyingPeriodParticipation[]|Collection - */ - public function getOpenedParticipations(): Collection - { - // create a criteria for filtering easily - $criteria = Criteria::create(); - $criteria - ->andWhere(Criteria::expr()->eq('endDate', NULL)) - ->orWhere(Criteria::expr()->gt('endDate', new \DateTime('now'))) - ; - - return $this->getAccompanyingPeriodParticipations() - ->matching($criteria) - ->filter(function (AccompanyingPeriodParticipation $app) { - $period = $app->getAccompanyingPeriod(); - return ( - NULL === $period->getClosingDate() - || new \DateTime('now') < $period->getClosingDate() - ) - && AccompanyingPeriod::STEP_DRAFT !== $period->getStep(); - }); - } - - /** - * Get the accompanying periods of a give person with the chronological order. - */ - public function getAccompanyingPeriodsOrdered(): array - { - $periods = $this->getAccompanyingPeriods(); - - //order by date : - usort($periods, function($a, $b) { - $dateA = $a->getOpeningDate(); - $dateB = $b->getOpeningDate(); - - if ($dateA == $dateB) { - $dateEA = $a->getClosingDate(); - $dateEB = $b->getClosingDate(); - - if ($dateEA == $dateEB) { - return 0; - } - - if ($dateEA < $dateEB) { - return -1; - } else { - return +1; - } - } - - if ($dateA < $dateB) { - return -1 ; - } else { - return 1; - } - }); - - return $periods; - } - - /** - * Check if the person is opened - */ - public function isOpen() : bool - { - foreach ($this->getAccompanyingPeriods() as $period) { - if ($period->isOpen()) { - return true; - } - } - - return false; - } - - /** - * Get id - * - * @return integer - */ - public function getId() - { - return $this->id; - } - - /** - * Set firstName - * - * @param string $firstName - * @return Person - */ - public function setFirstName($firstName) - { - $this->firstName = $firstName; - - return $this; - } - - /** - * Get firstName - * - * @return string - */ - public function getFirstName() - { - return $this->firstName; - } - - /** - * Set lastName - * - * @param string $lastName - * @return Person - */ - public function setLastName($lastName) - { - $this->lastName = $lastName; - - return $this; - } - - /** - * Get lastName - * - * @return string - */ - public function getLastName() - { - return $this->lastName; - } - - /** - * @return Collection - */ - public function getAltNames(): Collection - { - return $this->altNames; - } - - /** - * @param Collection $altNames * @return $this */ - public function setAltNames(Collection $altNames) + public function addAddress(Address $address) { - $this->altNames = $altNames; + $this->addresses[] = $address; return $this; } /** - * @param PersonAltName $altName * @return $this */ public function addAltName(PersonAltName $altName) { - if (FALSE === $this->altNames->contains($altName)) { + if (false === $this->altNames->contains($altName)) { $this->altNames->add($altName); $altName->setPerson($this); } @@ -790,401 +570,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this; } - /** - * @param PersonAltName $altName - * @return $this - */ - public function removeAltName(PersonAltName $altName) + public function addHouseholdParticipation(HouseholdMember $member): self { - if ($this->altNames->contains($altName)) { - $altName->setPerson(null); - $this->altNames->removeElement($altName); - } + $this->householdParticipations[] = $member; return $this; } /** - * Set birthdate - * - * @param \DateTime $birthdate - * @return Person - */ - public function setBirthdate($birthdate) - { - $this->birthdate = $birthdate; - - return $this; - } - - /** - * Get birthdate - * - * @return \DateTime - */ - public function getBirthdate() - { - return $this->birthdate; - } - - /** - * Return the age of a person, calculated at the date 'now'. - * - * If the person has a deathdate, calculate the age at the deathdate. - * - * @param string $at a valid string to create a DateTime - * @return int|null - */ - public function getAge($at = 'now'): ?int - { - if ($this->birthdate instanceof \DateTimeInterface) { - if ($this->deathdate instanceof \DateTimeInterface) { - return date_diff($this->birthdate, $this->deathdate)->format("%y"); - } - return date_diff($this->birthdate, date_create($at))->format("%y"); - } - - return null; - } - - /** - * Set placeOfBirth - * - * @param string $placeOfBirth - * @return Person - */ - public function setPlaceOfBirth($placeOfBirth) - { - if ($placeOfBirth === null) { - $placeOfBirth = ''; - } - - $this->placeOfBirth = $placeOfBirth; - - return $this; - } - - /** - * Get placeOfBirth - * - * @return string - */ - public function getPlaceOfBirth() - { - return $this->placeOfBirth; - } - - /** - * Set gender - * - * @param string $gender - * @return Person - */ - public function setGender($gender) - { - $this->gender = $gender; - - return $this; - } - - /** - * Get gender - * - * @return string - */ - public function getGender() - { - return $this->gender; - } - - /** - * return gender as a Numeric form. - * This is used for translations - * @return int - */ - public function getGenderNumeric() - { - if ($this->getGender() == self::FEMALE_GENDER) { - return 1; - } else { - return 0; - } - } - - /** - * Set memo - * - * @param string $memo - * @return Person - */ - public function setMemo($memo) - { - if ($memo === null) { - $memo = ''; - } - - if ($this->memo !== $memo) { - $this->memo = $memo; - } - - return $this; - } - - /** - * Get memo - * - * @return string - */ - public function getMemo() - { - return $this->memo; - } - - /** - * Set maritalStatus - * - * @param MaritalStatus $maritalStatus - * @return Person - */ - public function setMaritalStatus(MaritalStatus $maritalStatus = null) - { - $this->maritalStatus = $maritalStatus; - return $this; - } - - /** - * Get maritalStatus - * - * @return MaritalStatus - */ - public function getMaritalStatus() - { - return $this->maritalStatus; - } - - /** - * Set contactInfo - * - * @param string $contactInfo - * @return Person - */ - public function setcontactInfo($contactInfo) - { - if ($contactInfo === null) { - $contactInfo = ''; - } - - $this->contactInfo = $contactInfo; - - return $this; - } - - /** - * Get contactInfo - * - * @return string - */ - public function getcontactInfo() - { - return $this->contactInfo; - } - - /** - * Set email - * - * @param string $email - * @return Person - */ - public function setEmail($email) - { - if ($email === null) { - $email = ''; - } - - $this->email = $email; - - return $this; - } - - /** - * Get email - * - * @return string - */ - public function getEmail() - { - return $this->email; - } - - /** - * Set countryOfBirth - * - * @param Chill\MainBundle\Entity\Country $countryOfBirth - * @return Person - */ - public function setCountryOfBirth(Country $countryOfBirth = null) - { - $this->countryOfBirth = $countryOfBirth; - return $this; - } - - /** - * Get countryOfBirth - * - * @return Chill\MainBundle\Entity\Country - */ - public function getCountryOfBirth() - { - return $this->countryOfBirth; - } - - /** - * Set nationality - * - * @param Chill\MainBundle\Entity\Country $nationality - * @return Person - */ - public function setNationality(Country $nationality = null) - { - $this->nationality = $nationality; - - return $this; - } - - /** - * Get nationality - * - * @return Chill\MainBundle\Entity\Country - */ - public function getNationality() - { - return $this->nationality; - } - - /** - * @return string - */ - public function getLabel() - { - return $this->getFirstName()." ".$this->getLastName(); - } - - /** - * Get center - * - * @return Center - */ - public function getCenter() - { - return $this->center; - } - - /** - * Set the center - * - * @param Center $center - * @return \Chill\PersonBundle\Entity\Person - */ - public function setCenter(Center $center) - { - $this->center = $center; - return $this; - } - - /** - * Set cFData - * - * @param array $cFData - * - * @return Report - */ - public function setCFData($cFData) - { - $this->cFData = $cFData; - - return $this; - } - - /** - * Get cFData - * - * @return array - */ - public function getCFData() - { - if ($this->cFData === null) { - $this->cFData = []; - } - return $this->cFData; - } - - /** - * Set phonenumber - * - * @param string $phonenumber - * @return Person - */ - public function setPhonenumber($phonenumber = '') - { - $this->phonenumber = $phonenumber; - - return $this; - } - - /** - * Get phonenumber - * - * @return string - */ - public function getPhonenumber() - { - return $this->phonenumber; - } - - /** - * Set mobilenumber - * - * @param string $mobilenumber - * @return Person - */ - public function setMobilenumber($mobilenumber = '') - { - $this->mobilenumber = $mobilenumber; - - return $this; - } - - /** - * Get mobilenumber - * - * @return string - */ - public function getMobilenumber() - { - return $this->mobilenumber; - } - - /** - * @return Collection - */ - public function getOtherPhoneNumbers(): Collection - { - return $this->otherPhoneNumbers; - } - - /** - * @param Collection $otherPhoneNumbers - * @return $this - */ - public function setOtherPhoneNumbers(Collection $otherPhoneNumbers) - { - $this->otherPhoneNumbers = $otherPhoneNumbers; - - return $this; - } - - /** - * @param PersonPhone $otherPhoneNumber * @return $this */ public function addOtherPhoneNumber(PersonPhone $otherPhoneNumber) @@ -1197,189 +590,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this; } - /** - * @param PersonPhone $otherPhoneNumber - * @return $this - */ - public function removeOtherPhoneNumber(PersonPhone $otherPhoneNumber) - { - if ($this->otherPhoneNumbers->contains($otherPhoneNumber)) { - $this->otherPhoneNumbers->removeElement($otherPhoneNumber); - } - - return $this; - } - - /** - * @return string - */ - public function __toString() - { - return $this->getLabel(); - } - - /** - * Set spokenLanguages - * - * @param type $spokenLanguages - * @return Person - */ - public function setSpokenLanguages($spokenLanguages) - { - $this->spokenLanguages = $spokenLanguages; - - return $this; - } - - /** - * Get spokenLanguages - * - * @return ArrayCollection - */ - public function getSpokenLanguages() - { - return $this->spokenLanguages; - } - - /** - * @param Address $address - * @return $this - */ - public function addAddress(Address $address) - { - $this->addresses[] = $address; - - return $this; - } - - /** - * @param Address $address - */ - public function removeAddress(Address $address) - { - $this->addresses->removeElement($address); - } - - /** - * By default, the addresses are ordered by date, descending (the most - * recent first) - */ - public function getAddresses(): Collection - { - return $this->addresses; - } - - /** - * @deprecated Use `getCurrentPersonAddress` instead - * @param DateTime|null $from - * @return false|mixed|null - * @throws \Exception - */ - public function getLastAddress(DateTime $from = null) - { - return $this->getCurrentPersonAddress($from); - } - - /** - * get the address associated with the person at the given date - * - * @param DateTime|null $at - * @return Address|null - * @throws \Exception - */ - public function getCurrentPersonAddress(?\DateTime $at = null): ?Address - { - $at ??= new DateTime('now'); - - /** @var ArrayIterator $addressesIterator */ - $addressesIterator = $this->getAddresses() - ->filter(static fn (Address $address): bool => $address->getValidFrom() <= $at) - ->getIterator(); - - $addressesIterator->uasort( - static fn (Address $left, Address $right): int => $right->getValidFrom() <=> $left->getValidFrom() - ); - - return [] === ($addresses = iterator_to_array($addressesIterator)) ? - null : - current($addresses); - } - - /** - * Validation callback that checks if the accompanying periods are valid - * - * This method add violation errors. - * - * @Assert\Callback( - * groups={"accompanying_period_consistent"} - * ) - */ - public function isAccompanyingPeriodValid(ExecutionContextInterface $context) - { - $r = $this->checkAccompanyingPeriodsAreNotCollapsing(); - - if ($r !== true) { - if ($r['result'] === self::ERROR_PERIODS_ARE_COLLAPSING) { - $context->buildViolation('Two accompanying periods have days in commun') - ->atPath('accompanyingPeriods') - ->addViolation(); - } - - if ($r['result'] === self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD) { - $context->buildViolation('A period is opened and a period is added after it') - ->atPath('accompanyingPeriods') - ->addViolation(); - } - } - } - - /** - * Return true if the person has two addresses with the - * same validFrom date (in format 'Y-m-d') - */ - public function hasTwoAdressWithSameValidFromDate() - { - $validYMDDates = array(); - - foreach ($this->addresses as $ad) { - $validDate = $ad->getValidFrom()->format('Y-m-d'); - - if (in_array($validDate, $validYMDDates)) { - return true; - } - $validYMDDates[] = $validDate; - } - - return false; - } - - /** - * Validation callback that checks if the addresses are valid (do not have - * two addresses with the same validFrom date) - * - * This method add violation errors. - * - * @Assert\Callback( - * groups={"addresses_consistent"} - * ) - */ - public function isAddressesValid(ExecutionContextInterface $context) - { - if ($this->hasTwoAdressWithSameValidFromDate()) { - $context - ->buildViolation('Two addresses has the same validFrom date') - ->atPath('addresses') - ->addViolation() - ; - } - } - - - const ERROR_PERIODS_ARE_COLLAPSING = 1; // when two different periods - // have days in commun - const ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD = 2; // where there exist - // a period opened and another one after it - /** * Function used for validation that check if the accompanying periods of * the person are not collapsing (i.e. have not shared days) or having @@ -1391,325 +601,58 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI public function checkAccompanyingPeriodsAreNotCollapsing() { $periods = $this->getAccompanyingPeriodsOrdered(); - $periodsNbr = sizeof($periods); + $periodsNbr = count($periods); $i = 0; - while($i < $periodsNbr - 1) { + while ($periodsNbr - 1 > $i) { $periodI = $periods[$i]; $periodAfterI = $periods[$i + 1]; - if($periodI->isOpen()) { - return array( + if ($periodI->isOpen()) { + return [ 'result' => self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD, 'dateOpening' => $periodAfterI->getOpeningDate(), 'dateClosing' => $periodAfterI->getClosingDate(), - 'date' => $periodI->getOpeningDate() - ); - } elseif ($periodI->getClosingDate() >= $periodAfterI->getOpeningDate()) { - return array( + 'date' => $periodI->getOpeningDate(), + ]; + } + + if ($periodI->getClosingDate() >= $periodAfterI->getOpeningDate()) { + return [ 'result' => self::ERROR_PERIODS_ARE_COLLAPSING, 'dateOpening' => $periodI->getOpeningDate(), 'dateClosing' => $periodI->getClosingDate(), - 'date' => $periodAfterI->getOpeningDate() - ); + 'date' => $periodAfterI->getOpeningDate(), + ]; } - $i++; + ++$i; } return true; } - public function getFullnameCanonical() : string + /** + * Set the Person file as closed at the given date. + * + * For update a closing date, you should update AccompanyingPeriod instance + * directly. + * + * To check if the Person and its accompanying period are consistent, use validation. + * + * @throws Exception if two lines of the accompanying period are open. + */ + public function close(?AccompanyingPeriod $accompanyingPeriod = null): void { - return $this->fullnameCanonical; - } - - public function setFullnameCanonical($fullnameCanonical) : Person - { - $this->fullnameCanonical = $fullnameCanonical; - return $this; - } - - public function addHouseholdParticipation(HouseholdMember $member): self - { - $this->householdParticipations[] = $member; - - return $this; - } - - public function getHouseholdParticipations(): Collection - { - return $this->householdParticipations; + $this->proxyAccompanyingPeriodOpenState = false; } /** - * Get participation where the person does share the household. - * - * Order by startDate, desc + * This public function is the same but return only true or false. */ - public function getHouseholdParticipationsShareHousehold(): Collection + public function containsAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool { - $criteria = new Criteria(); - $expr = Criteria::expr(); - - $criteria - ->where( - $expr->eq('shareHousehold', true) - ) - ->orderBy(['startDate' => Criteria::DESC]) - ; - - return $this->getHouseholdParticipations() - ->matching($criteria) - ; - } - - /** - * Get participation where the person does not share the household. - * - * Order by startDate, desc - */ - public function getHouseholdParticipationsNotShareHousehold(): Collection - { - $criteria = new Criteria(); - $expr = Criteria::expr(); - - $criteria - ->where( - $expr->eq('shareHousehold', false) - ) - ->orderBy(['startDate' => Criteria::DESC]) - ; - - return $this->getHouseholdParticipations() - ->matching($criteria) - ; - } - - public function getCurrentHousehold(?\DateTimeImmutable $at = null): ?Household - { - $participation = $this->getCurrentHouseholdParticipationShareHousehold($at); - - return $participation instanceof HouseholdMember ? - $participation->getHousehold() - : null; - } - - public function getCurrentHouseholdParticipationShareHousehold(?\DateTimeImmutable $at = null): ?HouseholdMember - { - $criteria = new Criteria(); - $expr = Criteria::expr(); - $date = NULL === $at ? new \DateTimeImmutable('today') : $at; - $datef = $date->format('Y-m-d'); - - if ( - NULL !== ($this->currentHouseholdParticipationAt[$datef] ?? NULL)) { - return $this->currentHouseholdParticipationAt[$datef]; - } - - $criteria - ->where( - $expr->andX( - $expr->lte('startDate', $date), - $expr->orX( - $expr->isNull('endDate'), - $expr->gt('endDate', $date) - ), - $expr->eq('shareHousehold', true) - ) - ); - - $participations = $this->getHouseholdParticipations() - ->matching($criteria) - ; - - return $participations->count() > 0 ? - $this->currentHouseholdParticipationAt[$datef] = $participations->first() - : null; - } - - public function isSharingHousehold(?\DateTimeImmutable $at = null): bool - { - return NULL !== $this->getCurrentHousehold($at); - } - - public function getHouseholdAddresses(): Collection - { - return $this->householdAddresses; - } - - public function getCurrentHouseholdAddress(?\DateTimeImmutable $at = null): ?Address - { - if ( - NULL === $at - || - $at->format('Ymd') === (new \DateTime('today'))->format('Ymd') - ) { - return $this->currentPersonAddress instanceof PersonCurrentAddress - ? $this->currentPersonAddress->getAddress() : NULL; - } - - // if not now, compute the date from history - $criteria = new Criteria(); - $expr = Criteria::expr(); - - $criteria->where( - $expr->lte('validFrom', $at) - ) - ->andWhere( - $expr->orX( - $expr->isNull('validTo'), - $expr->gte('validTo', $at) - ) - ); - - $addrs = $this->getHouseholdAddresses() - ->matching($criteria) - ; - - if ($addrs->count() > 0) { - return $addrs->first()->getAddress(); - } else { - return null; - } - } - - public function hasCurrentHouseholdAddress(?\DateTimeImmutable $at = null): bool - { - return null !== $this->getCurrentHouseholdAddress($at); - } - - public function getGenderComment(): CommentEmbeddable - { - return $this->genderComment; - } - - public function setGenderComment(CommentEmbeddable $genderComment): self - { - $this->genderComment = $genderComment; - - return $this; - } - - public function getMaritalStatusComment(): CommentEmbeddable - { - return $this->maritalStatusComment; - } - - public function setMaritalStatusComment(CommentEmbeddable $maritalStatusComment): self - { - $this->maritalStatusComment = $maritalStatusComment; - - return $this; - } - - public function getDeathdate(): ?\DateTimeInterface - { - return $this->deathdate; - } - - public function setDeathdate(?\DateTimeInterface $deathdate): self - { - $this->deathdate = $deathdate; - - return $this; - } - - public function getMaritalStatusDate(): ?\DateTimeInterface - { - return $this->maritalStatusDate; - } - - public function setMaritalStatusDate(?\DateTimeInterface $maritalStatusDate): self - { - $this->maritalStatusDate = $maritalStatusDate; - - return $this; - } - - public function getAcceptSMS(): ?bool - { - return $this->acceptSMS; - } - - public function setAcceptSMS(bool $acceptSMS): self - { - $this->acceptSMS = $acceptSMS; - - return $this; - } - - public function getAcceptEmail(): ?bool - { - return $this->acceptEmail; - } - - public function setAcceptEmail(bool $acceptEmail): self - { - $this->acceptEmail = $acceptEmail; - - return $this; - } - - public function getNumberOfChildren(): ?int - { - return $this->numberOfChildren; - } - - public function setNumberOfChildren(int $numberOfChildren): self - { - $this->numberOfChildren = $numberOfChildren; - - return $this; - } - - /** - * @return AccompanyingPeriod[]|Collection - */ - public function getAccompanyingPeriodRequested(): Collection - { - return $this->accompanyingPeriodRequested; - } - - /** - * Return a list of all accompanying period where the person is involved: - * - * * as requestor; - * * as participant, only for opened participation; - * - * @param bool $asParticipantOpen add participation which are still opened - * @param bool $asRequestor add accompanying period where the person is requestor - * @return AccompanyingPeriod[]|Collection - */ - public function getAccompanyingPeriodInvolved( - bool $asParticipantOpen = true, - bool $asRequestor = true - ): Collection - { - $result = new ArrayCollection(); - - if ($asParticipantOpen) { - foreach ($this->getOpenedParticipations() - ->map(fn (AccompanyingPeriodParticipation $app) => - $app->getAccompanyingPeriod()) - as $period - ) { - if (!$result->contains($period)) { - $result->add($period); - } - } - } - - if ($asRequestor) { - foreach ($this->accompanyingPeriodRequested as $period) { - if (!$result->contains($period)) { - $result->add($period); - } - } - } - - return $result; + return ($this->participationsContainAccompanyingPeriod($accompanyingPeriod)) ? false : true; } /** @@ -1719,7 +662,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI * Used in template, to find the participation when iterating on a list * of period. * - * @param \Chill\PersonBundle\Entity\AccompanyingPeriod $period * @return AccompanyingPeriodParticipation */ public function findParticipationForPeriod(AccompanyingPeriod $period): ?AccompanyingPeriodParticipation @@ -1742,11 +684,934 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return null; } + public function getAcceptEmail(): ?bool + { + return $this->acceptEmail; + } + + public function getAcceptSMS(): ?bool + { + return $this->acceptSMS; + } + + /** + * Return a list of all accompanying period where the person is involved:. + * + * * as requestor; + * * as participant, only for opened participation; + * + * @param bool $asParticipantOpen add participation which are still opened + * @param bool $asRequestor add accompanying period where the person is requestor + * + * @return AccompanyingPeriod[]|Collection + */ + public function getAccompanyingPeriodInvolved( + bool $asParticipantOpen = true, + bool $asRequestor = true + ): Collection { + $result = new ArrayCollection(); + + if ($asParticipantOpen) { + foreach ($this->getOpenedParticipations() + ->map(fn (AccompanyingPeriodParticipation $app) => $app->getAccompanyingPeriod()) + as $period + ) { + if (!$result->contains($period)) { + $result->add($period); + } + } + } + + if ($asRequestor) { + foreach ($this->accompanyingPeriodRequested as $period) { + if (!$result->contains($period)) { + $result->add($period); + } + } + } + + return $result; + } + + /** + * Get AccompanyingPeriodParticipations Collection. + * + * @return AccompanyingPeriodParticipation[]|Collection + */ + public function getAccompanyingPeriodParticipations(): Collection + { + return $this->accompanyingPeriodParticipations; + } + + /** + * @return AccompanyingPeriod[]|Collection + */ + public function getAccompanyingPeriodRequested(): Collection + { + return $this->accompanyingPeriodRequested; + } + + /** + * Get AccompanyingPeriods array. + */ + public function getAccompanyingPeriods(): array + { + $accompanyingPeriods = []; + + foreach ($this->accompanyingPeriodParticipations as $participation) { + /** @var AccompanyingPeriodParticipation $participation */ + $accompanyingPeriods[] = $participation->getAccompanyingPeriod(); + } + + return $accompanyingPeriods; + } + + /** + * Get the accompanying periods of a give person with the chronological order. + */ + public function getAccompanyingPeriodsOrdered(): array + { + $periods = $this->getAccompanyingPeriods(); + + //order by date : + usort($periods, static function ($a, $b) { + $dateA = $a->getOpeningDate(); + $dateB = $b->getOpeningDate(); + + if ($dateA === $dateB) { + $dateEA = $a->getClosingDate(); + $dateEB = $b->getClosingDate(); + + if ($dateEA === $dateEB) { + return 0; + } + + if ($dateEA < $dateEB) { + return -1; + } + + return +1; + } + + if ($dateA < $dateB) { + return -1; + } + + return 1; + }); + + return $periods; + } + + /** + * get the address associated with the person at the given date. + * + * If the `$at` parameter is now, use the method `getCurrentPersonAddress`, which is optimized + * on database side. + * + * @throws Exception + */ + public function getAddressAt(?DateTime $at = null): ?Address + { + $at ??= new DateTime('now'); + + /** @var ArrayIterator $addressesIterator */ + $addressesIterator = $this->getAddresses() + ->filter(static fn (Address $address): bool => $address->getValidFrom() <= $at) + ->getIterator(); + + $addressesIterator->uasort( + static fn (Address $left, Address $right): int => $right->getValidFrom() <=> $left->getValidFrom() + ); + + return [] === ($addresses = iterator_to_array($addressesIterator)) ? + null : + current($addresses); + } + + /** + * By default, the addresses are ordered by date, descending (the most + * recent first). + */ + public function getAddresses(): Collection + { + return $this->addresses; + } + + /** + * Return the age of a person, calculated at the date 'now'. + * + * If the person has a deathdate, calculate the age at the deathdate. + * + * @param string $at A valid string to create a DateTime. + */ + public function getAge(string $at = 'now'): ?int + { + if ($this->birthdate instanceof DateTimeInterface) { + if ($this->deathdate instanceof DateTimeInterface) { + return (int) date_diff($this->birthdate, $this->deathdate)->format('%y'); + } + + return (int) date_diff($this->birthdate, date_create($at))->format('%y'); + } + + return null; + } + + public function getAltNames(): Collection + { + return $this->altNames; + } + + /** + * Get birthdate. + * + * @return DateTime + */ + public function getBirthdate() + { + return $this->birthdate; + } + + /** + * Get center. + * + * @return Center + */ + public function getCenter() + { + return $this->center; + } + + /** + * Get cFData. + * + * @return array + */ + public function getCFData() + { + if (null === $this->cFData) { + $this->cFData = []; + } + + return $this->cFData; + } + + /** + * Get civility. + * + * @return Civility + */ + public function getCivility() + { + return $this->civility; + } + + /** + * Get contactInfo. + * + * @return string + */ + public function getcontactInfo() + { + return $this->contactInfo; + } + + /** + * Get countryOfBirth. + * + * @return Chill\MainBundle\Entity\Country + */ + public function getCountryOfBirth() + { + return $this->countryOfBirth; + } + + public function getCreatedAt(): ?DateTimeInterface + { + return $this->createdAt; + } + public function getCreatedBy(): ?User { return $this->createdBy; } + /** + * Returns the opened accompanying period. + * + * @deprecated since 1.1 use `getOpenedAccompanyingPeriod instead + */ + public function getCurrentAccompanyingPeriod(): ?AccompanyingPeriod + { + return $this->getOpenedAccompanyingPeriod(); + } + + /** + * Get current accompanyingPeriods array. + */ + public function getCurrentAccompanyingPeriods(): array + { + $currentAccompanyingPeriods = []; + $currentDate = new DateTime(); + + foreach ($this->accompanyingPeriodParticipations as $participation) { + $endDate = $participation->getEndDate(); + + if (null === $endDate || $endDate > $currentDate) { + $currentAccompanyingPeriods[] = $participation->getAccompanyingPeriod(); + } + } + + return $currentAccompanyingPeriods; + } + + public function getCurrentHousehold(?DateTimeImmutable $at = null): ?Household + { + $participation = $this->getCurrentHouseholdParticipationShareHousehold($at); + + return $participation instanceof HouseholdMember ? + $participation->getHousehold() + : null; + } + + public function getCurrentHouseholdAddress(?DateTimeImmutable $at = null): ?Address + { + if ( + null === $at + || $at->format('Ymd') === (new DateTime('today'))->format('Ymd') + ) { + return $this->currentPersonAddress instanceof PersonCurrentAddress + ? $this->currentPersonAddress->getAddress() : null; + } + + // if not now, compute the date from history + $criteria = new Criteria(); + $expr = Criteria::expr(); + + $criteria->where( + $expr->lte('validFrom', $at) + ) + ->andWhere( + $expr->orX( + $expr->isNull('validTo'), + $expr->gte('validTo', $at) + ) + ); + + $addrs = $this->getHouseholdAddresses() + ->matching($criteria); + + if ($addrs->count() > 0) { + return $addrs->first()->getAddress(); + } + + return null; + } + + public function getCurrentHouseholdParticipationShareHousehold(?DateTimeImmutable $at = null): ?HouseholdMember + { + $criteria = new Criteria(); + $expr = Criteria::expr(); + $date = null === $at ? new DateTimeImmutable('today') : $at; + $datef = $date->format('Y-m-d'); + + if ( + null !== ($this->currentHouseholdParticipationAt[$datef] ?? null)) { + return $this->currentHouseholdParticipationAt[$datef]; + } + + $criteria + ->where( + $expr->andX( + $expr->lte('startDate', $date), + $expr->orX( + $expr->isNull('endDate'), + $expr->gt('endDate', $date) + ), + $expr->eq('shareHousehold', true) + ) + ); + + $participations = $this->getHouseholdParticipations() + ->matching($criteria); + + return $participations->count() > 0 ? + $this->currentHouseholdParticipationAt[$datef] = $participations->first() + : null; + } + + /** + * Get the current person address. + */ + public function getCurrentPersonAddress(): ?Address + { + if (null === $this->currentPersonAddress) { + return null; + } + + return $this->currentPersonAddress->getAddress(); + } + + public function getDeathdate(): ?DateTimeInterface + { + return $this->deathdate; + } + + /** + * Get email. + * + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * Get firstName. + * + * @return string + */ + public function getFirstName() + { + return $this->firstName; + } + + public function getFullnameCanonical(): string + { + return $this->fullnameCanonical; + } + + /** + * Get gender. + * + * @return string + */ + public function getGender() + { + return $this->gender; + } + + public function getGenderComment(): CommentEmbeddable + { + return $this->genderComment; + } + + /** + * return gender as a Numeric form. + * This is used for translations. + * + * @return int + * + * @deprecated Keep for legacy. Used in Chill 1.5 for feminize before icu translations + */ + public function getGenderNumeric() + { + switch ($this->getGender()) { + case self::FEMALE_GENDER: + return 1; + + case self::MALE_GENDER: + return 0; + + case self::BOTH_GENDER: + return 2; + + default: + return -1; + } + } + + public function getHouseholdAddresses(): Collection + { + return $this->householdAddresses; + } + + public function getHouseholdParticipations(): Collection + { + return $this->householdParticipations; + } + + /** + * Get participation where the person does not share the household. + * + * Order by startDate, desc + */ + public function getHouseholdParticipationsNotShareHousehold(): Collection + { + $criteria = new Criteria(); + $expr = Criteria::expr(); + + $criteria + ->where( + $expr->eq('shareHousehold', false) + ) + ->orderBy(['startDate' => Criteria::DESC]); + + return $this->getHouseholdParticipations() + ->matching($criteria); + } + + /** + * Get participation where the person does share the household. + * + * Order by startDate, desc + */ + public function getHouseholdParticipationsShareHousehold(): Collection + { + $criteria = new Criteria(); + $expr = Criteria::expr(); + + $criteria + ->where( + $expr->eq('shareHousehold', true) + ) + ->orderBy(['startDate' => Criteria::DESC]); + + return $this->getHouseholdParticipations() + ->matching($criteria); + } + + public function getId(): ?int + { + return $this->id; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->getFirstName() . ' ' . $this->getLastName(); + } + + /** + * @deprecated Use `getCurrentPersonAddress` instead + * + * @throws Exception + * + * @return false|mixed|null + */ + public function getLastAddress(?DateTime $from = null) + { + return $this->getCurrentPersonAddress(); + } + + /** + * Get lastName. + * + * @return string + */ + public function getLastName() + { + return $this->lastName; + } + + /** + * Get maritalStatus. + * + * @return MaritalStatus + */ + public function getMaritalStatus() + { + return $this->maritalStatus; + } + + public function getMaritalStatusComment(): CommentEmbeddable + { + return $this->maritalStatusComment; + } + + public function getMaritalStatusDate(): ?DateTimeInterface + { + return $this->maritalStatusDate; + } + + /** + * Get memo. + * + * @return string + */ + public function getMemo() + { + return $this->memo; + } + + /** + * Get mobilenumber. + */ + public function getMobilenumber(): string + { + return $this->mobilenumber; + } + + /** + * Get nationality. + * + * @return Country + */ + public function getNationality(): ?Country + { + return $this->nationality; + } + + public function getNumberOfChildren(): ?int + { + return $this->numberOfChildren; + } + + /** + * Return the opened accompanying period. + */ + public function getOpenedAccompanyingPeriod(): ?AccompanyingPeriod + { + if ($this->isOpen() === false) { + return null; + } + + foreach ($this->accompanyingPeriodParticipations as $participation) { + /** @var AccompanyingPeriodParticipation $participation */ + if ($participation->getAccompanyingPeriod()->isOpen()) { + return $participation->getAccompanyingPeriod(); + } + } + + return null; + } + + /** + * Return a collection of participation, where the participation + * is still opened or in draft state. + * + * @return AccompanyingPeriodParticipation[]|Collection + */ + public function getOpenedParticipations(): Collection + { + // create a criteria for filtering easily + $criteria = Criteria::create(); + $criteria + ->andWhere(Criteria::expr()->eq('endDate', null)) + ->orWhere(Criteria::expr()->gt('endDate', new DateTime('now'))); + + return $this->getAccompanyingPeriodParticipations() + ->matching($criteria) + ->filter(static function (AccompanyingPeriodParticipation $app) { + return AccompanyingPeriod::STEP_CLOSED !== $app->getAccompanyingPeriod()->getStep(); + }); + } + + public function getOtherPhoneNumbers(): Collection + { + return $this->otherPhoneNumbers; + } + + /** + * Get phonenumber. + */ + public function getPhonenumber(): string + { + return $this->phonenumber; + } + + /** + * Get placeOfBirth. + * + * @return string + */ + public function getPlaceOfBirth() + { + return $this->placeOfBirth; + } + + /** + * Get spokenLanguages. + * + * @return ArrayCollection + */ + public function getSpokenLanguages() + { + return $this->spokenLanguages; + } + + public function getUpdatedAt(): ?DateTimeInterface + { + return $this->updatedAt; + } + + public function getUpdatedBy(): ?User + { + return $this->updatedBy; + } + + public function hasCurrentHouseholdAddress(?DateTimeImmutable $at = null): bool + { + return null !== $this->getCurrentHouseholdAddress($at); + } + + /** + * Return true if the person has two addresses with the + * same validFrom date (in format 'Y-m-d'). + */ + public function hasTwoAdressWithSameValidFromDate() + { + $validYMDDates = []; + + foreach ($this->addresses as $ad) { + $validDate = $ad->getValidFrom()->format('Y-m-d'); + + if (in_array($validDate, $validYMDDates, true)) { + return true; + } + $validYMDDates[] = $validDate; + } + + return false; + } + + /** + * Validation callback that checks if the accompanying periods are valid. + * + * This method add violation errors. + * + * @Assert\Callback( + * groups={"accompanying_period_consistent"} + * ) + */ + public function isAccompanyingPeriodValid(ExecutionContextInterface $context) + { + $r = $this->checkAccompanyingPeriodsAreNotCollapsing(); + + if (true !== $r) { + if (self::ERROR_PERIODS_ARE_COLLAPSING === $r['result']) { + $context->buildViolation('Two accompanying periods have days in commun') + ->atPath('accompanyingPeriods') + ->addViolation(); + } + + if (self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD === $r['result']) { + $context->buildViolation('A period is opened and a period is added after it') + ->atPath('accompanyingPeriods') + ->addViolation(); + } + } + } + + /** + * Validation callback that checks if the addresses are valid (do not have + * two addresses with the same validFrom date). + * + * This method add violation errors. + * + * @Assert\Callback( + * groups={"addresses_consistent"} + * ) + */ + public function isAddressesValid(ExecutionContextInterface $context) + { + if ($this->hasTwoAdressWithSameValidFromDate()) { + $context + ->buildViolation('Two addresses has the same validFrom date') + ->atPath('addresses') + ->addViolation(); + } + } + + /** + * Check if the person is opened. + */ + public function isOpen(): bool + { + foreach ($this->getAccompanyingPeriods() as $period) { + if ($period->isOpen()) { + return true; + } + } + + return false; + } + + public function isSharingHousehold(?DateTimeImmutable $at = null): bool + { + return null !== $this->getCurrentHousehold($at); + } + + /** + * set the Person file as open at the given date. + * + * For updating a opening's date, you should update AccompanyingPeriod instance + * directly. + * + * For closing a file, @see this::close + * + * To check if the Person and its accompanying period is consistent, use validation. + */ + public function open(AccompanyingPeriod $accompanyingPeriod): void + { + $this->proxyAccompanyingPeriodOpenState = true; + $this->addAccompanyingPeriod($accompanyingPeriod); + } + + /** + * Remove AccompanyingPeriod. + */ + public function removeAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): void + { + $participation = $this->participationsContainAccompanyingPeriod($accompanyingPeriod); + + if (!null === $participation) { + $participation->setEndDate(DateTimeImmutable::class); + $this->accompanyingPeriodParticipations->removeElement($participation); + } + } + + public function removeAddress(Address $address) + { + $this->addresses->removeElement($address); + } + + /** + * @return $this + */ + public function removeAltName(PersonAltName $altName) + { + if ($this->altNames->contains($altName)) { + $altName->setPerson(null); + $this->altNames->removeElement($altName); + } + + return $this; + } + + /** + * @return $this + */ + public function removeOtherPhoneNumber(PersonPhone $otherPhoneNumber) + { + if ($this->otherPhoneNumbers->contains($otherPhoneNumber)) { + $this->otherPhoneNumbers->removeElement($otherPhoneNumber); + } + + return $this; + } + + public function setAcceptEmail(bool $acceptEmail): self + { + $this->acceptEmail = $acceptEmail; + + return $this; + } + + public function setAcceptSMS(bool $acceptSMS): self + { + $this->acceptSMS = $acceptSMS; + + return $this; + } + + /** + * @return $this + */ + public function setAltNames(Collection $altNames) + { + $this->altNames = $altNames; + + return $this; + } + + /** + * Set birthdate. + * + * @param DateTime $birthdate + * + * @return Person + */ + public function setBirthdate($birthdate) + { + $this->birthdate = $birthdate; + + return $this; + } + + /** + * Set the center. + * + * @return \Chill\PersonBundle\Entity\Person + */ + public function setCenter(Center $center) + { + $this->center = $center; + + return $this; + } + + /** + * Set cFData. + * + * @param array $cFData + * + * @return Report + */ + public function setCFData($cFData) + { + $this->cFData = $cFData; + + return $this; + } + + /** + * Set civility. + * + * @param Civility $civility + * + * @return Person + */ + public function setCivility(?Civility $civility = null) + { + $this->civility = $civility; + + return $this; + } + + /** + * Set contactInfo. + * + * @param string $contactInfo + * + * @return Person + */ + public function setcontactInfo($contactInfo) + { + if (null === $contactInfo) { + $contactInfo = ''; + } + + $this->contactInfo = $contactInfo; + + return $this; + } + + /** + * Set countryOfBirth. + * + * @param Chill\MainBundle\Entity\Country $countryOfBirth + * + * @return Person + */ + public function setCountryOfBirth(?Country $countryOfBirth = null) + { + $this->countryOfBirth = $countryOfBirth; + + return $this; + } + + public function setCreatedAt(DateTimeInterface $datetime): self + { + $this->createdAt = $datetime; + + return $this; + } + public function setCreatedBy(User $createdBy): self { $this->createdBy = $createdBy; @@ -1754,9 +1619,229 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this; } - public function setCreatedAt(\DateTimeInterface $datetime): self + public function setDeathdate(?DateTimeInterface $deathdate): self { - $this->createdAt = $datetime; + $this->deathdate = $deathdate; + + return $this; + } + + /** + * Set email. + * + * @param string $email + * + * @return Person + */ + public function setEmail($email) + { + if (null === $email) { + $email = ''; + } + + $this->email = $email; + + return $this; + } + + /** + * Set firstName. + * + * @param string $firstName + * + * @return Person + */ + public function setFirstName($firstName) + { + $this->firstName = $firstName; + + return $this; + } + + public function setFullnameCanonical($fullnameCanonical): Person + { + $this->fullnameCanonical = $fullnameCanonical; + + return $this; + } + + /** + * Set gender. + * + * @param string $gender + * + * @return Person + */ + public function setGender($gender) + { + $this->gender = $gender; + + return $this; + } + + public function setGenderComment(CommentEmbeddable $genderComment): self + { + $this->genderComment = $genderComment; + + return $this; + } + + /** + * Set lastName. + * + * @param string $lastName + * + * @return Person + */ + public function setLastName($lastName) + { + $this->lastName = $lastName; + + return $this; + } + + /** + * Set maritalStatus. + * + * @param MaritalStatus $maritalStatus + * + * @return Person + */ + public function setMaritalStatus(?MaritalStatus $maritalStatus = null) + { + $this->maritalStatus = $maritalStatus; + + return $this; + } + + public function setMaritalStatusComment(CommentEmbeddable $maritalStatusComment): self + { + $this->maritalStatusComment = $maritalStatusComment; + + return $this; + } + + public function setMaritalStatusDate(?DateTimeInterface $maritalStatusDate): self + { + $this->maritalStatusDate = $maritalStatusDate; + + return $this; + } + + /** + * Set memo. + * + * @param string $memo + * + * @return Person + */ + public function setMemo($memo) + { + if (null === $memo) { + $memo = ''; + } + + if ($this->memo !== $memo) { + $this->memo = $memo; + } + + return $this; + } + + /** + * Set mobilenumber. + * + * @param string $mobilenumber + * + * @return Person + */ + public function setMobilenumber(?string $mobilenumber = '') + { + $this->mobilenumber = (string) $mobilenumber; + + return $this; + } + + /** + * Set nationality. + * + * @param Chill\MainBundle\Entity\Country $nationality + * + * @return Person + */ + public function setNationality(?Country $nationality = null) + { + $this->nationality = $nationality; + + return $this; + } + + public function setNumberOfChildren(?int $numberOfChildren): self + { + $this->numberOfChildren = $numberOfChildren; + + return $this; + } + + /** + * @return $this + */ + public function setOtherPhoneNumbers(Collection $otherPhoneNumbers) + { + $this->otherPhoneNumbers = $otherPhoneNumbers; + + return $this; + } + + /** + * Set phonenumber. + * + * @param string $phonenumber + * + * @return Person + */ + public function setPhonenumber(?string $phonenumber = '') + { + $this->phonenumber = (string) $phonenumber; + + return $this; + } + + /** + * Set placeOfBirth. + * + * @param string $placeOfBirth + * + * @return Person + */ + public function setPlaceOfBirth($placeOfBirth) + { + if (null === $placeOfBirth) { + $placeOfBirth = ''; + } + + $this->placeOfBirth = $placeOfBirth; + + return $this; + } + + /** + * Set spokenLanguages. + * + * @param type $spokenLanguages + * + * @return Person + */ + public function setSpokenLanguages($spokenLanguages) + { + $this->spokenLanguages = $spokenLanguages; + + return $this; + } + + public function setUpdatedAt(DateTimeInterface $datetime): self + { + $this->updatedAt = $datetime; return $this; } @@ -1768,11 +1853,19 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this; } - public function setUpdatedAt(\DateTimeInterface $datetime): self + /** + * This private function scan accompanyingPeriodParticipations Collection, + * searching for a given AccompanyingPeriod. + */ + private function participationsContainAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): ?AccompanyingPeriodParticipation { - $this->updatedAt = $datetime; + foreach ($this->accompanyingPeriodParticipations as $participation) { + /** @var AccompanyingPeriodParticipation $participation */ + if ($participation->getAccompanyingPeriod() === $accompanyingPeriod) { + return $participation; + } + } - return $this; + return null; } - } diff --git a/src/Bundle/ChillPersonBundle/Entity/Person/PersonCurrentAddress.php b/src/Bundle/ChillPersonBundle/Entity/Person/PersonCurrentAddress.php index e982f8b97..0c9e416c2 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person/PersonCurrentAddress.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person/PersonCurrentAddress.php @@ -1,9 +1,19 @@ address; + } - /** - * @return Person - */ public function getPerson(): Person { return $this->person; } - /** - * @return Address - */ - public function getAddress(): Address - { - return $this->address; - } - /** * This date is the intersection of household membership - * and address validity - * - * @return \DateTimeImmutable + * and address validity. */ - public function getValidFrom(): \DateTimeImmutable + public function getValidFrom(): DateTimeImmutable { return $this->validFrom; } /** * This date is the intersection of household membership - * and address validity - * - * @return \DateTimeImmutable|null + * and address validity. */ - public function getValidTo(): ?\DateTimeImmutable + public function getValidTo(): ?DateTimeImmutable { return $this->validTo; } diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php b/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php index 95603d4ef..d233082c4 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php @@ -1,11 +1,20 @@ id; } + /** + * Get key. + * + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * Get label. + * + * @return string + */ + public function getLabel() + { + return $this->label; + } + + public function getPerson(): Person + { + return $this->person; + } + /** * Set key. * @@ -70,16 +103,6 @@ class PersonAltName return $this; } - /** - * Get key. - * - * @return string - */ - public function getKey() - { - return $this->key; - } - /** * Set label. * @@ -95,31 +118,12 @@ class PersonAltName } /** - * Get label. - * - * @return string - */ - public function getLabel() - { - return $this->label; - } - - /** - * @return Person - */ - public function getPerson(): Person - { - return $this->person; - } - - /** - * @param Person|null $person * @return $this */ public function setPerson(?Person $person = null) { $this->person = $person; - + return $this; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php b/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php index 9f116c501..c00eb91fa 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php @@ -1,12 +1,22 @@ date = new \DateTime(); - } - - public function getId() - { - return $this->id; - } - - public function setId($id) - { - $this->id = $id; - } - - public function getPerson1() - { - return $this->person1; - } - - public function setPerson1(Person $person1) - { - $this->person1 = $person1; - } - - public function getPerson2() - { - return $this->person2; - } - - public function setPerson2(Person $person2) - { - $this->person2 = $person2; + $this->date = new DateTime(); } public function getDate() @@ -90,9 +71,19 @@ class PersonNotDuplicate return $this->date; } - public function setDate(\DateTime $date) + public function getId() { - $this->date = $date; + return $this->id; + } + + public function getPerson1() + { + return $this->person1; + } + + public function getPerson2() + { + return $this->person2; } public function getUser() @@ -100,6 +91,26 @@ class PersonNotDuplicate return $this->user; } + public function setDate(DateTime $date) + { + $this->date = $date; + } + + public function setId($id) + { + $this->id = $id; + } + + public function setPerson1(Person $person1) + { + $this->person1 = $person1; + } + + public function setPerson2(Person $person2) + { + $this->person2 = $person2; + } + public function setUser(User $user) { $this->user = $user; diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php b/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php index 222495e17..400e39c88 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php @@ -1,18 +1,40 @@ date = new \DateTime(); + $this->date = new DateTime(); + } + + public function getDate(): DateTime + { + return $this->date; + } + + public function getDescription(): ?string + { + return $this->description; } public function getId(): int @@ -63,9 +85,9 @@ class PersonPhone return $this->person; } - public function setPerson(Person $person): void + public function getPhonenumber(): string { - $this->person = $person; + return $this->phonenumber; } public function getType(): string @@ -73,24 +95,14 @@ class PersonPhone return $this->type; } - public function setType(string $type): void + public function isEmpty(): bool { - $this->type = $type; + return empty($this->getDescription()) && empty($this->getPhonenumber()); } - public function getPhonenumber(): string + public function setDate(DateTime $date): void { - return $this->phonenumber; - } - - public function setPhonenumber(string $phonenumber): void - { - $this->phonenumber = $phonenumber; - } - - public function getDescription(): ?string - { - return $this->description; + $this->date = $date; } public function setDescription(?string $description): void @@ -98,18 +110,18 @@ class PersonPhone $this->description = $description; } - public function getDate(): \DateTime + public function setPerson(Person $person): void { - return $this->date; + $this->person = $person; } - public function setDate(\DateTime $date): void + public function setPhonenumber(string $phonenumber): void { - $this->date = $date; + $this->phonenumber = $phonenumber; } - - public function isEmpty(): bool + + public function setType(string $type): void { - return empty($this->getDescription()) && empty($this->getPhonenumber()); + $this->type = $type; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Relationships/Relation.php b/src/Bundle/ChillPersonBundle/Entity/Relationships/Relation.php new file mode 100644 index 000000000..c78e5df33 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/Relationships/Relation.php @@ -0,0 +1,95 @@ +id; + } + + public function getIsActive(): bool + { + return $this->isActive; + } + + public function getReverseTitle(): ?array + { + return $this->reverseTitle; + } + + public function getTitle(): ?array + { + return $this->title; + } + + public function setIsActive(?bool $isActive): self + { + $this->isActive = $isActive; + + return $this; + } + + public function setReverseTitle(?array $reverseTitle): self + { + $this->reverseTitle = $reverseTitle; + + return $this; + } + + public function setTitle(?array $title): self + { + $this->title = $title; + + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php b/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php new file mode 100644 index 000000000..d51881bb2 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php @@ -0,0 +1,221 @@ +createdAt; + } + + public function getCreatedBy(): ?User + { + return $this->createdBy; + } + + public function getFromPerson(): ?Person + { + return $this->fromPerson; + } + + public function getId(): ?int + { + return $this->id; + } + + /** + * Return the opposite person of the @see{counterpart} person. + * + * this is the from person if the given is associated to the To, + * or the To person otherwise. + * + * @throw RuntimeException if the counterpart is neither in the from or to person + */ + public function getOpposite(Person $counterpart): Person + { + if ($this->fromPerson !== $counterpart && $this->toPerson !== $counterpart) { + throw new RuntimeException('the counterpart is neither the from nor to person for this relationship'); + } + + if ($this->fromPerson === $counterpart) { + return $this->toPerson; + } + + return $this->fromPerson; + } + + public function getRelation(): ?Relation + { + return $this->relation; + } + + public function getReverse(): ?bool + { + return $this->reverse; + } + + public function getToPerson(): ?Person + { + return $this->toPerson; + } + + public function getUpdatedAt(): ?DateTimeImmutable + { + return $this->updatedAt; + } + + public function getUpdatedBy(): ?User + { + return $this->updatedBy; + } + + public function setCreatedAt(DateTimeInterface $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function setCreatedBy(?User $user): self + { + $this->createdBy = $user; + + return $this; + } + + public function setFromPerson(?Person $fromPerson): self + { + $this->fromPerson = $fromPerson; + + return $this; + } + + public function setRelation(?Relation $relation): self + { + $this->relation = $relation; + + return $this; + } + + public function setReverse(bool $reverse): self + { + $this->reverse = $reverse; + + return $this; + } + + public function setToPerson(?Person $toPerson): self + { + $this->toPerson = $toPerson; + + return $this; + } + + public function setUpdatedAt(?DateTimeInterface $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } + + public function setUpdatedBy(?User $updatedBy): self + { + $this->updatedBy = $updatedBy; + + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php index f01b2a4c1..aeb8d0a8d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php @@ -1,7 +1,17 @@ delay; + } public function getId(): ?int { return $this->id; } + public function getNotificationDelay(): ?DateInterval + { + return $this->notificationDelay; + } + + public function getSocialAction(): ?SocialAction + { + return $this->socialAction; + } + public function getTitle(): array { return $this->title; } - public function setTitle(array $title): self - { - $this->title = $title; - - return $this; - } - - public function getDelay(): ?\DateInterval - { - return $this->delay; - } - - public function setDelay(\DateInterval $delay): self + public function setDelay(DateInterval $delay): self { $this->delay = $delay; return $this; } - public function getNotificationDelay(): ?\DateInterval - { - return $this->notificationDelay; - } - - public function setNotificationDelay(\DateInterval $notificationDelay): self + public function setNotificationDelay(DateInterval $notificationDelay): self { $this->notificationDelay = $notificationDelay; return $this; } - public function getSocialAction(): ?SocialAction - { - return $this->socialAction; - } - public function setSocialAction(?SocialAction $socialAction): self { $this->socialAction = $socialAction; return $this; } + + public function setTitle(array $title): self + { + $this->title = $title; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php index 7ad301875..30abaa3ef 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php @@ -1,7 +1,17 @@ results = new ArrayCollection(); } - public function getId(): ?int - { - return $this->id; - } - - public function getTitle(): array - { - return $this->title; - } - - public function setTitle(array $title): self - { - $this->title = $title; - - return $this; - } - - public function getDesactivationDate(): ?\DateTimeInterface - { - return $this->desactivationDate; - } - - public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self - { - $this->desactivationDate = $desactivationDate; - - return $this; - } - - /** - * @return Collection|SocialAction[] - */ - public function getSocialActions(): Collection - { - return $this->socialActions; - } - - public function addSocialAction(SocialAction $socialAction): self - { - if (!$this->socialActions->contains($socialAction)) { - $this->socialActions[] = $socialAction; - } - - return $this; - } - - public function removeSocialAction(SocialAction $socialAction): self - { - $this->socialActions->removeElement($socialAction); - - return $this; - } - - /** - * @return Collection|Result[] - */ - public function getResults(): Collection - { - return $this->results; - } - public function addResult(Result $result): self { if (!$this->results->contains($result)) { @@ -125,10 +75,71 @@ class Goal return $this; } + public function addSocialAction(SocialAction $socialAction): self + { + if (!$this->socialActions->contains($socialAction)) { + $this->socialActions[] = $socialAction; + } + + return $this; + } + + public function getDesactivationDate(): ?DateTimeInterface + { + return $this->desactivationDate; + } + + public function getId(): ?int + { + return $this->id; + } + + /** + * @return Collection|Result[] + */ + public function getResults(): Collection + { + return $this->results; + } + + /** + * @return Collection|SocialAction[] + */ + public function getSocialActions(): Collection + { + return $this->socialActions; + } + + public function getTitle(): array + { + return $this->title; + } + public function removeResult(Result $result): self { $this->results->removeElement($result); return $this; } + + public function removeSocialAction(SocialAction $socialAction): self + { + $this->socialActions->removeElement($socialAction); + + return $this; + } + + public function setDesactivationDate(?DateTimeInterface $desactivationDate): self + { + $this->desactivationDate = $desactivationDate; + + return $this; + } + + public function setTitle(array $title): self + { + $this->title = $title; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php index 42a4c0929..b4a9b6aa2 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php @@ -1,9 +1,20 @@ accompanyingPeriodWorkGoals = new ArrayCollection(); } - public function getId(): ?int - { - return $this->id; - } - - public function getTitle(): array - { - return $this->title; - } - - public function setTitle(array $title): self - { - $this->title = $title; - - return $this; - } - - public function getDesactivationDate(): ?\DateTimeInterface - { - return $this->desactivationDate; - } - - public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self - { - $this->desactivationDate = $desactivationDate; - - return $this; - } - - /** - * @return Collection|SocialAction[] - */ - public function getSocialActions(): Collection - { - return $this->socialActions; - } - - public function addSocialAction(SocialAction $socialAction): self - { - if (!$this->socialActions->contains($socialAction)) { - $this->socialActions[] = $socialAction; - } - - return $this; - } - - public function removeSocialAction(SocialAction $socialAction): self - { - $this->socialActions->removeElement($socialAction); - - return $this; - } - - /** - * @return Collection|Goal[] - */ - public function getGoals(): Collection - { - return $this->goals; - } - - public function addGoal(Goal $goal): self - { - if (!$this->goals->contains($goal)) { - $this->goals[] = $goal; - } - - return $this; - } - - public function removeGoal(Goal $goal): self - { - $this->goals->removeElement($goal); - - return $this; - } - - /** - * @return Collection|AccompanyingPeriodWork[] - */ - public function getAccompanyingPeriodWorks(): Collection - { - return $this->accompanyingPeriodWorks; - } - public function addAccompanyingPeriodWork(AccompanyingPeriodWork $accompanyingPeriod): self { if (!$this->accompanyingPeriodWorks->contains($accompanyingPeriod)) { @@ -162,26 +89,83 @@ class Result return $this; } - public function removeAccompanyingPeriodWork(AccompanyingPeriodWork $accompanyingPeriod): self + public function addAccompanyingPeriodWorkGoal(AccompanyingPeriodWorkGoal $accompanyingPeriodWorkGoal): self { - $this->accompanyingPeriodWorks->removeElement($accompanyingPeriod); + if (!$this->accompanyingPeriodWorkGoals->contains($accompanyingPeriodWorkGoal)) { + $this->accompanyingPeriodWorkGoals[] = $accompanyingPeriodWorkGoal; + } + + return $this; + } + + public function addGoal(Goal $goal): self + { + if (!$this->goals->contains($goal)) { + $this->goals[] = $goal; + } + + return $this; + } + + public function addSocialAction(SocialAction $socialAction): self + { + if (!$this->socialActions->contains($socialAction)) { + $this->socialActions[] = $socialAction; + } return $this; } /** - * @return Collection|AccompanyingPeriodWorkGoal[] + * @return AccompanyingPeriodWorkGoal[]|Collection */ public function getAccompanyingPeriodWorkGoals(): Collection { return $this->accompanyingPeriodWorkGoals; } - public function addAccompanyingPeriodWorkGoal(AccompanyingPeriodWorkGoal $accompanyingPeriodWorkGoal): self + /** + * @return AccompanyingPeriodWork[]|Collection + */ + public function getAccompanyingPeriodWorks(): Collection { - if (!$this->accompanyingPeriodWorkGoals->contains($accompanyingPeriodWorkGoal)) { - $this->accompanyingPeriodWorkGoals[] = $accompanyingPeriodWorkGoal; - } + return $this->accompanyingPeriodWorks; + } + + public function getDesactivationDate(): ?DateTimeInterface + { + return $this->desactivationDate; + } + + /** + * @return Collection|Goal[] + */ + public function getGoals(): Collection + { + return $this->goals; + } + + public function getId(): ?int + { + return $this->id; + } + + /** + * @return Collection|SocialAction[] + */ + public function getSocialActions(): Collection + { + return $this->socialActions; + } + + public function getTitle(): array + { + return $this->title; + } + + public function removeAccompanyingPeriodWork(AccompanyingPeriodWork $accompanyingPeriod): self + { + $this->accompanyingPeriodWorks->removeElement($accompanyingPeriod); return $this; } @@ -192,4 +176,32 @@ class Result return $this; } + + public function removeGoal(Goal $goal): self + { + $this->goals->removeElement($goal); + + return $this; + } + + public function removeSocialAction(SocialAction $socialAction): self + { + $this->socialActions->removeElement($socialAction); + + return $this; + } + + public function setDesactivationDate(?DateTimeInterface $desactivationDate): self + { + $this->desactivationDate = $desactivationDate; + + return $this; + } + + public function setTitle(array $title): self + { + $this->title = $title; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php index bb05c900e..c5ca7d18a 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php @@ -1,7 +1,18 @@ results = new ArrayCollection(); } - public function getId(): ?int - { - return $this->id; - } - - public function getDesactivationDate(): ?\DateTimeInterface - { - return $this->desactivationDate; - } - - public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self - { - $this->desactivationDate = $desactivationDate; - - return $this; - } - - public function getIssue(): ?SocialIssue - { - return $this->issue; - } - - public function setIssue(?SocialIssue $issue): self - { - $this->issue = $issue; - - return $this; - } - - public function getParent(): ?self - { - return $this->parent; - } - - public function hasParent(): bool - { - return $this->getParent() instanceof self; - } - - public function setParent(?self $parent): self - { - $this->parent = $parent; - - return $this; - } - - /** - * @return Collection|self[] - */ - public function getChildren(): Collection - { - return $this->children; - } - public function addChild(self $child): self { if (!$this->children->contains($child)) { @@ -148,18 +104,42 @@ class SocialAction return $this; } - public function removeChild(self $child): self + public function addGoal(Goal $goal): self { - if ($this->children->removeElement($child)) { - // set the owning side to null (unless already changed) - if ($child->getParent() === $this) { - $child->setParent(null); - } + if (!$this->goals->contains($goal)) { + $this->goals[] = $goal; } return $this; } + public function addResult(Result $result): self + { + if (!$this->results->contains($result)) { + $this->results[] = $result; + } + + return $this; + } + + /** + * @return Collection|self[] + */ + public function getChildren(): Collection + { + return $this->children; + } + + public function getDefaultNotificationDelay(): ?DateInterval + { + return $this->defaultNotificationDelay; + } + + public function getDesactivationDate(): ?DateTimeInterface + { + return $this->desactivationDate; + } + /** * @return Collection|self[] All the descendants (children, children of children, ...) */ @@ -168,10 +148,11 @@ class SocialAction $descendants = new ArrayCollection(); foreach ($this->getChildren() as $child) { - if(! $descendants->contains($child)) { + if (!$descendants->contains($child)) { $descendants->add($child); - foreach($child->getDescendants() as $descendantsOfChild) { - if(! $descendants->contains($descendantsOfChild)) { + + foreach ($child->getDescendants() as $descendantsOfChild) { + if (!$descendants->contains($descendantsOfChild)) { $descendants->add($descendantsOfChild); } } @@ -188,35 +169,16 @@ class SocialAction { $descendants = $this->getDescendants(); - if(! $descendants->contains($this)) { + if (!$descendants->contains($this)) { $descendants->add($this); } return $descendants; } - public function getDefaultNotificationDelay(): ?\DateInterval + public function getEvaluations(): Collection { - return $this->defaultNotificationDelay; - } - - public function setDefaultNotificationDelay(\DateInterval $defaultNotificationDelay): self - { - $this->defaultNotificationDelay = $defaultNotificationDelay; - - return $this; - } - - public function getTitle(): array - { - return $this->title; - } - - public function setTitle(array $title): self - { - $this->title = $title; - - return $this; + return $this->evaluations; } /** @@ -227,10 +189,46 @@ class SocialAction return $this->goals; } - public function addGoal(Goal $goal): self + public function getId(): ?int { - if (!$this->goals->contains($goal)) { - $this->goals[] = $goal; + return $this->id; + } + + public function getIssue(): ?SocialIssue + { + return $this->issue; + } + + public function getParent(): ?self + { + return $this->parent; + } + + /** + * @return Collection|Result[] + */ + public function getResults(): Collection + { + return $this->results; + } + + public function getTitle(): array + { + return $this->title; + } + + public function hasParent(): bool + { + return $this->getParent() instanceof self; + } + + public function removeChild(self $child): self + { + if ($this->children->removeElement($child)) { + // set the owning side to null (unless already changed) + if ($child->getParent() === $this) { + $child->setParent(null); + } } return $this; @@ -243,23 +241,6 @@ class SocialAction return $this; } - /** - * @return Collection|Result[] - */ - public function getResults(): Collection - { - return $this->results; - } - - public function addResult(Result $result): self - { - if (!$this->results->contains($result)) { - $this->results[] = $result; - } - - return $this; - } - public function removeResult(Result $result): self { $this->results->removeElement($result); @@ -267,11 +248,38 @@ class SocialAction return $this; } - /** - * @return Collection - */ - public function getEvaluations(): Collection + public function setDefaultNotificationDelay(DateInterval $defaultNotificationDelay): self { - return $this->evaluations; + $this->defaultNotificationDelay = $defaultNotificationDelay; + + return $this; + } + + public function setDesactivationDate(?DateTimeInterface $desactivationDate): self + { + $this->desactivationDate = $desactivationDate; + + return $this; + } + + public function setIssue(?SocialIssue $issue): self + { + $this->issue = $issue; + + return $this; + } + + public function setParent(?self $parent): self + { + $this->parent = $parent; + + return $this; + } + + public function setTitle(array $title): self + { + $this->title = $title; + + return $this; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php index f9956e57f..80b106b4e 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php @@ -1,21 +1,42 @@ children = new ArrayCollection(); $this->socialActions = new ArrayCollection(); } - public function getId(): ?int - { - return $this->id; - } - - public function getParent(): ?self - { - return $this->parent; - } - - public function hasParent(): bool - { - return $this->parent !== null; - } - - public function setParent(?self $parent): self - { - $this->parent = $parent; - - return $this; - } - - /** - * @return Collection|self[] - */ - public function getChildren(): Collection - { - return $this->children; - } - public function addChild(self $child): self { if (!$this->children->contains($child)) { @@ -95,18 +76,86 @@ class SocialIssue return $this; } - public function removeChild(self $child): self + public function addSocialAction(SocialAction $socialAction): self { - if ($this->children->removeElement($child)) { - // set the owning side to null (unless already changed) - if ($child->getParent() === $this) { - $child->setParent(null); - } + if (!$this->socialActions->contains($socialAction)) { + $this->socialActions[] = $socialAction; + $socialAction->setSocialIssue($this); } return $this; } + /** + * In a SocialIssues's collection, find the elements which are an ancestor of + * other elements. + * + * Removing those elements of the Collection (which is not done by this method) + * will ensure that only the most descendent elements are present in the collection, + * (any ancestor of another element are present). + * + * @param Collection|SocialIssue[] $socialIssues + * + * @return Collection|SocialIssue[] + */ + public static function findAncestorSocialIssues(Collection $socialIssues): Collection + { + $ancestors = new ArrayCollection(); + + foreach ($socialIssues as $candidateChild) { + if ($ancestors->contains($candidateChild)) { + continue; + } + + foreach ($socialIssues as $candidateParent) { + if ($ancestors->contains($candidateParent)) { + continue; + } + + if ($candidateChild->isDescendantOf($candidateParent)) { + $ancestors->add($candidateParent); + } + } + } + + return $ancestors; + } + + /** + * get all the ancestors of the social issue. + * + * @param bool $includeThis if the array in the result must include the present SocialIssue + */ + public function getAncestors(bool $includeThis = true): array + { + $ancestors = []; + + if ($includeThis) { + $ancestors[] = $this; + } + + $current = $this; + + while ($current->hasParent()) { + $ancestors[] = $current = $current->getParent(); + } + + return $ancestors; + } + + /** + * @return Collection|self[] + */ + public function getChildren(): Collection + { + return $this->children; + } + + public function getDesactivationDate(): ?DateTimeInterface + { + return $this->desactivationDate; + } + /** * @return Collection|self[] All the descendants (children, children of children, ...) */ @@ -115,10 +164,11 @@ class SocialIssue $descendants = new ArrayCollection(); foreach ($this->getChildren() as $child) { - if(! $descendants->contains($child)) { + if (!$descendants->contains($child)) { $descendants->add($child); - foreach($child->getDescendants() as $descendantsOfChild) { - if(! $descendants->contains($descendantsOfChild)) { + + foreach ($child->getDescendants() as $descendantsOfChild) { + if (!$descendants->contains($descendantsOfChild)) { $descendants->add($descendantsOfChild); } } @@ -128,6 +178,24 @@ class SocialIssue return $descendants; } + /** + * @return Collection|SocialAction[] All the descendant social actions of the entity + */ + public function getDescendantsSocialActions(): Collection + { + $descendantsSocialActions = new ArrayCollection(); + + foreach ($this->getSocialActions() as $socialAction) { + foreach ($socialAction->getDescendantsWithThis() as $descendant) { + if (!$descendantsSocialActions->contains($descendant)) { + $descendantsSocialActions->add($descendant); + } + } + } + + return $descendantsSocialActions; + } + /** * @return Collection|self[] All the descendants with the current entity (this) */ @@ -135,36 +203,40 @@ class SocialIssue { $descendants = $this->getDescendants(); - if(! $descendants->contains($this)) { + if (!$descendants->contains($this)) { $descendants->add($this); } return $descendants; } - - public function getDesactivationDate(): ?\DateTimeInterface + public function getId(): ?int { - return $this->desactivationDate; + return $this->id; } - public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self + public function getParent(): ?self { - $this->desactivationDate = $desactivationDate; - - return $this; + return $this->parent; } - public function getTitle(): array + /** + * @return Collection|SocialAction[] All the descendant social actions of all + * the descendants of the entity + */ + public function getRecursiveSocialActions(): Collection { - return $this->title; - } + $recursiveSocialActions = new ArrayCollection(); - public function setTitle(array $title): self - { - $this->title = $title; + foreach ($this->getDescendantsWithThis() as $socialIssue) { + foreach ($socialIssue->getDescendantsSocialActions() as $descendant) { + if (!$recursiveSocialActions->contains($descendant)) { + $recursiveSocialActions->add($descendant); + } + } + } - return $this; + return $recursiveSocialActions; } /** @@ -175,11 +247,40 @@ class SocialIssue return $this->socialActions; } - public function addSocialAction(SocialAction $socialAction): self + public function getTitle(): array { - if (!$this->socialActions->contains($socialAction)) { - $this->socialActions[] = $socialAction; - $socialAction->setSocialIssue($this); + return $this->title; + } + + public function hasParent(): bool + { + return null !== $this->parent; + } + + /** + * Recursive method which return true if the current $issue is a descendant + * of the $issue given in parameter. + */ + public function isDescendantOf(SocialIssue $issue): bool + { + if (!$this->hasParent()) { + return false; + } + + if ($this->getParent() === $issue) { + return true; + } + + return $this->getParent()->isDescendantOf($issue); + } + + public function removeChild(self $child): self + { + if ($this->children->removeElement($child)) { + // set the owning side to null (unless already changed) + if ($child->getParent() === $this) { + $child->setParent(null); + } } return $this; @@ -197,93 +298,24 @@ class SocialIssue return $this; } - /** - * @return Collection|SocialAction[] All the descendant social actions of the entity - */ - public function getDescendantsSocialActions(): Collection + public function setDesactivationDate(?DateTimeInterface $desactivationDate): self { - $descendantsSocialActions = new ArrayCollection(); + $this->desactivationDate = $desactivationDate; - foreach ($this->getSocialActions() as $socialAction) { - foreach ($socialAction->getDescendantsWithThis() as $descendant) { - if(! $descendantsSocialActions->contains($descendant)) { - $descendantsSocialActions->add($descendant); - } - } - } - - return $descendantsSocialActions; + return $this; } - /** - * @return Collection|SocialAction[] All the descendant social actions of all - * the descendants of the entity - */ - public function getRecursiveSocialActions(): Collection + public function setParent(?self $parent): self { - $recursiveSocialActions = new ArrayCollection(); + $this->parent = $parent; - foreach ($this->getDescendantsWithThis() as $socialIssue) { - foreach ($socialIssue->getDescendantsSocialActions() as $descendant) { - if(! $recursiveSocialActions->contains($descendant)) { - $recursiveSocialActions->add($descendant); - } - } - } - - return $recursiveSocialActions; + return $this; } - /** - * Recursive method which return true if the current $issue is a descendant - * of the $issue given in parameter. - * - * @param SocialIssue $issue - * @return bool - */ - public function isDescendantOf(SocialIssue $issue): bool + public function setTitle(array $title): self { - if (!$this->hasParent()) { - return false; - } + $this->title = $title; - if ($this->getParent() === $issue) { - return true; - } - - return $this->getParent()->isDescendantOf($issue); - } - - /** - * In a SocialIssues's collection, find the elements which are an ancestor of - * other elements. - * - * Removing those elements of the Collection (which is not done by this method) - * will ensure that only the most descendent elements are present in the collection, - * (any ancestor of another element are present). - * - * @param Collection|SocialIssue[] $socialIssues - * @return Collection|SocialIssue[] - */ - public static function findAncestorSocialIssues(Collection $socialIssues): Collection - { - $ancestors = new ArrayCollection(); - - foreach ($socialIssues as $candidateChild) { - if ($ancestors->contains($candidateChild)) { - continue; - } - foreach ($socialIssues as $candidateParent) { - if ($ancestors->contains($candidateParent)) { - continue; - } - - if ($candidateChild->isDescendantOf($candidateParent)) { - $ancestors->add($candidateParent); - } - } - } - - return $ancestors; + return $this; } } diff --git a/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php b/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php index 20855a166..d3b4beb47 100644 --- a/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php +++ b/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php @@ -1,17 +1,28 @@ getLabel(), 'UTF-8'); + $altname->setLabel($altnameCaps); + } + public function prePersistPerson(Person $person): void { $firstnameCaps = mb_convert_case(mb_strtolower($person->getFirstName()), MB_CASE_TITLE, 'UTF-8'); @@ -21,10 +32,4 @@ class PersonEventListener $lastnameCaps = mb_strtoupper($person->getLastName(), 'UTF-8'); $person->setLastName($lastnameCaps); } - - public function prePersistAltName(PersonAltName $altname) - { - $altnameCaps = mb_strtoupper($altname->getLabel(), 'UTF-8'); - $altname->setLabel($altnameCaps); - } } diff --git a/src/Bundle/ChillPersonBundle/Export/AbstractAccompanyingPeriodExportElement.php b/src/Bundle/ChillPersonBundle/Export/AbstractAccompanyingPeriodExportElement.php index c21d5733a..b570c83de 100644 --- a/src/Bundle/ChillPersonBundle/Export/AbstractAccompanyingPeriodExportElement.php +++ b/src/Bundle/ChillPersonBundle/Export/AbstractAccompanyingPeriodExportElement.php @@ -1,59 +1,46 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Export; use Doctrine\ORM\QueryBuilder; +use LogicException; +use function in_array; -/** - * - * - */ class AbstractAccompanyingPeriodExportElement { + /** + * Add the accompanying period alias to the query. + * + * @throws LogicException if the "person" alias is not present and attaching accompanying period is not possible + */ + protected function addJoinAccompanyingPeriod(QueryBuilder $query): void + { + if (false === $this->havingAccompanyingPeriodInJoin($query)) { + if (false === in_array('person', $query->getAllAliases(), true)) { + throw new LogicException("the alias 'person' does not exists in " + . 'query builder'); + } + + $query->join('person.accompanyingPeriods', 'accompanying_period'); + } + } + /** * Return true if "accompanying_period" alias is present in the query alises. - * - * @param QueryBuilder $query - * @return bool */ protected function havingAccompanyingPeriodInJoin(QueryBuilder $query): bool { $joins = $query->getDQLPart('join') ?? []; - - return (\in_array('accompanying_period', $query->getAllAliases())); - } - - /** - * Add the accompanying period alias to the query - * - * @param QueryBuilder $query - * @return void - * @throws \LogicException if the "person" alias is not present and attaching accompanying period is not possible - */ - protected function addJoinAccompanyingPeriod(QueryBuilder $query): void - { - if (FALSE === $this->havingAccompanyingPeriodInJoin($query)) { - if (FALSE === \in_array('person', $query->getAllAliases())) { - throw new \LogicException("the alias 'person' does not exists in " - . "query builder"); - } - - $query->join('person.accompanyingPeriods', 'accompanying_period'); - } + + return in_array('accompanying_period', $query->getAllAliases(), true); } } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AgeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AgeAggregator.php index bd07d3a48..04edade32 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AgeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AgeAggregator.php @@ -1,28 +1,21 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Aggregator; use Chill\MainBundle\Export\AggregatorInterface; +use Chill\MainBundle\Export\ExportElementValidatedInterface; +use DateTime; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\DateType; -use Chill\MainBundle\Export\ExportElementValidatedInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -54,48 +47,47 @@ final class AgeAggregator implements AggregatorInterface, ExportElementValidated public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) { - $builder->add('date_age_calculation', DateType::class, array( - 'label' => "Calculate age in relation to this date", - 'data' => new \DateTime(), - 'attr' => array('class' => 'datepicker'), - 'widget'=> 'single_text', - 'format' => 'dd-MM-yyyy' - )); - } - - public function validateForm($data, ExecutionContextInterface $context) - { - if ($data['date_age_calculation'] === null) { - $context->buildViolation("The date should not be empty") - ->addViolation(); - } + $builder->add('date_age_calculation', DateType::class, [ + 'label' => 'Calculate age in relation to this date', + 'data' => new DateTime(), + 'attr' => ['class' => 'datepicker'], + 'widget' => 'single_text', + 'format' => 'dd-MM-yyyy', + ]); } public function getLabels($key, array $values, $data) { - return function($value) { - if ($value === '_header') { - return "Age"; + return function ($value) { + if ('_header' === $value) { + return 'Age'; } - - if ($value === NULL) { - return $this->translator->trans("without data"); + + if (null === $value) { + return $this->translator->trans('without data'); } - + return $value; }; } public function getQueryKeys($data) { - return array( - 'person_age' - ); + return [ + 'person_age', + ]; } public function getTitle() { - return "Aggregate by age"; + return 'Aggregate by age'; } + public function validateForm($data, ExecutionContextInterface $context) + { + if (null === $data['date_age_calculation']) { + $context->buildViolation('The date should not be empty') + ->addViolation(); + } + } } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/CountryOfBirthAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/CountryOfBirthAggregator.php index 1deefe993..9126542ad 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/CountryOfBirthAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/CountryOfBirthAggregator.php @@ -1,37 +1,27 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Aggregator; use Chill\MainBundle\Export\AggregatorInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Doctrine\ORM\QueryBuilder; -use Doctrine\ORM\EntityRepository; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Component\Translation\TranslatorInterface; -use Chill\MainBundle\Util\CountriesInfo; -use Symfony\Component\Security\Core\Role\Role; -use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Repository\CountryRepository; -use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Util\CountriesInfo; +use Doctrine\ORM\QueryBuilder; +use LogicException; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; final class CountryOfBirthAggregator implements AggregatorInterface, ExportElementValidatedInterface { @@ -51,38 +41,17 @@ final class CountryOfBirthAggregator implements AggregatorInterface, ExportEleme $this->translator = $translator; } - public function applyOn() + public function addRole() { - return 'person'; - } - - public function buildForm(FormBuilderInterface $builder) - { - $builder->add('group_by_level', ChoiceType::class, array( - 'choices' => array( - 'Group by continents' => 'continent', - 'Group by country' => 'country' - ), - 'expanded' => true, - 'multiple' => false - )); - - } - - public function validateForm($data, ExecutionContextInterface $context) - { - if ($data['group_by_level'] === null) { - $context->buildViolation("You should select an option") - ->addViolation(); - } + return null; } public function alterQuery(QueryBuilder $qb, $data) { // add a clause in select part - if ($data['group_by_level'] === 'country') { + if ('country' === $data['group_by_level']) { $qb->addSelect('countryOfBirth.countryCode as country_of_birth_aggregator'); - } elseif ($data['group_by_level'] === 'continent') { + } elseif ('continent' === $data['group_by_level']) { $clause = 'CASE ' . 'WHEN countryOfBirth.countryCode IN(:cob_africa_codes) THEN \'AF\' ' . 'WHEN countryOfBirth.countryCode IN(:cob_asia_codes) THEN \'AS\' ' @@ -95,24 +64,24 @@ final class CountryOfBirthAggregator implements AggregatorInterface, ExportEleme . 'END as country_of_birth_aggregator '; $qb->addSelect($clause); $params = - array( + [ 'cob_africa_codes' => CountriesInfo::getCountriesCodeByContinent('AF'), 'cob_asia_codes' => CountriesInfo::getCountriesCodeByContinent('AS'), 'cob_europe_codes' => CountriesInfo::getCountriesCodeByContinent('EU'), 'cob_north_america_codes' => CountriesInfo::getCountriesCodeByContinent('NA'), 'cob_south_america_codes' => CountriesInfo::getCountriesCodeByContinent('SA'), 'cob_oceania_codes' => CountriesInfo::getCountriesCodeByContinent('OC'), - 'cob_antartica_codes' => CountriesInfo::getCountriesCodeByContinent('AN') - ); + 'cob_antartica_codes' => CountriesInfo::getCountriesCodeByContinent('AN'), + ]; + foreach ($params as $k => $v) { $qb->setParameter($k, $v); } } else { - throw new \LogicException("The group_by_level '".$data['group_by_level'] - ." is not known."); + throw new LogicException("The group_by_level '" . $data['group_by_level'] + . ' is not known.'); } - $qb->leftJoin('person.countryOfBirth', 'countryOfBirth'); // add group by @@ -123,46 +92,51 @@ final class CountryOfBirthAggregator implements AggregatorInterface, ExportEleme } else { $qb->groupBy('country_of_birth_aggregator'); } - } - public function getTitle() + public function applyOn() { - return "Group people by country of birth"; + return 'person'; } - public function getQueryKeys($data) + public function buildForm(FormBuilderInterface $builder) { - return array('country_of_birth_aggregator'); - } - - public function addRole() - { - return NULL; + $builder->add('group_by_level', ChoiceType::class, [ + 'choices' => [ + 'Group by continents' => 'continent', + 'Group by country' => 'country', + ], + 'expanded' => true, + 'multiple' => false, + ]); } public function getLabels($key, array $values, $data) { - if ($data['group_by_level'] === 'country') { + $labels = []; + + if ('country' === $data['group_by_level']) { $qb = $this->countriesRepository->createQueryBuilder('c'); $countries = $qb - ->andWhere($qb->expr()->in('c.countryCode', ':countries')) - ->setParameter('countries', $values) - ->getQuery() - ->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR); + ->andWhere($qb->expr()->in('c.countryCode', ':countries')) + ->setParameter('countries', $values) + ->getQuery() + ->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR); // initialize array and add blank key for null values - $labels[''] = $this->translator->trans('without data'); - $labels['_header'] = $this->translator->trans('Country of birth'); - foreach($countries as $row) { + $labels = [ + '' => $this->translator->trans('without data'), + '_header' => $this->translator->trans('Country of birth'), + ]; + + foreach ($countries as $row) { $labels[$row['c_countryCode']] = $this->translatableStringHelper->localize($row['c_name']); } + } - - } elseif ($data['group_by_level'] === 'continent') { - - $labels = array( + if ('continent' === $data['group_by_level']) { + $labels = [ 'EU' => $this->translator->trans('Europe'), 'AS' => $this->translator->trans('Asia'), 'AN' => $this->translator->trans('Antartica'), @@ -170,15 +144,31 @@ final class CountryOfBirthAggregator implements AggregatorInterface, ExportEleme 'SA' => $this->translator->trans('South America'), 'NA' => $this->translator->trans('North America'), 'OC' => $this->translator->trans('Oceania'), - '' => $this->translator->trans('without data'), - '_header' => $this->translator->trans('Continent of birth') - ); + '' => $this->translator->trans('without data'), + '_header' => $this->translator->trans('Continent of birth'), + ]; } - - return function($value) use ($labels) { + return static function (string $value) use ($labels): string { return $labels[$value]; }; + } + public function getQueryKeys($data) + { + return ['country_of_birth_aggregator']; + } + + public function getTitle() + { + return 'Group people by country of birth'; + } + + public function validateForm($data, ExecutionContextInterface $context) + { + if (null === $data['group_by_level']) { + $context->buildViolation('You should select an option') + ->addViolation(); + } } } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/GenderAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/GenderAggregator.php index cd3c022d9..d40f0e739 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/GenderAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/GenderAggregator.php @@ -1,30 +1,23 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Aggregator; use Chill\MainBundle\Export\AggregatorInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Doctrine\ORM\QueryBuilder; -use Symfony\Component\Translation\TranslatorInterface; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Export\Declarations; +use Doctrine\ORM\QueryBuilder; +use LogicException; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Translation\TranslatorInterface; final class GenderAggregator implements AggregatorInterface { @@ -35,6 +28,18 @@ final class GenderAggregator implements AggregatorInterface $this->translator = $translator; } + public function addRole() + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb->addSelect('person.gender as gender'); + + $qb->addGroupBy('gender'); + } + public function applyOn() { return Declarations::PERSON_TYPE; @@ -42,51 +47,40 @@ final class GenderAggregator implements AggregatorInterface public function buildForm(FormBuilderInterface $builder) { - } - - public function alterQuery(QueryBuilder $qb, $data) - { - $qb->addSelect('person.gender as gender'); - - $qb->addGroupBy('gender'); - - } - - public function getTitle() - { - return "Group people by gender"; - } - - public function getQueryKeys($data) - { - return array('gender'); - } - public function getLabels($key, array $values, $data) - { - return function($value) { + { + return function ($value) { switch ($value) { - case Person::FEMALE_GENDER : + case Person::FEMALE_GENDER: return $this->translator->trans('woman'); - case Person::MALE_GENDER : + + case Person::MALE_GENDER: return $this->translator->trans('man'); + case Person::BOTH_GENDER: return $this->translator->trans('both'); + case null: return $this->translator->trans('Not given'); - case '_header' : + + case '_header': return $this->translator->trans('Gender'); + default: - throw new \LogicException(sprintf("The value %s is not valid", $value)); + throw new LogicException(sprintf('The value %s is not valid', $value)); } }; } - public function addRole() + public function getQueryKeys($data) { - return NULL; + return ['gender']; } + public function getTitle() + { + return 'Group people by gender'; + } } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/NationalityAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/NationalityAggregator.php index e856bedc6..05a0baa05 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/NationalityAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/NationalityAggregator.php @@ -1,37 +1,27 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Aggregator; use Chill\MainBundle\Export\AggregatorInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Doctrine\ORM\QueryBuilder; -use Doctrine\ORM\EntityRepository; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Component\Translation\TranslatorInterface; -use Chill\MainBundle\Util\CountriesInfo; -use Symfony\Component\Security\Core\Role\Role; -use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Repository\CountryRepository; -use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Util\CountriesInfo; +use Doctrine\ORM\QueryBuilder; +use LogicException; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; final class NationalityAggregator implements AggregatorInterface, ExportElementValidatedInterface { @@ -51,39 +41,17 @@ final class NationalityAggregator implements AggregatorInterface, ExportElementV $this->translator = $translator; } - public function applyOn() + public function addRole() { - return 'person'; - } - - - public function buildForm(FormBuilderInterface $builder) - { - $builder->add('group_by_level', ChoiceType::class, array( - 'choices' => array( - 'Group by continents' => 'continent', - 'Group by country' => 'country' - ), - 'expanded' => true, - 'multiple' => false - )); - - } - - public function validateForm($data, ExecutionContextInterface $context) - { - if ($data['group_by_level'] === null) { - $context->buildViolation("You should select an option") - ->addViolation(); - } + return null; } public function alterQuery(QueryBuilder $qb, $data) { // add a clause in select part - if ($data['group_by_level'] === 'country') { + if ('country' === $data['group_by_level']) { $qb->addSelect('nationality.countryCode as nationality_aggregator'); - } elseif ($data['group_by_level'] === 'continent') { + } elseif ('continent' === $data['group_by_level']) { $clause = 'CASE ' . 'WHEN nationality.countryCode IN(:africa_codes) THEN \'AF\' ' . 'WHEN nationality.countryCode IN(:asia_codes) THEN \'AS\' ' @@ -96,24 +64,24 @@ final class NationalityAggregator implements AggregatorInterface, ExportElementV . 'END as nationality_aggregator '; $qb->addSelect($clause); $params = - array( + [ 'africa_codes' => CountriesInfo::getCountriesCodeByContinent('AF'), 'asia_codes' => CountriesInfo::getCountriesCodeByContinent('AS'), 'europe_codes' => CountriesInfo::getCountriesCodeByContinent('EU'), 'north_america_codes' => CountriesInfo::getCountriesCodeByContinent('NA'), 'south_america_codes' => CountriesInfo::getCountriesCodeByContinent('SA'), 'oceania_codes' => CountriesInfo::getCountriesCodeByContinent('OC'), - 'antartica_codes' => CountriesInfo::getCountriesCodeByContinent('AN') - ); + 'antartica_codes' => CountriesInfo::getCountriesCodeByContinent('AN'), + ]; + foreach ($params as $k => $v) { $qb->setParameter($k, $v); } } else { - throw new \LogicException("The group_by_level '".$data['group_by_level'] - ." is not known."); + throw new LogicException("The group_by_level '" . $data['group_by_level'] + . ' is not known.'); } - $qb->leftJoin('person.nationality', 'nationality'); // add group by @@ -124,46 +92,51 @@ final class NationalityAggregator implements AggregatorInterface, ExportElementV } else { $qb->groupBy('nationality_aggregator'); } - } - public function getTitle() + public function applyOn() { - return "Group people by nationality"; + return 'person'; } - public function getQueryKeys($data) + public function buildForm(FormBuilderInterface $builder) { - return array('nationality_aggregator'); - } - - public function addRole() - { - return NULL; + $builder->add('group_by_level', ChoiceType::class, [ + 'choices' => [ + 'Group by continents' => 'continent', + 'Group by country' => 'country', + ], + 'expanded' => true, + 'multiple' => false, + ]); } public function getLabels($key, array $values, $data) { - if ($data['group_by_level'] === 'country') { + $labels = []; + + if ('country' === $data['group_by_level']) { $qb = $this->countriesRepository->createQueryBuilder('c'); $countries = $qb - ->andWhere($qb->expr()->in('c.countryCode', ':countries')) - ->setParameter('countries', $values) - ->getQuery() - ->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR); + ->andWhere($qb->expr()->in('c.countryCode', ':countries')) + ->setParameter('countries', $values) + ->getQuery() + ->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR); // initialize array and add blank key for null values - $labels[''] = $this->translator->trans('without data'); - $labels['_header'] = $this->translator->trans('Nationality'); - foreach($countries as $row) { + $labels = [ + '' => $this->translator->trans('without data'), + '_header' => $this->translator->trans('Nationality'), + ]; + + foreach ($countries as $row) { $labels[$row['c_countryCode']] = $this->translatableStringHelper->localize($row['c_name']); } + } - - } elseif ($data['group_by_level'] === 'continent') { - - $labels = array( + if ('continent' === $data['group_by_level']) { + $labels = [ 'EU' => $this->translator->trans('Europe'), 'AS' => $this->translator->trans('Asia'), 'AN' => $this->translator->trans('Antartica'), @@ -171,15 +144,31 @@ final class NationalityAggregator implements AggregatorInterface, ExportElementV 'SA' => $this->translator->trans('South America'), 'NA' => $this->translator->trans('North America'), 'OC' => $this->translator->trans('Oceania'), - '' => $this->translator->trans('without data'), - '_header' => $this->translator->trans('Continent') - ); + '' => $this->translator->trans('without data'), + '_header' => $this->translator->trans('Continent'), + ]; } - - return function($value) use ($labels) { + return static function (string $value) use ($labels): string { return $labels[$value]; }; + } + public function getQueryKeys($data) + { + return ['nationality_aggregator']; + } + + public function getTitle() + { + return 'Group people by nationality'; + } + + public function validateForm($data, ExecutionContextInterface $context) + { + if (null === $data['group_by_level']) { + $context->buildViolation('You should select an option') + ->addViolation(); + } } } diff --git a/src/Bundle/ChillPersonBundle/Export/Declarations.php b/src/Bundle/ChillPersonBundle/Export/Declarations.php index bbdf3e8e4..b23325e5e 100644 --- a/src/Bundle/ChillPersonBundle/Export/Declarations.php +++ b/src/Bundle/ChillPersonBundle/Export/Declarations.php @@ -1,32 +1,22 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export; /** * This class declare constants used for the export framework. - * - * - * @author Julien Fastré */ abstract class Declarations { - CONST PERSON_TYPE = 'person'; - CONST PERSON_IMPLIED_IN = 'person_implied_in'; + public const PERSON_IMPLIED_IN = 'person_implied_in'; + + public const PERSON_TYPE = 'person'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php b/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php index 0431095ec..ffa69026a 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php @@ -1,136 +1,115 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\ExportInterface; -use Doctrine\ORM\QueryBuilder; -use Symfony\Component\Form\FormBuilderInterface; -use Doctrine\ORM\Query; -use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Symfony\Component\Security\Core\Role\Role; -use Chill\PersonBundle\Export\Declarations; use Chill\MainBundle\Export\FormatterInterface; +use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; +use LogicException; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Security\Core\Role\Role; -/** - * - * - * @author Julien Fastré - */ class CountPerson implements ExportInterface { /** - * * @var EntityManagerInterface */ protected $entityManager; - + public function __construct( - EntityManagerInterface $em - ) - { + EntityManagerInterface $em + ) { $this->entityManager = $em; } - - /** - * - */ - public function getType() + + public function buildForm(FormBuilderInterface $builder) { - return Declarations::PERSON_TYPE; } - + + public function getAllowedFormattersTypes() + { + return [FormatterInterface::TYPE_TABULAR]; + } + public function getDescription() { - return "Count peoples by various parameters."; + return 'Count peoples by various parameters.'; } - - public function getTitle() + + public function getLabels($key, array $values, $data) { - return "Count peoples"; + if ('export_result' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + $labels = array_combine($values, $values); + $labels['_header'] = $this->getTitle(); + + return static function ($value) use ($labels) { + return $labels[$value]; + }; } - - public function requiredRole() + + public function getQueryKeys($data) { - return new Role(PersonVoter::STATS); + return ['export_result']; } - - /** - * Initiate the query - * - * @param QueryBuilder $qb - * @return QueryBuilder - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = array()) - { - $centers = array_map(function($el) { return $el['center']; }, $acl); - - $qb = $this->entityManager->createQueryBuilder(); - - $qb->select('COUNT(person.id) AS export_result') - ->from('ChillPersonBundle:Person', 'person') - ->join('person.center', 'center') - ->andWhere('center IN (:authorized_centers)') - ->setParameter('authorized_centers', $centers); - ; - - - return $qb; - } - + public function getResult($qb, $data) { return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); } - - public function getQueryKeys($data) - { - return array('export_result'); - } - - public function getLabels($key, array $values, $data) - { - if ($key !== 'export_result') { - throw new \LogicException("the key $key is not used by this export"); - } - - $labels = array_combine($values, $values); - $labels['_header'] = $this->getTitle(); - - return function($value) use ($labels) { - return $labels[$value]; - }; - } - - public function getAllowedFormattersTypes() - { - return array(FormatterInterface::TYPE_TABULAR); - } - - public function buildForm(FormBuilderInterface $builder) { + public function getTitle() + { + return 'Count peoples'; } - + + public function getType() + { + return Declarations::PERSON_TYPE; + } + + /** + * Initiate the query. + * + * @return QueryBuilder + */ + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static function ($el) { return $el['center']; }, $acl); + + $qb = $this->entityManager->createQueryBuilder(); + + $qb->select('COUNT(person.id) AS export_result') + ->from('ChillPersonBundle:Person', 'person') + ->join('person.center', 'center') + ->andWhere('center IN (:authorized_centers)') + ->setParameter('authorized_centers', $centers); + + return $qb; + } + + public function requiredRole() + { + return new Role(PersonVoter::STATS); + } + public function supportsModifiers() { - return array(Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN); + return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN]; } - } diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php index dc36c8c57..6c02f4033 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php @@ -1,75 +1,75 @@ entityManager = $em; $this->translator = $translator; @@ -77,360 +77,214 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface $this->customFieldProvider = $customFieldProvider; } - /** - * {@inheritDoc} - * - * @param FormBuilderInterface $builder - */ public function buildForm(FormBuilderInterface $builder) { $choices = array_combine($this->fields, $this->fields); foreach ($this->getCustomFields() as $cf) { - $choices - [$this->translatableStringHelper->localize($cf->getName())] + $choices[$this->translatableStringHelper->localize($cf->getName())] = $cf->getSlug(); } // Add a checkbox to select fields - $builder->add('fields', ChoiceType::class, array( + $builder->add('fields', ChoiceType::class, [ 'multiple' => true, 'expanded' => true, 'choices' => $choices, - 'label' => 'Fields to include in export', - 'choice_attr' => function($val, $key, $index) { + 'label' => 'Fields to include in export', + 'choice_attr' => static function (string $val): array { // add a 'data-display-target' for address fields if (substr($val, 0, 8) === 'address_') { return ['data-display-target' => 'address_date']; - } else { - return []; } + + return []; }, - 'constraints' => [new Callback(array( - 'callback' => function($selected, ExecutionContextInterface $context) { + 'constraints' => [new Callback([ + 'callback' => static function ($selected, ExecutionContextInterface $context) { if (count($selected) === 0) { $context->buildViolation('You must select at least one element') ->atPath('fields') ->addViolation(); } - } - ))] - )); + }, + ])], + ]); // add a date field for addresses - $builder->add('address_date', DateType::class, array( - 'label' => "Address valid at this date", - 'data' => new \DateTime(), - 'attr' => array( 'class' => 'datepicker'), - 'widget'=> 'single_text', + $builder->add('address_date', DateType::class, [ + 'label' => 'Address valid at this date', + 'data' => new DateTime(), + 'attr' => ['class' => 'datepicker'], + 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', 'required' => false, - 'block_name' => 'list_export_form_address_date' - )); + 'block_name' => 'list_export_form_address_date', + ]); } - public function validateForm($data, ExecutionContextInterface $context) - { - // get the field starting with address_ - $addressFields = array_filter(function($el) { - return substr($el, 0, 8) === 'address_'; - }, $this->fields); - - // check if there is one field starting with address in data - if (count(array_intersect($data['fields'], $addressFields)) > 0) { - // if a field address is checked, the date must not be empty - if (empty($data['address_date'])) { - $context - ->buildViolation("You must set this date if an address is checked") - ->atPath('address_date') - ->addViolation(); - } - } - } - - /** - * Get custom fields associated with person - * - * @return CustomField[] - */ - private function getCustomFields() - { - return $this->entityManager - ->createQuery("SELECT cf " - . "FROM ChillCustomFieldsBundle:CustomField cf " - . "JOIN cf.customFieldGroup g " - . "WHERE cf.type != :title AND g.entity LIKE :entity") - ->setParameters(array( - 'title' => 'title', - 'entity' => \addcslashes(Person::class, "\\") - )) - ->getResult(); - } - - /** - * {@inheritDoc} - * - * @return type - */ public function getAllowedFormattersTypes() { - return array(FormatterInterface::TYPE_LIST); + return [FormatterInterface::TYPE_LIST]; } - /** - * {@inheritDoc} - * - * @return string - */ public function getDescription() { - return "Create a list of people according to various filters."; + return 'Create a list of people according to various filters.'; } - /** - * {@inheritDoc} - * - * @param type $key - * @param array $values - * @param type $data - * @return type - */ public function getLabels($key, array $values, $data) { switch ($key) { case 'birthdate': // for birthdate, we have to transform the string into a date // to format the date correctly. - return function($value) { - if ($value === '_header') { return 'birthdate'; } - - if (empty($value)) - { - return ""; + return static function ($value) { + if ('_header' === $value) { + return 'birthdate'; } - $date = \DateTime::createFromFormat('Y-m-d', $value); + if (empty($value)) { + return ''; + } + + $date = DateTime::createFromFormat('Y-m-d', $value); // check that the creation could occurs. - if ($date === false) { - throw new \Exception(sprintf("The value %s could " - . "not be converted to %s", $value, \DateTime::class)); + if (false === $date) { + throw new Exception(sprintf('The value %s could ' + . 'not be converted to %s', $value, DateTime::class)); } return $date->format('d-m-Y'); }; - case 'gender' : + + case 'gender': // for gender, we have to translate men/women statement - return function($value) { - if ($value === '_header') { return 'gender'; } + return function ($value) { + if ('_header' === $value) { + return 'gender'; + } return $this->translator->trans($value); }; + case 'countryOfBirth': case 'nationality': $countryRepository = $this->entityManager ->getRepository('ChillMainBundle:Country'); // load all countries in a single query - $countryRepository->findBy(array('countryCode' => $values)); + $countryRepository->findBy(['countryCode' => $values]); - return function($value) use ($key, $countryRepository) { - if ($value === '_header') { return \strtolower($key); } + return function ($value) use ($key, $countryRepository) { + if ('_header' === $value) { + return strtolower($key); + } - if ($value === NULL) { + if (null === $value) { return $this->translator->trans('no data'); } $country = $countryRepository->find($value); return $this->translatableStringHelper->localize( - $country->getName()); + $country->getName() + ); }; - case 'address_country_name': - return function($value) use ($key) { - if ($value === '_header') { return \strtolower($key); } - if ($value === NULL) { + case 'address_country_name': + return function ($value) use ($key) { + if ('_header' === $value) { + return strtolower($key); + } + + if (null === $value) { return ''; } return $this->translatableStringHelper->localize(json_decode($value, true)); }; + case 'address_isnoaddress': - return function($value) use ($key) { - if ($value === '_header') { return 'address.address_homeless'; } - - if ($value) { - return 'X'; - } else { - return ''; + return static function (?string $value): string { + if ('_header' === $value) { + return 'address.address_homeless'; } + + if (null !== $value) { + return 'X'; + } + + return ''; }; + default: // for fields which are associated with person - if (in_array($key, $this->fields)) { - return function($value) use ($key) { - if ($value === '_header') { return \strtolower($key); } + if (in_array($key, $this->fields, true)) { + return static function ($value) use ($key) { + if ('_header' === $value) { + return strtolower($key); + } return $value; - - }; - } else { - 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; + return $this->getLabelForCustomField($key, $values, $data); } } - /** - * {@inheritDoc} - * - * @param type $data - * @return type - */ public function getQueryKeys($data) { - $fields = array(); + $fields = []; foreach ($data['fields'] as $key) { - if (in_array($key, $this->fields)) { + if (in_array($key, $this->fields, true)) { $fields[] = $key; } } - + // add the key from slugs and return - return \array_merge($fields, \array_keys($this->slugs)); + return array_merge($fields, array_keys($this->slugs)); } - /** - * clean a slug to be usable by DQL - * - * @param string $slugsanitize - * @param string $type the type of the customfield, if required (currently only for choices) - * @return string - */ - private function slugToDQL($slug, $type = "default", array $additionalInfos = []) - { - $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 $this->slugs[$slug]; - } - - /** - * {@inheritDoc} - * - */ public function getResult($query, $data) { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - /** - * {@inheritDoc} - * - * @return string - */ public function getTitle() { - return "List peoples"; + return 'List peoples'; } - /** - * {@inheritDoc} - * - */ public function getType() { return Declarations::PERSON_TYPE; } - /** - * {@inheritDoc} - * - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = array()) + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = array_map(function($el) { return $el['center']; }, $acl); + $centers = array_map(static function ($el) { return $el['center']; }, $acl); // throw an error if any fields are present - if (!\array_key_exists('fields', $data)) { - throw new \Doctrine\DBAL\Exception\InvalidArgumentException("any fields " - . "have been checked"); + if (!array_key_exists('fields', $data)) { + throw new \Doctrine\DBAL\Exception\InvalidArgumentException('any fields ' + . 'have been checked'); } $qb = $this->entityManager->createQueryBuilder(); foreach ($this->fields as $f) { - if (in_array($f, $data['fields'])) { + if (in_array($f, $data['fields'], true)) { switch ($f) { case 'countryOfBirth': case 'nationality': $qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f)); + break; + case 'address_street_address_1': case 'address_street_address_2': case 'address_valid_from': @@ -439,14 +293,16 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface case 'address_country_name': case 'address_country_code': case 'address_isnoaddress': - $qb->addSelect(sprintf( 'GET_PERSON_ADDRESS_%s(person.id, :address_date) AS %s', // get the part after address_ strtoupper(substr($f, 8)), - $f)); + $f + )); $qb->setParameter('address_date', $data['address_date']); + break; + default: $qb->addSelect(sprintf('person.%s as %s', $f, $f)); } @@ -455,49 +311,159 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface foreach ($this->getCustomFields() as $cf) { $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 ]); + + if ($cfType instanceof CustomFieldChoice && $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)); + 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)); + sprintf( + 'GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s', + $slug, + $slug + ) + ); $qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug()); } } $qb - ->from('ChillPersonBundle:Person', 'person') - ->join('person.center', 'center') - ->andWhere('center IN (:authorized_centers)') - ->setParameter('authorized_centers', $centers); - ; - + ->from('ChillPersonBundle:Person', 'person') + ->join('person.center', 'center') + ->andWhere('center IN (:authorized_centers)') + ->setParameter('authorized_centers', $centers); return $qb; } - /** - * - * {@inheritDoc} - */ public function requiredRole() { return new Role(PersonVoter::LISTS); } - /** - * - * {@inheritDoc} - */ public function supportsModifiers() { - return array(Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN); + return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN]; + } + + public function validateForm($data, ExecutionContextInterface $context) + { + // get the field starting with address_ + $addressFields = array_filter( + $this->fields, + static fn (string $el): bool => substr($el, 0, 8) === 'address_' + ); + + // check if there is one field starting with address in data + if (count(array_intersect($data['fields'], $addressFields)) > 0) { + // if a field address is checked, the date must not be empty + if (empty($data['address_date'])) { + $context + ->buildViolation('You must set this date if an address is checked') + ->atPath('address_date') + ->addViolation(); + } + } + } + + private function DQLToSlug($cleanedSlug) + { + return $this->slugs[$cleanedSlug]['slug']; + } + + /** + * @param mixed $slug + * + * @return array An array with keys = 'slug', 'type', 'additionnalInfo' + */ + private function extractInfosFromSlug($slug): array + { + return $this->slugs[$slug]; + } + + /** + * Get custom fields associated with person. + * + * @return CustomField[] + */ + private function getCustomFields() + { + return $this->entityManager + ->createQuery('SELECT cf ' + . 'FROM ChillCustomFieldsBundle:CustomField cf ' + . 'JOIN cf.customFieldGroup g ' + . 'WHERE cf.type != :title AND g.entity LIKE :entity') + ->setParameters([ + 'title' => 'title', + 'entity' => addcslashes(Person::class, '\\'), + ]) + ->getResult(); + } + + private function getLabelForCustomField($key, array $values, $data) + { + // for fields which are custom fields + /** @var CustomField $cf */ + $cf = $this->entityManager + ->getRepository(CustomField::class) + ->findOneBy(['slug' => $this->DQLToSlug($key)]); + $cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType()); + $defaultFunction = function ($value) use ($cf) { + if ('_header' === $value) { + return $this->translatableStringHelper->localize($cf->getName()); + } + + return $this->customFieldProvider + ->getCustomFieldByType($cf->getType()) + ->render(json_decode($value, true), $cf, 'csv'); + }; + + if ($cfType instanceof CustomFieldChoice && $cfType->isMultiple($cf)) { + return function ($value) use ($cf, $cfType, $key) { + $slugChoice = $this->extractInfosFromSlug($key)['additionnalInfos']['choiceSlug']; + $decoded = json_decode($value, true); + + if ('_header' === $value) { + $label = $cfType->getChoices($cf)[$slugChoice]; + + return $this->translatableStringHelper->localize($cf->getName()) + . ' | ' . $label; + } + + if ('_other' === $slugChoice && $cfType->isChecked($cf, $choiceSlug, $decoded)) { + return $cfType->extractOtherValue($cf, $decoded); + } + + return $cfType->isChecked($cf, $slugChoice, $decoded); + }; + } + + return $defaultFunction; + } + + /** + * Clean a slug to be usable by DQL. + */ + private function slugToDQL(string $slug, string $type = 'default', array $additionalInfos = []): string + { + $uid = 'slug_' . uniqid('', true); + + $this->slugs[$uid] = [ + 'slug' => $slug, + 'type' => $type, + 'additionnalInfos' => $additionalInfos, + ]; + + return $uid; } } diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php index 366e952ba..d82c8b0aa 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php @@ -1,11 +1,18 @@ entityManager = $em; $this->translator = $translator; $this->router = $router; - $this->baseUrl = $routeParameters['scheme']. - '://'.$routeParameters['host']; + $this->baseUrl = $routeParameters['scheme'] . + '://' . $routeParameters['host']; } - /** - * {@inheritDoc} - * - * @return string - */ - public function getTitle() - { - return "List duplicates"; - } - - /** - * {@inheritDoc} - * - * @return string - */ - public function getDescription() - { - return "Create a list of duplicate people."; - } - - /** - * {@inheritDoc} - * - * @param FormBuilderInterface $builder - */ public function buildForm(FormBuilderInterface $builder) { $builder->add('precision', NumberType::class, [ @@ -97,27 +78,23 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat ]); } - public function validateForm($data, ExecutionContextInterface $context) - { - - } - public function generate(array $acl, array $data = []): Response { $values = []; $values[] = $this->getHeaders(); $result = $this->getResult($data); + foreach ($result as $row) { $values[] = [ $row['id1'], $row['firstname1'], $row['lastname1'], - $this->baseUrl.$this->router->generate('chill_person_view', ['person_id' => $row['id1']]), + $this->baseUrl . $this->router->generate('chill_person_view', ['person_id' => $row['id1']]), $row['id2'], $row['firstname2'], $row['lastname2'], - $this->baseUrl.$this->router->generate('chill_person_view', ['person_id' => $row['id2']]), + $this->baseUrl . $this->router->generate('chill_person_view', ['person_id' => $row['id2']]), ]; } @@ -125,15 +102,15 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat $spreadsheet->getActiveSheet()->fromArray($values); // Make links clickable - for ($i = 1; $i <= $spreadsheet->getActiveSheet()->getHighestDataRow(); $i++) { - $spreadsheet->getActiveSheet()->getCell('D'.$i)->getHyperlink() - ->setUrl($spreadsheet->getActiveSheet()->getCell('D'.$i)->getValue()); - $spreadsheet->getActiveSheet()->getCell('H'.$i)->getHyperlink() - ->setUrl($spreadsheet->getActiveSheet()->getCell('H'.$i)->getValue()); + for ($i = 1; $spreadsheet->getActiveSheet()->getHighestDataRow() >= $i; ++$i) { + $spreadsheet->getActiveSheet()->getCell('D' . $i)->getHyperlink() + ->setUrl($spreadsheet->getActiveSheet()->getCell('D' . $i)->getValue()); + $spreadsheet->getActiveSheet()->getCell('H' . $i)->getHyperlink() + ->setUrl($spreadsheet->getActiveSheet()->getCell('H' . $i)->getValue()); } $writer = new Xlsx($spreadsheet); - $temp_file = sys_get_temp_dir().'/'.uniqid('export_').'.xlsx'; + $temp_file = sys_get_temp_dir() . '/' . uniqid('export_') . '.xlsx'; $writer->save($temp_file); $response = new BinaryFileResponse($temp_file); @@ -143,6 +120,45 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat return $response; } + /** + * @return string + */ + public function getDescription() + { + return 'Create a list of duplicate people.'; + } + + /** + * @return string + */ + public function getTitle() + { + return 'List duplicates'; + } + + public function requiredRole(): Role + { + return new Role(PersonVoter::DUPLICATE); + } + + public function validateForm($data, ExecutionContextInterface $context) + { + } + + protected function getHeaders(): array + { + return [ + $this->translator->trans('Departure folder number'), + $this->translator->trans('Last name'), + $this->translator->trans('First name'), + $this->translator->trans('Link'), + $this->translator->trans('Arrival folder number'), + $this->translator->trans('Last name'), + $this->translator->trans('First name'), + $this->translator->trans('Link'), + ]; + } + protected function getResult($data = []) { $precision = $data['precision'] ?? self::PRECISION_DEFAULT_VALUE; @@ -153,23 +169,23 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat SIMILARITY(p.fullnamecanonical, p2.fullnamecanonical) AS "similarity nom + prenom", SIMILARITY(p.lastname, p2.lastname) AS "similarity nom", SIMILARITY(p.firstname, p2.firstname) AS "similarity prenom" - FROM chill_person_person AS p - JOIN chill_person_person AS p2 - ON p.id != p2.id - AND (SIMILARITY(p.fullnamecanonical, p2.fullnamecanonical) > :precision + FROM chill_person_person AS p + JOIN chill_person_person AS p2 + ON p.id != p2.id + AND (SIMILARITY(p.fullnamecanonical, p2.fullnamecanonical) > :precision AND p.id < p2.id) OR (UNACCENT(LOWER(p.firstname)) = UNACCENT(LOWER(p2.lastname)) AND UNACCENT(LOWER(p.lastname)) = UNACCENT(LOWER(p2.firstname))) - JOIN centers AS p1center + JOIN centers AS p1center ON p1center.id = p.center_id - JOIN centers AS p2center + JOIN centers AS p2center ON p2center.id = p2.center_id WHERE NOT EXISTS ( - SELECT id - FROM chill_person_not_duplicate as pnd - WHERE (pnd.person1_id = p.id + SELECT id + FROM chill_person_not_duplicate as pnd + WHERE (pnd.person1_id = p.id AND pnd.person2_id = p2.id) - OR (pnd.person2_id = p.id + OR (pnd.person2_id = p.id AND pnd.person1_id = p2.id) ) ORDER BY p.fullnamecanonical, p.id, p2.id'; @@ -180,23 +196,4 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat return $statement->fetchAll(); } - - protected function getHeaders(): array - { - return [ - $this->translator->trans('Departure folder number'), - $this->translator->trans('Last name'), - $this->translator->trans('First name'), - $this->translator->trans('Link'), - $this->translator->trans('Arrival folder number'), - $this->translator->trans('Last name'), - $this->translator->trans('First name'), - $this->translator->trans('Link'), - ]; - } - - public function requiredRole(): Role - { - return new Role(PersonVoter::DUPLICATE); - } } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodClosingFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodClosingFilter.php index 7179de4d3..787bf46d6 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodClosingFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodClosingFilter.php @@ -1,33 +1,24 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Filter; use Chill\MainBundle\Export\FilterInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Doctrine\ORM\QueryBuilder; use Chill\MainBundle\Form\Type\ChillDateType; -use Doctrine\DBAL\Types\Type; use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; +use DateTime; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Form\FormBuilderInterface; -/** - * - * - */ class AccompanyingPeriodClosingFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface { public function addRole() @@ -38,14 +29,15 @@ class AccompanyingPeriodClosingFilter extends AbstractAccompanyingPeriodExportEl public function alterQuery(QueryBuilder $qb, $data) { $this->addJoinAccompanyingPeriod($qb); - + $clause = $qb->expr()->andX( $qb->expr()->lte('accompanying_period.closingDate', ':date_to'), - $qb->expr()->gte('accompanying_period.closingDate', ':date_from')); + $qb->expr()->gte('accompanying_period.closingDate', ':date_from') + ); $qb->andWhere($clause); - $qb->setParameter('date_from', $data['date_from'], Type::DATE); - $qb->setParameter('date_to', $data['date_to'], Type::DATE); + $qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE); + $qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE); } public function applyOn(): string @@ -55,31 +47,31 @@ class AccompanyingPeriodClosingFilter extends AbstractAccompanyingPeriodExportEl public function buildForm(FormBuilderInterface $builder) { - $builder->add('date_from', ChillDateType::class, array( - 'label' => "Having an accompanying period closed after this date", - 'data' => new \DateTime("-1 month"), - )); - - $builder->add('date_to', ChillDateType::class, array( - 'label' => "Having an accompanying period closed before this date", - 'data' => new \DateTime(), - )); + $builder->add('date_from', ChillDateType::class, [ + 'label' => 'Having an accompanying period closed after this date', + 'data' => new DateTime('-1 month'), + ]); + + $builder->add('date_to', ChillDateType::class, [ + 'label' => 'Having an accompanying period closed before this date', + 'data' => new DateTime(), + ]); } public function describeAction($data, $format = 'string') { return [ - "Filtered by accompanying period: persons having an accompanying period" - . " closed between the %date_from% and %date_to%", + 'Filtered by accompanying period: persons having an accompanying period' + . ' closed between the %date_from% and %date_to%', [ '%date_from%' => $data['date_from']->format('d-m-Y'), - '%date_to%' => $data['date_to']->format('d-m-Y') - ] + '%date_to%' => $data['date_to']->format('d-m-Y'), + ], ]; } public function getTitle(): string { - return "Filter by accompanying period: closed between two dates"; + return 'Filter by accompanying period: closed between two dates'; } } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodFilter.php index 40f7dd501..1c658527f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodFilter.php @@ -1,33 +1,24 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Filter; use Chill\MainBundle\Export\FilterInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Doctrine\ORM\QueryBuilder; use Chill\MainBundle\Form\Type\ChillDateType; -use Doctrine\DBAL\Types\Type; use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; +use DateTime; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Form\FormBuilderInterface; -/** - * - * - */ class AccompanyingPeriodFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface { public function addRole() @@ -38,22 +29,22 @@ class AccompanyingPeriodFilter extends AbstractAccompanyingPeriodExportElement i public function alterQuery(QueryBuilder $qb, $data) { $this->addJoinAccompanyingPeriod($qb); - + $clause = $qb->expr()->andX(); - + $clause->add( $qb->expr()->lte('accompanying_period.openingDate', ':date_to') - ); + ); $clause->add( $qb->expr()->orX( $qb->expr()->gte('accompanying_period.closingDate', ':date_from'), $qb->expr()->isNull('accompanying_period.closingDate') - ) - ); + ) + ); $qb->andWhere($clause); - $qb->setParameter('date_from', $data['date_from'], Type::DATE); - $qb->setParameter('date_to', $data['date_to'], Type::DATE); + $qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE); + $qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE); } public function applyOn(): string @@ -63,33 +54,33 @@ class AccompanyingPeriodFilter extends AbstractAccompanyingPeriodExportElement i public function buildForm(FormBuilderInterface $builder) { - $builder->add('date_from', ChillDateType::class, array( - 'label' => "Having an accompanying period opened after this date", - 'data' => new \DateTime("-1 month"), - )); - - $builder->add('date_to', ChillDateType::class, array( - 'label' => "Having an accompanying period ending before this date, or " - . "still opened at this date", - 'data' => new \DateTime(), - )); + $builder->add('date_from', ChillDateType::class, [ + 'label' => 'Having an accompanying period opened after this date', + 'data' => new DateTime('-1 month'), + ]); + + $builder->add('date_to', ChillDateType::class, [ + 'label' => 'Having an accompanying period ending before this date, or ' + . 'still opened at this date', + 'data' => new DateTime(), + ]); } public function describeAction($data, $format = 'string') { return [ - "Filtered by accompanying period: persons having an accompanying period" - . " opened after the %date_from% and closed before the %date_to% (or still opened " - . "at the %date_to%)", + 'Filtered by accompanying period: persons having an accompanying period' + . ' opened after the %date_from% and closed before the %date_to% (or still opened ' + . 'at the %date_to%)', [ '%date_from%' => $data['date_from']->format('d-m-Y'), - '%date_to%' => $data['date_to']->format('d-m-Y') - ] + '%date_to%' => $data['date_to']->format('d-m-Y'), + ], ]; } public function getTitle(): string { - return "Filter by accompanying period: active period"; + return 'Filter by accompanying period: active period'; } } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodOpeningFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodOpeningFilter.php index 26a8818df..7d00060f4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodOpeningFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodOpeningFilter.php @@ -1,33 +1,24 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Filter; use Chill\MainBundle\Export\FilterInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Doctrine\ORM\QueryBuilder; use Chill\MainBundle\Form\Type\ChillDateType; -use Doctrine\DBAL\Types\Type; use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; +use DateTime; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Form\FormBuilderInterface; -/** - * - * - */ class AccompanyingPeriodOpeningFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface { public function addRole() @@ -38,14 +29,15 @@ class AccompanyingPeriodOpeningFilter extends AbstractAccompanyingPeriodExportEl public function alterQuery(QueryBuilder $qb, $data) { $this->addJoinAccompanyingPeriod($qb); - + $clause = $qb->expr()->andX( $qb->expr()->lte('accompanying_period.openingDate', ':date_to'), - $qb->expr()->gte('accompanying_period.openingDate', ':date_from')); + $qb->expr()->gte('accompanying_period.openingDate', ':date_from') + ); $qb->andWhere($clause); - $qb->setParameter('date_from', $data['date_from'], Type::DATE); - $qb->setParameter('date_to', $data['date_to'], Type::DATE); + $qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE); + $qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE); } public function applyOn(): string @@ -55,31 +47,31 @@ class AccompanyingPeriodOpeningFilter extends AbstractAccompanyingPeriodExportEl public function buildForm(FormBuilderInterface $builder) { - $builder->add('date_from', ChillDateType::class, array( - 'label' => "Having an accompanying period opened after this date", - 'data' => new \DateTime("-1 month"), - )); - - $builder->add('date_to', ChillDateType::class, array( - 'label' => "Having an accompanying period opened before this date", - 'data' => new \DateTime(), - )); + $builder->add('date_from', ChillDateType::class, [ + 'label' => 'Having an accompanying period opened after this date', + 'data' => new DateTime('-1 month'), + ]); + + $builder->add('date_to', ChillDateType::class, [ + 'label' => 'Having an accompanying period opened before this date', + 'data' => new DateTime(), + ]); } public function describeAction($data, $format = 'string') { return [ - "Filtered by accompanying period: persons having an accompanying period" - . " opened between the %date_from% and %date_to%", + 'Filtered by accompanying period: persons having an accompanying period' + . ' opened between the %date_from% and %date_to%', [ '%date_from%' => $data['date_from']->format('d-m-Y'), - '%date_to%' => $data['date_to']->format('d-m-Y') - ] + '%date_to%' => $data['date_to']->format('d-m-Y'), + ], ]; } public function getTitle(): string { - return "Filter by accompanying period: starting between two dates"; + return 'Filter by accompanying period: starting between two dates'; } } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/BirthdateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/BirthdateFilter.php index 234d6bd3e..36457e0f8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/BirthdateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/BirthdateFilter.php @@ -1,43 +1,25 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Filter; +use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Export\FilterInterface; +use DateTime; +use Doctrine\ORM\Query\Expr; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Symfony\Component\Validator\Constraints; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; -use Doctrine\ORM\Query\Expr; -use Chill\MainBundle\Form\Type\Export\FilterType; -use Symfony\Component\Form\FormError; -use Chill\MainBundle\Export\ExportElementValidatedInterface; -/** - * - * - * @author Julien Fastré - */ -class BirthdateFilter implements FilterInterface, ExportElementValidatedInterface +class BirthdateFilter implements ExportElementValidatedInterface, FilterInterface { - public function addRole() { return null; @@ -46,15 +28,18 @@ class BirthdateFilter implements FilterInterface, ExportElementValidatedInterfac public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data) { $where = $qb->getDQLPart('where'); - $clause = $qb->expr()->between('person.birthdate', ':date_from', - ':date_to'); + $clause = $qb->expr()->between( + 'person.birthdate', + ':date_from', + ':date_to' + ); if ($where instanceof Expr\Andx) { $where->add($clause); } else { $where = $qb->expr()->andX($clause); } - + $qb->add('where', $where); $qb->setParameter('date_from', $data['date_from']); $qb->setParameter('date_to', $data['date_to']); @@ -67,59 +52,30 @@ class BirthdateFilter implements FilterInterface, ExportElementValidatedInterfac public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) { - $builder->add('date_from', DateType::class, array( - 'label' => "Born after this date", - 'data' => new \DateTime(), - 'attr' => array('class' => 'datepicker'), - 'widget'=> 'single_text', + $builder->add('date_from', DateType::class, [ + 'label' => 'Born after this date', + 'data' => new DateTime(), + 'attr' => ['class' => 'datepicker'], + 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', - )); - - $builder->add('date_to', DateType::class, array( - 'label' => "Born before this date", - 'data' => new \DateTime(), - 'attr' => array('class' => 'datepicker'), - 'widget'=> 'single_text', + ]); + + $builder->add('date_to', DateType::class, [ + 'label' => 'Born before this date', + 'data' => new DateTime(), + 'attr' => ['class' => 'datepicker'], + 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', - )); - - } - - public function validateForm($data, ExecutionContextInterface $context) - { - $date_from = $data['date_from']; - $date_to = $data['date_to']; - - if ($date_from === null) { - $context->buildViolation('The "date from" should not be empty') - //->atPath('date_from') - ->addViolation(); - } - - if ($date_to === null) { - $context->buildViolation('The "date to" should not be empty') - //->atPath('date_to') - ->addViolation(); - } - - if ( - ($date_from !== null && $date_to !== null) - && - $date_from >= $date_to - ) { - $context->buildViolation('The date "date to" should be after the ' - . 'date given in "date from" field') - ->addViolation(); - } + ]); } public function describeAction($data, $format = 'string') { - return array('Filtered by person\'s birtdate: ' - . 'between %date_from% and %date_to%', array( + return ['Filtered by person\'s birtdate: ' + . 'between %date_from% and %date_to%', [ '%date_from%' => $data['date_from']->format('d-m-Y'), - '%date_to%' => $data['date_to']->format('d-m-Y') - )); + '%date_to%' => $data['date_to']->format('d-m-Y'), + ], ]; } public function getTitle() @@ -127,4 +83,30 @@ class BirthdateFilter implements FilterInterface, ExportElementValidatedInterfac return 'Filter by person\'s birthdate'; } + public function validateForm($data, ExecutionContextInterface $context) + { + $date_from = $data['date_from']; + $date_to = $data['date_to']; + + if (null === $date_from) { + $context->buildViolation('The "date from" should not be empty') + //->atPath('date_from') + ->addViolation(); + } + + if (null === $date_to) { + $context->buildViolation('The "date to" should not be empty') + //->atPath('date_to') + ->addViolation(); + } + + if ( + (null !== $date_from && null !== $date_to) + && $date_from >= $date_to + ) { + $context->buildViolation('The date "date to" should be after the ' + . 'date given in "date from" field') + ->addViolation(); + } + } } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/GenderFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/GenderFilter.php index 7db76f6a7..c7587874b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/GenderFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/GenderFilter.php @@ -1,90 +1,56 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Filter; -use Chill\MainBundle\Export\FilterInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Chill\PersonBundle\Entity\Person; -use Doctrine\ORM\QueryBuilder; -use Doctrine\ORM\Query\Expr; -use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\Export\ExportElementValidatedInterface; -use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Chill\MainBundle\Export\FilterInterface; +use Chill\PersonBundle\Entity\Person; +use Doctrine\ORM\Query\Expr; +use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use function array_filter; +use function count; +use function implode; +use function in_array; +use function is_array; -/** - * - * - * @author Julien Fastré - */ -class GenderFilter implements FilterInterface, - ExportElementValidatedInterface +class GenderFilter implements + ExportElementValidatedInterface, + FilterInterface { /** - * * @var TranslatorInterface */ protected $translator; - - function __construct(TranslatorInterface $translator) + + public function __construct(TranslatorInterface $translator) { $this->translator = $translator; } - public function applyOn() + public function addRole() { - return 'person'; - } - - /** - * - */ - public function buildForm(FormBuilderInterface $builder) - { - $builder->add('accepted_genders', ChoiceType::class, array( - 'choices' => array( - 'Woman' => Person::FEMALE_GENDER, - 'Man' => Person::MALE_GENDER, - 'Both' => Person::BOTH_GENDER, - 'Not given' => 'null' - ), - 'multiple' => true, - 'expanded' => true - )); - } - - public function validateForm($data, ExecutionContextInterface $context) - { - if (!is_array($data['accepted_genders']) || count($data['accepted_genders']) === 0 ) { - $context->buildViolation("You should select an option") - ->addViolation(); - } + return null; } public function alterQuery(QueryBuilder $qb, $data) { $where = $qb->getDQLPart('where'); $isIn = $qb->expr()->in('person.gender', ':person_gender'); - - if (!\in_array('null', $data['accepted_genders'])) { + + if (!in_array('null', $data['accepted_genders'], true)) { $clause = $isIn; } else { $clause = $qb->expr()->orX($isIn, $qb->expr()->isNull('person.gender')); @@ -97,15 +63,53 @@ class GenderFilter implements FilterInterface, } $qb->add('where', $where); - $qb->setParameter('person_gender', \array_filter( + $qb->setParameter('person_gender', array_filter( $data['accepted_genders'], - function($el) { - return $el !== 'null'; - })); + static function ($el) { + return 'null' !== $el; + } + )); + } + + public function applyOn() + { + return 'person'; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_genders', ChoiceType::class, [ + 'choices' => [ + 'Woman' => Person::FEMALE_GENDER, + 'Man' => Person::MALE_GENDER, + 'Both' => Person::BOTH_GENDER, + 'Not given' => 'null', + ], + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string') + { + $genders = []; + + foreach ($data['accepted_genders'] as $g) { + if ('null' === $g) { + $genders[] = $this->translator->trans('Not given'); + } else { + $genders[] = $this->translator->trans($g); + } + } + + return [ + 'Filtering by genders: only %genders%', + ['%genders%' => implode(', ', $genders)], + ]; } /** - * A title which will be used in the label for the form + * A title which will be used in the label for the form. * * @return string */ @@ -114,26 +118,11 @@ class GenderFilter implements FilterInterface, return 'Filter by person gender'; } - public function addRole() + public function validateForm($data, ExecutionContextInterface $context) { - return NULL; - } - - public function describeAction($data, $format = 'string') - { - $genders = []; - - foreach ($data['accepted_genders'] as $g) { - if ('null' === $g) { - $genders[] = $this->translator->trans('Not given'); - } else { - $genders[] = $this->translator->trans($g); - } + if (!is_array($data['accepted_genders']) || count($data['accepted_genders']) === 0) { + $context->buildViolation('You should select an option') + ->addViolation(); } - - return [ - "Filtering by genders: only %genders%", - [ "%genders%" => \implode(", ", $genders)] - ]; } } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/NationalityFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/NationalityFilter.php index d6dc52af8..602457024 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/NationalityFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/NationalityFilter.php @@ -1,45 +1,31 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Export\Filter; -use Symfony\Component\Form\FormBuilderInterface; -use Doctrine\ORM\QueryBuilder; -use Chill\MainBundle\Export\FilterInterface; -use Doctrine\ORM\Query\Expr; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Doctrine\ORM\EntityRepository; use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\Select2CountryType; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\ORM\Query\Expr; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; -/** - * - * - * @author Julien Fastré - */ -class NationalityFilter implements FilterInterface, - ExportElementValidatedInterface +class NationalityFilter implements + ExportElementValidatedInterface, + FilterInterface { /** - * * @var TranslatableStringHelper */ private $translatableStringHelper; @@ -49,25 +35,9 @@ class NationalityFilter implements FilterInterface, $this->translatableStringHelper = $helper; } - public function applyOn() + public function addRole() { - return 'person'; - } - - public function buildForm(FormBuilderInterface $builder) - { - $builder->add('nationalities', Select2CountryType::class, array( - 'placeholder' => 'Choose countries' - )); - - } - - public function validateForm($data, ExecutionContextInterface $context) - { - if ($data['nationalities'] === null) { - $context->buildViolation("A nationality must be selected") - ->addViolation(); - } + return null; } public function alterQuery(QueryBuilder $qb, $data) @@ -82,7 +52,33 @@ class NationalityFilter implements FilterInterface, } $qb->add('where', $where); - $qb->setParameter('person_nationality', array($data['nationalities'])); + $qb->setParameter('person_nationality', [$data['nationalities']]); + } + + public function applyOn() + { + return 'person'; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('nationalities', Select2CountryType::class, [ + 'placeholder' => 'Choose countries', + ]); + } + + public function describeAction($data, $format = 'string') + { + $countries = $data['nationalities']; + + $names = array_map(function (Country $c) { + return $this->translatableStringHelper->localize($c->getName()); + }, [$countries]); + + return [ + 'Filtered by nationality : %nationalities%', + ['%nationalities%' => implode(', ', $names)], + ]; } public function getTitle() @@ -90,22 +86,11 @@ class NationalityFilter implements FilterInterface, return "Filter by person's nationality"; } - public function addRole() + public function validateForm($data, ExecutionContextInterface $context) { - return NULL; - } - - public function describeAction($data, $format = 'string') - { - $countries = $data['nationalities']; - - $names = array_map(function(Country $c) { - return $this->translatableStringHelper->localize($c->getName()); - }, array($countries)); - - return array( - "Filtered by nationality : %nationalities%", - array('%nationalities%' => implode(", ", $names)) - ); + if (null === $data['nationalities']) { + $context->buildViolation('A nationality must be selected') + ->addViolation(); + } } } diff --git a/src/Bundle/ChillPersonBundle/Form/AccompanyingCourseType.php b/src/Bundle/ChillPersonBundle/Form/AccompanyingCourseType.php new file mode 100644 index 000000000..55d7f7c92 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Form/AccompanyingCourseType.php @@ -0,0 +1,33 @@ +add( + 'closingDate', + ChillDateType::class, + [ + 'required' => true, + ] + ); + + $builder->add('closingMotive', ClosingMotivePickerType::class); + } +} diff --git a/src/Bundle/ChillPersonBundle/Form/AccompanyingPeriodType.php b/src/Bundle/ChillPersonBundle/Form/AccompanyingPeriodType.php index b4c146960..ec1766088 100644 --- a/src/Bundle/ChillPersonBundle/Form/AccompanyingPeriodType.php +++ b/src/Bundle/ChillPersonBundle/Form/AccompanyingPeriodType.php @@ -1,25 +1,31 @@ config = $config; } - - /** - * @param FormBuilderInterface $builder - * @param array $options - */ + public function buildForm(FormBuilderInterface $builder, array $options) { //if the period_action is close, date opening should not be seen - if ($options['period_action'] !== 'close') { + if ('close' !== $options['period_action']) { $builder ->add('openingDate', DateType::class, [ - "required" => true, - 'widget' => 'single_text', - 'format' => 'dd-MM-yyyy' - ]) - ; + 'required' => true, + 'widget' => 'single_text', + 'format' => 'dd-MM-yyyy', + ]); } - + // closingDate should be seen only if // period_action = close // OR ( period_action = update AND accompanying period is already closed ) $accompanyingPeriod = $options['data']; - + if ( - ($options['period_action'] === 'close') - OR - ($options['period_action'] === 'create') - OR - ($options['period_action'] === 'update' AND !$accompanyingPeriod->isOpen()) + ('close' === $options['period_action']) + || ('create' === $options['period_action']) + || ('update' === $options['period_action'] && !$accompanyingPeriod->isOpen()) ) { - $builder->add('closingDate', DateType::class, [ 'required' => true, 'widget' => 'single_text', - 'format' => 'dd-MM-yyyy' + 'format' => 'dd-MM-yyyy', ]); - + $builder->add('closingMotive', ClosingMotivePickerType::class); } - - if ($this->config['user'] === 'visible') { + + if ('visible' === $this->config['user']) { $builder->add('user', UserPickerType::class, [ 'center' => $options['center'], - 'role' => new Role(PersonVoter::SEE), + 'role' => new Role(PersonVoter::SEE), ]); } - $builder->add('remark', ChillTextareaType::class, [ - 'required' => false - ]); + $builder->add('remark', ChillTextareaType::class, [ + 'required' => false, + ]); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['action'] = $options['period_action']; } - /** - * @param OptionsResolver $resolver - */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ - 'data_class' => 'Chill\PersonBundle\Entity\AccompanyingPeriod' + 'data_class' => 'Chill\PersonBundle\Entity\AccompanyingPeriod', ]); $resolver @@ -107,18 +106,7 @@ class AccompanyingPeriodType extends AbstractType ->addAllowedTypes('period_action', 'string') ->addAllowedValues('period_action', ['update', 'open', 'close', 'create']) ->setRequired('center') - ->setAllowedTypes('center', Center::class) - ; - } - - /** - * @param FormView $view - * @param FormInterface $form - * @param array $options - */ - public function buildView(FormView $view, FormInterface $form, array $options) - { - $view->vars['action'] = $options['period_action']; + ->setAllowedTypes('center', Center::class); } /** diff --git a/src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php b/src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php index db1d5c6f8..2924b7e35 100644 --- a/src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php +++ b/src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php @@ -1,56 +1,116 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Form\ChoiceLoader; -use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; -use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Repository\PersonRepository; +use RuntimeException; +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use function call_user_func; +use function count; +use function in_array; /** - * Allow to load a list of person + * Allow to load a list of person. */ class PersonChoiceLoader implements ChoiceLoaderInterface { - protected PersonRepository $personRepository; - - protected array $lazyLoadedPersons = []; - protected array $centers = []; - + + protected array $lazyLoadedPersons = []; + + protected PersonRepository $personRepository; + /** * PersonChoiceLoader constructor. * * @param EntityRepository $personRepository - * @param array|null $centers */ public function __construct( PersonRepository $personRepository, - array $centers = null + ?array $centers = null ) { $this->personRepository = $personRepository; - if (NULL !== $centers) { + + if (null !== $centers) { $this->centers = $centers; } } - + + /** + * @param null $value + */ + public function loadChoiceList($value = null): ChoiceListInterface + { + return new \Symfony\Component\Form\ChoiceList\ArrayChoiceList( + $this->lazyLoadedPersons, + static function (Person $p) use ($value) { + return call_user_func($value, $p); + } + ); + } + + /** + * @param null $value + * + * @return array + */ + public function loadChoicesForValues(array $values, $value = null) + { + $choices = []; + + foreach ($values as $value) { + if (empty($value)) { + continue; + } + + $person = $this->personRepository->find($value); + + if ($this->hasCenterFilter() + && !in_array($person->getCenter(), $this->centers, true)) { + throw new RuntimeException('chosen a person not in correct center'); + } + + $choices[] = $person; + } + + return $choices; + } + + /** + * @param null $value + * + * @return array|string[] + */ + public function loadValuesForChoices(array $choices, $value = null) + { + $values = []; + + foreach ($choices as $choice) { + if (null === $choice) { + $values[] = null; + + continue; + } + + $id = call_user_func($value, $choice); + $values[] = $id; + $this->lazyLoadedPersons[$id] = $choice; + } + + return $values; + } + /** * @return bool */ @@ -58,69 +118,4 @@ class PersonChoiceLoader implements ChoiceLoaderInterface { return count($this->centers) > 0; } - - /** - * @param null $value - * @return ChoiceListInterface - */ - public function loadChoiceList($value = null): ChoiceListInterface - { - $list = new \Symfony\Component\Form\ChoiceList\ArrayChoiceList( - $this->lazyLoadedPersons, - function(Person $p) use ($value) { - return \call_user_func($value, $p); - }); - - return $list; - } - - /** - * @param array $values - * @param null $value - * @return array - */ - public function loadChoicesForValues(array $values, $value = null) - { - $choices = []; - - foreach($values as $value) { - if (empty($value)) { - continue; - } - - $person = $this->personRepository->find($value); - - if ($this->hasCenterFilter() && - !\in_array($person->getCenter(), $this->centers)) { - throw new \RuntimeException("chosen a person not in correct center"); - } - - $choices[] = $person; - } - - return $choices; - } - - /** - * @param array $choices - * @param null $value - * @return array|string[] - */ - public function loadValuesForChoices(array $choices, $value = null) - { - $values = []; - - foreach ($choices as $choice) { - if (NULL === $choice) { - $values[] = null; - continue; - } - - $id = \call_user_func($value, $choice); - $values[] = $id; - $this->lazyLoadedPersons[$id] = $choice; - } - - return $values; - } } diff --git a/src/Bundle/ChillPersonBundle/Form/ClosingMotiveType.php b/src/Bundle/ChillPersonBundle/Form/ClosingMotiveType.php index 672fc4f19..9cf24d8c3 100644 --- a/src/Bundle/ChillPersonBundle/Form/ClosingMotiveType.php +++ b/src/Bundle/ChillPersonBundle/Form/ClosingMotiveType.php @@ -1,76 +1,57 @@ - * - * 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; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; -use Chill\PersonBundle\Form\Type\ClosingMotivePickerType; -use Chill\MainBundle\Form\Type\TranslatableStringFormType; -use Symfony\Component\Form\Extension\Core\Type\CheckboxType; -use Symfony\Component\Form\Extension\Core\Type\NumberType; /** - * Class ClosingMotiveType + * Chill is a software for social workers * - * @package Chill\PersonBundle\Form + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\Form; + +use Chill\MainBundle\Form\Type\TranslatableStringFormType; +use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; +use Chill\PersonBundle\Form\Type\ClosingMotivePickerType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Class ClosingMotiveType. */ class ClosingMotiveType extends AbstractType { - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name', TranslatableStringFormType::class, [ - 'label' => 'Nom' + 'label' => 'Nom', ]) ->add('active', CheckboxType::class, [ 'label' => 'Actif ?', - 'required' => false + 'required' => false, ]) ->add('ordering', NumberType::class, [ 'label' => 'Ordre d\'apparition', 'required' => true, - 'scale' => 5 + 'scale' => 5, ]) ->add('parent', ClosingMotivePickerType::class, [ 'label' => 'Parent', 'required' => false, 'placeholder' => 'closing_motive.any parent', 'multiple' => false, - 'only_leaf' => false - ]) - ; + 'only_leaf' => false, + ]); } - - /** - * @param OptionsResolver $resolver - */ + public function configureOptions(OptionsResolver $resolver) { $resolver - ->setDefault('class', ClosingMotive::class) - ; + ->setDefault('class', ClosingMotive::class); } } - \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php b/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php index 8fa95fe4f..3d409129b 100644 --- a/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php @@ -1,73 +1,52 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Form; use Chill\MainBundle\Form\Event\CustomizeFormEvent; -use Chill\MainBundle\Repository\CenterRepository; +use Chill\MainBundle\Form\Type\ChillDateType; +use Chill\MainBundle\Form\Type\PickCenterType; +use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Form\Type\GenderType; +use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Chill\MainBundle\Form\Type\ChillDateType; -use Chill\MainBundle\Form\Type\PickCenterType; -use Chill\PersonBundle\Form\Type\GenderType; -use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; -use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; -use Chill\PersonBundle\Form\Type\PersonAltNameType; final class CreationPersonType extends AbstractType { // TODO: This is only used in test. // TODO: See if this is still valid and update accordingly. - const NAME = 'chill_personbundle_person_creation'; + public const NAME = 'chill_personbundle_person_creation'; - private CenterRepository $centerRepository; + private bool $askCenters; - /** - * - * @var ConfigPersonAltNamesHelper - */ - protected $configPersonAltNamesHelper; + private ConfigPersonAltNamesHelper $configPersonAltNamesHelper; private EventDispatcherInterface $dispatcher; public function __construct( - CenterRepository $centerRepository, ConfigPersonAltNamesHelper $configPersonAltNamesHelper, - EventDispatcherInterface $dispatcher - ) { - $this->centerTransformer = $centerRepository; + EventDispatcherInterface $dispatcher, + ParameterBagInterface $parameterBag + ) { $this->configPersonAltNamesHelper = $configPersonAltNamesHelper; $this->dispatcher = $dispatcher; + $this->askCenters = $parameterBag->get('chill_main')['acl']['form_show_centers']; } - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -76,35 +55,35 @@ final class CreationPersonType extends AbstractType ->add('birthdate', ChillDateType::class, [ 'required' => false, ]) - ->add('gender', GenderType::class, array( - 'required' => true, 'placeholder' => null - )) - ->add('center', PickCenterType::class, [ - 'required' => false, - 'role' => PersonVoter::CREATE, - ]) - ; + ->add('gender', GenderType::class, [ + 'required' => true, 'placeholder' => null, + ]); + + if ($this->askCenters) { + $builder + ->add('center', PickCenterType::class, [ + 'required' => false, + 'role' => PersonVoter::CREATE, + ]); + } if ($this->configPersonAltNamesHelper->hasAltNames()) { - $builder->add('altNames', PersonAltNameType::class, [ - 'by_reference' => false + $builder->add('altNames', PersonAltNameType::class, [ + 'by_reference' => false, ]); } $this->dispatcher->dispatch( - new CustomizeFormEvent(static::class, $builder), + new CustomizeFormEvent(self::class, $builder), CustomizeFormEvent::NAME ); } - /** - * @param OptionsResolver $resolver - */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( - 'data_class' => Person::class - )); + $resolver->setDefaults([ + 'data_class' => Person::class, + ]); } /** diff --git a/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php b/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php index d1f50404b..13de3b9f5 100644 --- a/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php +++ b/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php @@ -1,17 +1,24 @@ getIterator() as $key => $altName) { /** @var PersonAltName $altName */ $mapIndexToKey[$altName->getKey()] = $key; } - + foreach ($forms as $key => $form) { - if (\array_key_exists($key, $mapIndexToKey)) { + if (array_key_exists($key, $mapIndexToKey)) { $form->setData($viewData->get($mapIndexToKey[$key])->getLabel()); } } } /** - * * @param FormInterface[] $forms * @param Collection $viewData */ @@ -52,16 +59,16 @@ class PersonAltNameDataMapper implements DataMapperInterface $dataIterator = $viewData instanceof ArrayCollection ? $viewData->toArray() : $viewData->getIterator(); } - + foreach ($dataIterator as $key => $altName) { /** @var PersonAltName $altName */ $mapIndexToKey[$altName->getKey()] = $key; } - + foreach ($forms as $key => $form) { $isEmpty = empty($form->getData()); - - if (\array_key_exists($key, $mapIndexToKey)) { + + if (array_key_exists($key, $mapIndexToKey)) { if ($isEmpty) { $viewData->remove($mapIndexToKey[$key]); } else { @@ -71,8 +78,7 @@ class PersonAltNameDataMapper implements DataMapperInterface if (!$isEmpty) { $altName = (new PersonAltName()) ->setKey($key) - ->setLabel($form->getData()) - ; + ->setLabel($form->getData()); if (is_array($viewData)) { $viewData[] = $altName; @@ -82,6 +88,5 @@ class PersonAltNameDataMapper implements DataMapperInterface } } } - } } diff --git a/src/Bundle/ChillPersonBundle/Form/DataTransformer/PersonToIdTransformer.php b/src/Bundle/ChillPersonBundle/Form/DataTransformer/PersonToIdTransformer.php index 2561019d5..5e7ed4043 100644 --- a/src/Bundle/ChillPersonBundle/Form/DataTransformer/PersonToIdTransformer.php +++ b/src/Bundle/ChillPersonBundle/Form/DataTransformer/PersonToIdTransformer.php @@ -1,11 +1,20 @@ om = $om; } - /** - * Transforms an object (issue) to a string (id). - * - * @param Person|null $issue - * @return string - */ - public function transform($issue) - { - if (null === $issue) { - return ""; - } - - return $issue->getId(); - } - /** * Transforms a string (id) to an object (issue). * * @param string $id * - * @return Person|null - * * @throws TransformationFailedException if object (issue) is not found. + * + * @return Person|null */ public function reverseTransform($id) { @@ -54,8 +45,7 @@ class PersonToIdTransformer implements DataTransformerInterface $issue = $this->om ->getRepository('ChillPersonBundle:Person') - ->findOneBy(array('id' => $id)) - ; + ->findOneBy(['id' => $id]); if (null === $issue) { throw new TransformationFailedException(sprintf( @@ -66,5 +56,20 @@ class PersonToIdTransformer implements DataTransformerInterface return $issue; } + + /** + * Transforms an object (issue) to a string (id). + * + * @param Person|null $issue + * + * @return string + */ + public function transform($issue) + { + if (null === $issue) { + return ''; + } + + return $issue->getId(); + } } -?> \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Form/HouseholdMemberType.php b/src/Bundle/ChillPersonBundle/Form/HouseholdMemberType.php index 4d8d5a165..e0d2d9d47 100644 --- a/src/Bundle/ChillPersonBundle/Form/HouseholdMemberType.php +++ b/src/Bundle/ChillPersonBundle/Form/HouseholdMemberType.php @@ -1,11 +1,19 @@ add('startDate', ChillDateType::class, [ 'label' => 'household.Start date', 'input' => 'datetime_immutable', - ]) - ->add('endDate', ChillDateType::class, [ - 'label' => 'household.End date', - 'input' => 'datetime_immutable', - 'required' => false - ]) - ; - if ($options['data']->getPosition()->isAllowHolder()) { - $builder - ->add('holder', ChoiceType::class, [ - 'label' => 'household.holder', - 'choices' => [ - 'household.is holder' => true, - 'household.is not holder' => false - ] - ]); - } + ]); $builder ->add('comment', ChillTextareaType::class, [ 'label' => 'household.Comment', - 'required' => false - ]) - ; - } + 'required' => false, + ]); + } } diff --git a/src/Bundle/ChillPersonBundle/Form/HouseholdType.php b/src/Bundle/ChillPersonBundle/Form/HouseholdType.php index 999e2ff79..68f00fa22 100644 --- a/src/Bundle/ChillPersonBundle/Form/HouseholdType.php +++ b/src/Bundle/ChillPersonBundle/Form/HouseholdType.php @@ -1,12 +1,21 @@ add('commentMembers', CommentType::class, [ 'label' => 'household.comment_membership', - 'required' => false + 'required' => false, ]) ->add('waitingForBirth', CheckboxType::class, [ 'required' => false, - 'label' => 'household.expecting_birth' + 'label' => 'household.expecting_birth', ]) ->add('waitingForBirthDate', ChillDateType::class, [ 'required' => false, 'label' => 'household.date_expecting_birth', - 'input' => 'datetime_immutable' - ]) - ; + 'input' => 'datetime_immutable', + ]); } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillPersonBundle/Form/MaritalStatusType.php b/src/Bundle/ChillPersonBundle/Form/MaritalStatusType.php index 1897c7dc4..fb81331c6 100644 --- a/src/Bundle/ChillPersonBundle/Form/MaritalStatusType.php +++ b/src/Bundle/ChillPersonBundle/Form/MaritalStatusType.php @@ -1,45 +1,42 @@ add('id', TextType::class, [ - 'label' => 'Identifiant' + 'label' => 'Identifiant', ]) ->add('name', TranslatableStringFormType::class, [ - 'label' => 'Nom' - ]) - ; + 'label' => 'Nom', + ]); } - - /** - * @param OptionsResolver $resolver - */ + public function configureOptions(OptionsResolver $resolver) { $resolver - ->setDefault('class', MaritalStatus::class) - ; + ->setDefault('class', MaritalStatus::class); } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillPersonBundle/Form/PersonConfimDuplicateType.php b/src/Bundle/ChillPersonBundle/Form/PersonConfimDuplicateType.php index fd7fa3e50..52ed35db6 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonConfimDuplicateType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonConfimDuplicateType.php @@ -1,5 +1,14 @@ add('confirm', CheckboxType::class, [ - 'label' => 'I confirm the merger of these 2 people', - 'mapped' => false, - ]); + ->add('confirm', CheckboxType::class, [ + 'label' => 'I confirm the merger of these 2 people', + 'mapped' => false, + ]); } /** diff --git a/src/Bundle/ChillPersonBundle/Form/PersonFindManuallyDuplicateType.php b/src/Bundle/ChillPersonBundle/Form/PersonFindManuallyDuplicateType.php index c46f2cf82..7ecbcdc9b 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonFindManuallyDuplicateType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonFindManuallyDuplicateType.php @@ -1,19 +1,23 @@ add('direction', HiddenType::class, [ 'data' => 'starting', - ]) - ; + ]); } /** diff --git a/src/Bundle/ChillPersonBundle/Form/PersonType.php b/src/Bundle/ChillPersonBundle/Form/PersonType.php index 8bc982713..47baafe2b 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonType.php @@ -1,46 +1,42 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Form; use Chill\CustomFieldsBundle\Form\Type\CustomFieldType; +use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Form\Type\ChillCollectionType; +use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; +use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\Select2CountryType; use Chill\MainBundle\Form\Type\Select2LanguageType; +use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; +use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Form\Type\GenderType; use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Form\Type\PersonPhoneType; -use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Form\Type\Select2MaritalStatusType; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; -use Chill\MainBundle\Form\Type\ChillDateType; -use Chill\MainBundle\Form\Type\CommentType; -use Symfony\Component\Form\Extension\Core\Type\EmailType; -use Symfony\Component\Form\Extension\Core\Type\TelType; -use Symfony\Component\Form\Extension\Core\Type\TextType; -use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\TelType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -54,30 +50,28 @@ class PersonType extends AbstractType * * @var string[] */ - protected $config = array(); + protected $config = []; /** - * * @var ConfigPersonAltNamesHelper */ protected $configAltNamesHelper; + protected TranslatableStringHelper $translatableStringHelper; + /** - * * @param string[] $personFieldsConfiguration configuration of visibility of some fields */ public function __construct( array $personFieldsConfiguration, - ConfigPersonAltNamesHelper $configAltNamesHelper + ConfigPersonAltNamesHelper $configAltNamesHelper, + TranslatableStringHelper $translatableStringHelper ) { $this->config = $personFieldsConfiguration; $this->configAltNamesHelper = $configAltNamesHelper; + $this->translatableStringHelper = $translatableStringHelper; } - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -89,48 +83,63 @@ class PersonType extends AbstractType ->add('deathdate', DateType::class, [ 'required' => false, 'input' => 'datetime_immutable', - 'widget' => 'single_text' + 'widget' => 'single_text', ]) - ->add('gender', GenderType::class, array( - 'required' => true - )) - ->add('genderComment', CommentType::class, array( - 'required' => false - )) - ->add('numberOfChildren', IntegerType::class, array( - 'required' => false - )); + ->add('gender', GenderType::class, [ + 'required' => true, + ]) + ->add('genderComment', CommentType::class, [ + 'required' => false, + ]) + ->add('numberOfChildren', IntegerType::class, [ + 'required' => false, + 'attr' => ['min' => 0], + ]); if ($this->configAltNamesHelper->hasAltNames()) { $builder->add('altNames', PersonAltNameType::class, [ - 'by_reference' => false + 'by_reference' => false, ]); } - if ($this->config['memo'] === 'visible') { + if ('visible' === $this->config['memo']) { $builder - ->add('memo', ChillTextareaType::class, array('required' => false)) - ; + ->add('memo', ChillTextareaType::class, ['required' => false]); } - if ($this->config['place_of_birth'] === 'visible') { - $builder->add('placeOfBirth', TextType::class, array('required' => false)); + if ('visible' === $this->config['place_of_birth']) { + $builder->add('placeOfBirth', TextType::class, [ + 'required' => false, + 'attr' => ['style' => 'text-transform: uppercase;'], + ]); + + $builder->get('placeOfBirth')->addModelTransformer(new CallbackTransformer( + static function ($string) { + return strtoupper((string) $string); + }, + static function ($string) { + return strtoupper((string) $string); + } + )); } - if ($this->config['contact_info'] === 'visible') { - $builder->add('contactInfo', ChillTextareaType::class, array('required' => false)); + if ('visible' === $this->config['contact_info']) { + $builder->add('contactInfo', ChillTextareaType::class, ['required' => false]); } - if ($this->config['phonenumber'] === 'visible') { - $builder->add('phonenumber', TelType::class, array('required' => false)); + if ('visible' === $this->config['phonenumber']) { + $builder->add('phonenumber', TelType::class, [ + 'required' => false, + // 'placeholder' => '+33623124554' //TODO placeholder for phone numbers + ]); } - if ($this->config['mobilenumber'] === 'visible') { + if ('visible' === $this->config['mobilenumber']) { $builder - ->add('mobilenumber', TelType::class, array('required' => false)) - ->add('acceptSMS', CheckboxType::class, array( - 'required' => false - )); + ->add('mobilenumber', TelType::class, ['required' => false]) + ->add('acceptSMS', CheckboxType::class, [ + 'required' => false, + ]); } $builder->add('otherPhoneNumbers', ChillCollectionType::class, [ @@ -142,55 +151,79 @@ class PersonType extends AbstractType 'allow_delete' => true, 'by_reference' => false, 'label' => false, - 'delete_empty' => function(PersonPhone $pp = null) { - return NULL === $pp || $pp->isEmpty(); + 'delete_empty' => static function (?PersonPhone $pp = null) { + return null === $pp || $pp->isEmpty(); }, - 'error_bubbling' => false + 'error_bubbling' => false, + 'empty_collection_explain' => 'No additional phone numbers', ]); - if ($this->config['email'] === 'visible') { + if ('visible' === $this->config['email']) { $builder - ->add('email', EmailType::class, array('required' => false)) - ->add('acceptEmail', CheckboxType::class, array('required' => false)); + ->add('email', EmailType::class, ['required' => false]); } - if ($this->config['country_of_birth'] === 'visible') { - $builder->add('countryOfBirth', Select2CountryType::class, array( - 'required' => false - )); + if ('visible' === $this->config['acceptEmail']) { + $builder + ->add('acceptEmail', CheckboxType::class, ['required' => false]); } - if ($this->config['nationality'] === 'visible') { - $builder->add('nationality', Select2CountryType::class, array( - 'required' => false - )); - } - - if ($this->config['spoken_languages'] === 'visible') { - $builder->add('spokenLanguages', Select2LanguageType::class, array( + if ('visible' === $this->config['country_of_birth']) { + $builder->add('countryOfBirth', Select2CountryType::class, [ 'required' => false, - 'multiple' => true - )); + ]); } - if ($this->config['marital_status'] === 'visible'){ - $builder - ->add('maritalStatus', Select2MaritalStatusType::class, array( - 'required' => false - )) - ->add('maritalStatusDate', ChillDateType::class, array( - 'required' => false - )) - ->add('maritalStatusComment', CommentType::class, array( - 'required' => false - )); + if ('visible' === $this->config['nationality']) { + $builder->add('nationality', Select2CountryType::class, [ + 'required' => false, + ]); } - if($options['cFGroup']) { + if ('visible' === $this->config['spoken_languages']) { + $builder->add('spokenLanguages', Select2LanguageType::class, [ + 'required' => false, + 'multiple' => true, + ]); + } + + if ('visible' === $this->config['civility']) { $builder - ->add('cFData', CustomFieldType::class, - array('attr' => array('class' => 'cf-fields'), 'group' => $options['cFGroup'])) - ; + ->add('civility', EntityType::class, [ + 'label' => 'Civility', + 'class' => Civility::class, + 'choice_label' => function (Civility $civility): string { + return $this->translatableStringHelper->localize($civility->getName()); + }, + 'query_builder' => static function (EntityRepository $er): QueryBuilder { + return $er->createQueryBuilder('c') + ->where('c.active = true'); + }, + 'placeholder' => 'choose civility', + 'required' => false, + ]); + } + + if ('visible' === $this->config['marital_status']) { + $builder + ->add('maritalStatus', Select2MaritalStatusType::class, [ + 'required' => false, + ]) + ->add('maritalStatusDate', ChillDateType::class, [ + 'required' => false, + ]) + ->add('maritalStatusComment', CommentType::class, [ + 'required' => false, + ]); + } + + if ($options['cFGroup']) { + $builder + ->add( + 'cFData', + CustomFieldType::class, + ['attr' => ['class' => 'cf-fields'], 'group' => $options['cFGroup']] + ); } } @@ -199,17 +232,18 @@ class PersonType extends AbstractType */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( + $resolver->setDefaults([ 'data_class' => 'Chill\PersonBundle\Entity\Person', - 'validation_groups' => array('general', 'creation'), - )); + 'validation_groups' => ['general', 'creation'], + ]); - $resolver->setRequired(array( - 'cFGroup' - )); + $resolver->setRequired([ + 'cFGroup', + ]); $resolver->setAllowedTypes( - 'cFGroup', array('null', 'Chill\CustomFieldsBundle\Entity\CustomFieldsGroup') + 'cFGroup', + ['null', 'Chill\CustomFieldsBundle\Entity\CustomFieldsGroup'] ); } diff --git a/src/Bundle/ChillPersonBundle/Form/SocialWork/EvaluationType.php b/src/Bundle/ChillPersonBundle/Form/SocialWork/EvaluationType.php index b45cd327e..0c6bb0d32 100644 --- a/src/Bundle/ChillPersonBundle/Form/SocialWork/EvaluationType.php +++ b/src/Bundle/ChillPersonBundle/Form/SocialWork/EvaluationType.php @@ -1,53 +1,38 @@ - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Form\SocialWork; +use Chill\MainBundle\Form\Type\TranslatableStringFormType; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\PersonBundle\Entity\SocialWork\Evaluation; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\MainBundle\Form\Type\TranslatableStringFormType; -use Symfony\Component\Form\Extension\Core\Type\DateType; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Chill\PersonBundle\Entity\SocialWork\Evaluation; /** - * Class EvaluationType - * - * @package Chill\PersonBundle\Form + * Class EvaluationType. */ class EvaluationType extends AbstractType { /** - * * @var TranslatableStringHelper */ protected $translatableStringHelper; - public function __construct(TranslatableStringHelper $translatableStringHelper) { + public function __construct(TranslatableStringHelper $translatableStringHelper) + { $this->translatableStringHelper = $translatableStringHelper; } - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -59,13 +44,9 @@ class EvaluationType extends AbstractType ->add('notificationDelay'); } - /** - * @param OptionsResolver $resolver - */ public function configureOptions(OptionsResolver $resolver) { $resolver - ->setDefault('class', Evaluation::class) - ; + ->setDefault('class', Evaluation::class); } } diff --git a/src/Bundle/ChillPersonBundle/Form/SocialWork/GoalType.php b/src/Bundle/ChillPersonBundle/Form/SocialWork/GoalType.php index c86a7d6b9..4ea0a5e70 100644 --- a/src/Bundle/ChillPersonBundle/Form/SocialWork/GoalType.php +++ b/src/Bundle/ChillPersonBundle/Form/SocialWork/GoalType.php @@ -1,85 +1,68 @@ - * - * 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\SocialWork; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\MainBundle\Form\Type\TranslatableStringFormType; -use Symfony\Component\Form\Extension\Core\Type\DateType; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Chill\PersonBundle\Entity\SocialWork\Goal; -use Chill\PersonBundle\Entity\SocialWork\SocialAction; /** - * Class GoalType + * Chill is a software for social workers * - * @package Chill\PersonBundle\Form + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\Form\SocialWork; + +use Chill\MainBundle\Form\Type\TranslatableStringFormType; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\PersonBundle\Entity\SocialWork\Goal; +use Chill\PersonBundle\Entity\SocialWork\SocialAction; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Class GoalType. */ class GoalType extends AbstractType { /** - * * @var TranslatableStringHelper */ protected $translatableStringHelper; - public function __construct(TranslatableStringHelper $translatableStringHelper) { + public function __construct(TranslatableStringHelper $translatableStringHelper) + { $this->translatableStringHelper = $translatableStringHelper; } - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title', TranslatableStringFormType::class, [ - 'label' => 'Nom' + 'label' => 'Nom', ]) ->add('socialActions', EntityType::class, [ 'class' => SocialAction::class, - 'required' => false, + 'required' => false, 'multiple' => true, 'choice_label' => function (SocialAction $issue) { return $this->translatableStringHelper->localize($issue->getTitle()); - } + }, ]) ->add('desactivationDate', DateType::class, [ - 'attr' => array('class' => 'datepicker'), - 'widget'=> 'single_text', + 'attr' => ['class' => 'datepicker'], + 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', 'required' => false, ]); -} + } - /** - * @param OptionsResolver $resolver - */ public function configureOptions(OptionsResolver $resolver) { $resolver - ->setDefault('class', Goal::class) - ; + ->setDefault('class', Goal::class); } } diff --git a/src/Bundle/ChillPersonBundle/Form/SocialWork/ResultType.php b/src/Bundle/ChillPersonBundle/Form/SocialWork/ResultType.php index ee2cefe48..a0219aaf3 100644 --- a/src/Bundle/ChillPersonBundle/Form/SocialWork/ResultType.php +++ b/src/Bundle/ChillPersonBundle/Form/SocialWork/ResultType.php @@ -1,76 +1,58 @@ - * - * 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\SocialWork; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\MainBundle\Form\Type\TranslatableStringFormType; -use Symfony\Component\Form\Extension\Core\Type\DateType; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Chill\PersonBundle\Entity\SocialWork\Result; /** - * Class ResultType + * Chill is a software for social workers * - * @package Chill\PersonBundle\Form + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\Form\SocialWork; + +use Chill\MainBundle\Form\Type\TranslatableStringFormType; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\PersonBundle\Entity\SocialWork\Result; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Class ResultType. */ class ResultType extends AbstractType { /** - * * @var TranslatableStringHelper */ protected $translatableStringHelper; - public function __construct(TranslatableStringHelper $translatableStringHelper) { + public function __construct(TranslatableStringHelper $translatableStringHelper) + { $this->translatableStringHelper = $translatableStringHelper; } - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title', TranslatableStringFormType::class, [ - 'label' => 'Nom' + 'label' => 'Nom', ]) ->add('accompanyingPeriodWorks') ->add('accompanyingPeriodWorkGoals') ->add('desactivationDate', DateType::class, [ - 'attr' => array('class' => 'datepicker'), - 'widget'=> 'single_text', + 'attr' => ['class' => 'datepicker'], + 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', 'required' => false, ]); -} + } - /** - * @param OptionsResolver $resolver - */ public function configureOptions(OptionsResolver $resolver) { $resolver - ->setDefault('class', Result::class) - ; + ->setDefault('class', Result::class); } } diff --git a/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialActionType.php b/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialActionType.php index 7613bc2c7..1f1ae0810 100644 --- a/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialActionType.php +++ b/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialActionType.php @@ -1,90 +1,72 @@ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -namespace Chill\PersonBundle\Form\SocialWork; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\MainBundle\Form\Type\TranslatableStringFormType; -use Symfony\Component\Form\Extension\Core\Type\DateType; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Chill\PersonBundle\Entity\SocialWork\SocialAction; -use Chill\PersonBundle\Entity\SocialWork\SocialIssue; - /** - * Class SocialActionType + * Chill is a software for social workers * - * @package Chill\PersonBundle\Form + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\Form\SocialWork; + +use Chill\MainBundle\Form\Type\TranslatableStringFormType; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\PersonBundle\Entity\SocialWork\SocialAction; +use Chill\PersonBundle\Entity\SocialWork\SocialIssue; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Class SocialActionType. */ class SocialActionType extends AbstractType { /** - * * @var TranslatableStringHelper */ protected $translatableStringHelper; - public function __construct(TranslatableStringHelper $translatableStringHelper) { + public function __construct(TranslatableStringHelper $translatableStringHelper) + { $this->translatableStringHelper = $translatableStringHelper; } - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title', TranslatableStringFormType::class, [ - 'label' => 'Nom' + 'label' => 'Nom', ]) ->add('issue', EntityType::class, [ 'class' => SocialIssue::class, 'choice_label' => function (SocialIssue $issue) { return $this->translatableStringHelper->localize($issue->getTitle()); - } + }, ]) ->add('parent', EntityType::class, [ 'class' => SocialAction::class, - 'required' => false, + 'required' => false, 'choice_label' => function (SocialAction $issue) { return $this->translatableStringHelper->localize($issue->getTitle()); - } + }, ]) ->add('defaultNotificationDelay') ->add('desactivationDate', DateType::class, [ - 'attr' => array('class' => 'datepicker'), - 'widget'=> 'single_text', + 'attr' => ['class' => 'datepicker'], + 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', 'required' => false, ]); -} + } - /** - * @param OptionsResolver $resolver - */ public function configureOptions(OptionsResolver $resolver) { $resolver - ->setDefault('class', SocialIssue::class) - ; + ->setDefault('class', SocialIssue::class); } } diff --git a/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialIssueType.php b/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialIssueType.php index 1f21defd9..0058b1e51 100644 --- a/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialIssueType.php +++ b/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialIssueType.php @@ -1,83 +1,56 @@ - * - * 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\SocialWork; - -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\MainBundle\Form\Type\TranslatableStringFormType; -use Symfony\Component\Form\Extension\Core\Type\DateType; -use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Chill\PersonBundle\Entity\SocialWork\SocialIssue; /** - * Class SocialIssueType + * Chill is a software for social workers * - * @package Chill\PersonBundle\Form + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + +namespace Chill\PersonBundle\Form\SocialWork; + +use Chill\MainBundle\Form\Type\TranslatableStringFormType; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Chill\PersonBundle\Entity\SocialWork\SocialIssue; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + class SocialIssueType extends AbstractType { - /** - * - * @var TranslatableStringHelper - */ - protected $translatableStringHelper; + protected TranslatableStringHelperInterface $translatableStringHelper; - public function __construct(TranslatableStringHelper $translatableStringHelper) { + public function __construct( + TranslatableStringHelperInterface $translatableStringHelper + ) { $this->translatableStringHelper = $translatableStringHelper; } - /** - * @param FormBuilderInterface $builder - * @param array $options - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title', TranslatableStringFormType::class, [ - 'label' => 'Nom' + 'label' => 'Nom', ]) - ->add('parent', EntityType::class, [ 'class' => SocialIssue::class, - 'required' => false, - 'choice_label' => function (SocialIssue $issue) { - return $this->translatableStringHelper->localize($issue->getTitle()); - } + 'required' => false, + 'choice_label' => fn (SocialIssue $issue): ?string => $this->translatableStringHelper->localize($issue->getTitle()), ]) - ->add('desactivationDate', DateType::class, [ - 'attr' => ['class' => 'datepicker'], - 'widget'=> 'single_text', + 'attr' => ['class' => 'datepicker'], + 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', 'required' => false, ]); -} + } - /** - * @param OptionsResolver $resolver - */ public function configureOptions(OptionsResolver $resolver) { - $resolver - ->setDefault('class', SocialIssue::class) - ; + $resolver->setDefault('class', SocialIssue::class); } } diff --git a/src/Bundle/ChillPersonBundle/Form/Type/ClosingMotivePickerType.php b/src/Bundle/ChillPersonBundle/Form/Type/ClosingMotivePickerType.php index d2f85c924..600720ddf 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/ClosingMotivePickerType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/ClosingMotivePickerType.php @@ -1,32 +1,31 @@ entityRenderExtension = $chillEntityRenderExtension; $this->repository = $closingMotiveRepository; } - + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'class' => ClosingMotive::class, + 'empty_data' => null, + 'placeholder' => 'Choose a motive', + 'choice_label' => function (ClosingMotive $cm) { + return $this->entityRenderExtension->renderString($cm); + }, + 'only_leaf' => true, + ]); + + $resolver + ->setAllowedTypes('only_leaf', 'bool') + ->setNormalizer('choices', function (Options $options) { + return $this->repository + ->getActiveClosingMotive($options['only_leaf']); + }); + } + /** * @return string */ @@ -61,38 +81,12 @@ class ClosingMotivePickerType extends AbstractType { return 'closing_motive'; } - + /** - * @return null|string + * @return string|null */ public function getParent() { return EntityType::class; } - - /** - * @param OptionsResolver $resolver - */ - public function configureOptions(OptionsResolver $resolver) - { - - $resolver->setDefaults([ - 'class' => ClosingMotive::class, - 'empty_data' => null, - 'placeholder' => 'Choose a motive', - 'choice_label' => function(ClosingMotive $cm) { - return $this->entityRenderExtension->renderString($cm); - }, - 'only_leaf' => true - ]); - - $resolver - ->setAllowedTypes('only_leaf', 'bool') - ->setNormalizer('choices', function (Options $options) { - return $this->repository - ->getActiveClosingMotive($options['only_leaf']); - }) - ; - } - } diff --git a/src/Bundle/ChillPersonBundle/Form/Type/GenderType.php b/src/Bundle/ChillPersonBundle/Form/Type/GenderType.php index bdd31e899..43edfb8d0 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/GenderType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/GenderType.php @@ -1,38 +1,45 @@ Person::MALE_GENDER, Person::FEMALE_GENDER => Person::FEMALE_GENDER, - Person::BOTH_GENDER => Person::BOTH_GENDER - ); + Person::BOTH_GENDER => Person::BOTH_GENDER, + ]; - $resolver->setDefaults(array( + $resolver->setDefaults([ 'choices' => $a, 'expanded' => true, 'multiple' => false, - 'placeholder' => null - )); + 'placeholder' => null, + ]); } + public function getParent() + { + return ChoiceType::class; + } } diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php b/src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php index 1ba58e86e..02335c397 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php @@ -1,35 +1,38 @@ configHelper = $configHelper; @@ -40,28 +43,17 @@ class PersonAltNameType extends AbstractType { foreach ($this->getKeyChoices() as $label => $key) { $builder->add( - $key, - $options['force_hidden'] ? HiddenType::class : TextType::class, [ - 'label' => $label, - 'required' => false - ]); + $key, + $options['force_hidden'] ? HiddenType::class : TextType::class, + [ + 'label' => $label, + 'required' => false, + ] + ); } - + $builder->setDataMapper(new \Chill\PersonBundle\Form\DataMapper\PersonAltNameDataMapper()); } - - protected function getKeyChoices() - { - $choices = $this->configHelper->getChoices(); - $translatedChoices = []; - - foreach ($choices as $key => $labels) { - $label = $this->translatableStringHelper->localize($labels); - $translatedChoices[$label] = $key; - } - - return $translatedChoices; - } public function configureOptions(OptionsResolver $resolver) { @@ -69,8 +61,19 @@ class PersonAltNameType extends AbstractType ->setDefault('class', \Chill\PersonBundle\Entity\PersonAltName::class) ->setDefined('force_hidden') ->setAllowedTypes('force_hidden', 'bool') - ->setDefault('force_hidden', false) - ; + ->setDefault('force_hidden', false); } + protected function getKeyChoices() + { + $choices = $this->configHelper->getChoices(); + $translatedChoices = []; + + foreach ($choices as $key => $labels) { + $label = $this->translatableStringHelper->localize($labels); + $translatedChoices[$label] = $key; + } + + return $translatedChoices; + } } diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php b/src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php index f06d5cd72..00243349f 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php @@ -1,5 +1,14 @@ phonenumberHelper = $phonenumberHelper; @@ -38,11 +45,11 @@ class PersonPhoneType extends AbstractType 'required' => false, ]); - $builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) { - if (NULL === $event->getData()) { + $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { + if (null === $event->getData()) { return; } - + $oldPersonPhone = $this->em->getUnitOfWork() ->getOriginalEntityData($event->getData()); @@ -59,7 +66,6 @@ class PersonPhoneType extends AbstractType ->setDefaults([ 'data_class' => PersonPhone::class, 'validation_groups' => ['general', 'creation'], - ]) - ; + ]); } } diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PickPersonType.php b/src/Bundle/ChillPersonBundle/Form/Type/PickPersonType.php index 839c84b69..98572b8bc 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PickPersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PickPersonType.php @@ -1,41 +1,35 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Form\Type; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\GroupCenter; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Form\ChoiceLoader\PersonChoiceLoader; +use Chill\PersonBundle\Repository\PersonRepository; +use Chill\PersonBundle\Search\PersonSearch; +use RuntimeException; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Role\Role; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Chill\MainBundle\Entity\GroupCenter; -use Chill\PersonBundle\Entity\Person; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Chill\MainBundle\Entity\Center; -use Chill\PersonBundle\Repository\PersonRepository; -use Chill\PersonBundle\Search\PersonSearch; use Symfony\Component\Translation\TranslatorInterface; -use Chill\PersonBundle\Form\ChoiceLoader\PersonChoiceLoader; -use Symfony\Component\OptionsResolver\Options; +use function in_array; +use function is_array; /** * This type allow to pick a person. @@ -49,49 +43,41 @@ use Symfony\Component\OptionsResolver\Options; * `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; - + /** - * - * @var UrlGeneratorInterface + * @var PersonRepository */ - protected $urlGenerator; - + protected $personRepository; + /** - * * @var TranslatorInterface */ protected $translator; + /** + * @var UrlGeneratorInterface + */ + protected $urlGenerator; + + /** + * @var \Chill\MainBundle\Entity\User + */ + protected $user; + public function __construct( - PersonRepository $personRepository, - TokenStorageInterface $tokenStorage, - AuthorizationHelper $authorizationHelper, - UrlGeneratorInterface $urlGenerator, - TranslatorInterface $translator - ) - { + PersonRepository $personRepository, + TokenStorageInterface $tokenStorage, + AuthorizationHelper $authorizationHelper, + UrlGeneratorInterface $urlGenerator, + TranslatorInterface $translator + ) { $this->personRepository = $personRepository; $this->user = $tokenStorage->getToken()->getUser(); $this->authorizationHelper = $authorizationHelper; @@ -99,42 +85,16 @@ class PickPersonType extends AbstractType $this->translator = $translator; } - protected function filterCentersfom(Options $options) + public function buildView(\Symfony\Component\Form\FormView $view, \Symfony\Component\Form\FormInterface $form, array $options) { - 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(); - $optionsCenters = is_array($options['centers']) ? - $options['centers'] : array($options['centers']); - - foreach ($optionsCenters 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; - } - } - - return $selectedCenters; + $view->vars['attr']['data-person-picker'] = true; + $view->vars['attr']['data-select-interactive-loading'] = true; + $view->vars['attr']['data-search-url'] = $this->urlGenerator + ->generate('chill_main_search', ['name' => PersonSearch::NAME, '_format' => 'json']); + $view->vars['attr']['data-placeholder'] = $this->translator->trans($options['placeholder']); + $view->vars['attr']['data-no-results-label'] = $this->translator->trans('select2.no_results'); + $view->vars['attr']['data-error-load-label'] = $this->translator->trans('select2.error_loading'); + $view->vars['attr']['data-searching-label'] = $this->translator->trans('select2.searching'); } public function configureOptions(OptionsResolver $resolver) @@ -143,49 +103,74 @@ class PickPersonType extends AbstractType // 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) - ; + ->addAllowedTypes('centers', ['array', Center::class, 'null']) + ->setDefault('centers', null) + ->setDefined('role') + ->addAllowedTypes('role', [Role::class, 'null']) + ->setDefault('role', null); // add the default options - $resolver->setDefaults(array( + $resolver->setDefaults([ 'class' => Person::class, - 'choice_label' => function(Person $p) { - return $p->getFirstname().' '.$p->getLastname(); + 'choice_label' => static function (Person $p) { + return $p->getFirstname() . ' ' . $p->getLastname(); }, 'placeholder' => 'Pick a person', - 'choice_attr' => function(Person $p) { - return array( - 'data-center' => $p->getCenter()->getId() - ); + 'choice_attr' => static function (Person $p) { + return [ + 'data-center' => $p->getCenter()->getId(), + ]; }, - 'attr' => array('class' => 'select2 '), - 'choice_loader' => function(Options $options) { + 'attr' => ['class' => 'select2 '], + 'choice_loader' => function (Options $options) { $centers = $this->filterCentersfom($options); - + return new PersonChoiceLoader($this->personRepository, $centers); - } - )); + }, + ]); } public function getParent() { return EntityType::class; } - - public function buildView(\Symfony\Component\Form\FormView $view, \Symfony\Component\Form\FormInterface $form, array $options) - { - $view->vars['attr']['data-person-picker'] = true; - $view->vars['attr']['data-select-interactive-loading'] = true; - $view->vars['attr']['data-search-url'] = $this->urlGenerator - ->generate('chill_main_search', [ 'name' => PersonSearch::NAME, '_format' => 'json' ]); - $view->vars['attr']['data-placeholder'] = $this->translator->trans($options['placeholder']); - $view->vars['attr']['data-no-results-label'] = $this->translator->trans('select2.no_results'); - $view->vars['attr']['data-error-load-label'] = $this->translator->trans('select2.error_loading'); - $view->vars['attr']['data-searching-label'] = $this->translator->trans('select2.searching'); - } + protected function filterCentersfom(Options $options) + { + if (null === $options['role']) { + $centers = array_map(static function (GroupCenter $g) { + return $g->getCenter(); + }, $this->user->getGroupCenters()->toArray()); + } else { + $centers = $this->authorizationHelper + ->getReachableCenters($this->user, $options['role']); + } + + if (null === $options['centers']) { + // we select all selected centers + $selectedCenters = $centers; + } else { + $selectedCenters = []; + $optionsCenters = is_array($options['centers']) ? + $options['centers'] : [$options['centers']]; + + foreach ($optionsCenters 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( + static function (Center $c) { return $c->getId(); }, + $centers + ), true)) { + throw new AccessDeniedException('The given center is not reachable'); + } + $selectedCenters[] = $c; + } + } + + return $selectedCenters; + } } diff --git a/src/Bundle/ChillPersonBundle/Form/Type/Select2MaritalStatusType.php b/src/Bundle/ChillPersonBundle/Form/Type/Select2MaritalStatusType.php index 34fd9897e..c75b93f7a 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/Select2MaritalStatusType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/Select2MaritalStatusType.php @@ -1,40 +1,29 @@ * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Form\Type; +use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer; +use Chill\MainBundle\Form\Type\Select2ChoiceType; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\MaritalStatus; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\HttpFoundation\RequestStack; -use Doctrine\Persistence\ObjectManager; -use Chill\MainBundle\Form\Type\Select2ChoiceType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use const SORT_FLAG_CASE; +use const SORT_STRING; /** - * A type to select the marital status - * - * @author Champs-Libres COOP + * A type to select the marital status. */ class Select2MaritalStatusType extends AbstractType { @@ -48,24 +37,16 @@ class Select2MaritalStatusType extends AbstractType $this->em = $em; } - public function getBlockPrefix() { - return 'select2_chill_marital_status'; - } - - public function getParent() { - return Select2ChoiceType::class; - } - public function buildForm(FormBuilderInterface $builder, array $options) { - $transformer = new ObjectToIdTransformer($this->em,'Chill\PersonBundle\Entity\MaritalStatus'); + $transformer = new ObjectToIdTransformer($this->em, 'Chill\PersonBundle\Entity\MaritalStatus'); $builder->addModelTransformer($transformer); } public function configureOptions(OptionsResolver $resolver) { $maritalStatuses = $this->em->getRepository('Chill\PersonBundle\Entity\MaritalStatus')->findAll(); - $choices = array(); + $choices = []; foreach ($maritalStatuses as $ms) { $choices[$ms->getId()] = $this->translatableStringHelper->localize($ms->getName()); @@ -73,9 +54,19 @@ class Select2MaritalStatusType extends AbstractType asort($choices, SORT_STRING | SORT_FLAG_CASE); - $resolver->setDefaults(array( - 'class' => MaritalStatus::class, - 'choices' => array_combine(array_values($choices),array_keys($choices)) - )); + $resolver->setDefaults([ + 'class' => MaritalStatus::class, + 'choices' => array_combine(array_values($choices), array_keys($choices)), + ]); + } + + public function getBlockPrefix() + { + return 'select2_chill_marital_status'; + } + + public function getParent() + { + return Select2ChoiceType::class; } } diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 2fd322bb4..2d3749fa7 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -1,37 +1,55 @@ validator = $validator; $this->household = $household; } - - public function addMovement(\DateTimeImmutable $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self + + public function addMovement(DateTimeImmutable $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self { - if (NULL === $this->household) { - throw new \LogicException("You must define a household first"); + if (null === $this->household) { + throw new LogicException('You must define a household first'); } $membership = (new HouseholdMember()) @@ -39,8 +57,7 @@ class MembersEditor ->setPerson($person) ->setPosition($position) ->setHolder($holder) - ->setComment($comment) - ; + ->setComment($comment); $this->household->addMember($membership); if ($position->getShareHousehold()) { @@ -53,9 +70,10 @@ class MembersEditor continue; } - if ($participation->getEndDate() === NULL || $participation->getEndDate() > $date) { + if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { $participation->setEndDate($date); $this->membershipsAffected[] = $participation; + $this->oldMembershipsHashes[] = spl_object_hash($participation); } } } @@ -66,8 +84,23 @@ class MembersEditor return $this; } + public function getHousehold(): ?Household + { + return $this->household; + } + + public function getPersistable(): array + { + return $this->persistables; + } + + public function hasHousehold(): bool + { + return null !== $this->household; + } + public function leaveMovement( - \DateTimeImmutable $date, + DateTimeImmutable $date, Person $person ): self { $criteria = new Criteria(); @@ -81,8 +114,7 @@ class MembersEditor ); $participations = $person->getHouseholdParticipations() - ->matching($criteria) - ; + ->matching($criteria); foreach ($participations as $participation) { $participation->setEndDate($date); @@ -92,36 +124,24 @@ class MembersEditor return $this; } - public function validate(): ConstraintViolationListInterface { if ($this->hasHousehold()) { $list = $this->validator - ->validate($this->getHousehold(), null, [ self::VALIDATION_GROUP ]); + ->validate($this->getHousehold(), null, [self::VALIDATION_GROUP_AFFECTED]); } else { $list = new ConstraintViolationList(); } foreach ($this->membershipsAffected as $m) { - $list->addAll($this->validator->validate($m, null, [ self::VALIDATION_GROUP ])); + if (in_array(spl_object_hash($m), $this->oldMembershipsHashes, true)) { + $list->addAll($this->validator->validate($m, null, [self::VALIDATION_GROUP_AFFECTED])); + } else { + $list->addAll($this->validator->validate($m, null, [self::VALIDATION_GROUP_CREATED, + self::VALIDATION_GROUP_AFFECTED, ])); + } } return $list; } - - public function getPersistable(): array - { - return $this->persistables; - } - - - public function getHousehold(): ?Household - { - return $this->household; - } - - public function hasHousehold(): bool - { - return $this->household !== null; - } } diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php index adf55277e..a26841064 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php @@ -1,12 +1,19 @@ translator = $translator; - } - - public static function getMenuIds(): array - { - return [ 'accompanyingCourse' ]; + $this->registry = $registry; } public function buildMenu($menuId, MenuItem $menu, array $parameters): void { + /** @var AccompanyingPeriod $period */ $period = $parameters['accompanyingCourse']; $menu->addChild($this->translator->trans('Resume Accompanying Course'), [ 'route' => 'chill_person_accompanying_course_index', 'routeParameters' => [ - 'accompanying_period_id' => $period->getId() - ]]) - ->setExtras(['order' => 10]); + 'accompanying_period_id' => $period->getId(), + ], ]) + ->setExtras(['order' => 10]); $menu->addChild($this->translator->trans('Edit Accompanying Course'), [ 'route' => 'chill_person_accompanying_course_edit', 'routeParameters' => [ - 'accompanying_period_id' => $period->getId() - ]]) - ->setExtras(['order' => 20]); + 'accompanying_period_id' => $period->getId(), + ], ]) + ->setExtras(['order' => 20]); if (AccompanyingPeriod::STEP_DRAFT === $period->getStep()) { // no more menu items if the period is draft @@ -57,17 +62,31 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface $menu->addChild($this->translator->trans('Accompanying Course History'), [ 'route' => 'chill_person_accompanying_course_history', 'routeParameters' => [ - 'accompanying_period_id' => $period->getId() - ]]) - ->setExtras(['order' => 30]); + 'accompanying_period_id' => $period->getId(), + ], ]) + ->setExtras(['order' => 30]); $menu->addChild($this->translator->trans('Accompanying Course Action'), [ 'route' => 'chill_person_accompanying_period_work_list', 'routeParameters' => [ - 'id' => $period->getId() - ]]) - ->setExtras(['order' => 50]); + 'id' => $period->getId(), + ], ]) + ->setExtras(['order' => 40]); + + $workflow = $this->registry->get($period, 'accompanying_period_lifecycle'); + + if ($workflow->can($period, 'close')) { + $menu->addChild($this->translator->trans('Close Accompanying Course'), [ + 'route' => 'chill_person_accompanying_course_close', + 'routeParameters' => [ + 'accompanying_period_id' => $period->getId(), + ], ]) + ->setExtras(['order' => 99999]); + } } - + public static function getMenuIds(): array + { + return ['accompanyingCourse']; + } } diff --git a/src/Bundle/ChillPersonBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/AdminMenuBuilder.php index e8a73d2ab..25996f7bf 100644 --- a/src/Bundle/ChillPersonBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/AdminMenuBuilder.php @@ -1,61 +1,48 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + 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; -/** - * - * - */ class AdminMenuBuilder 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('ROLE_ADMIN')) { return; } - + $menu->addChild('Person', [ - 'route' => 'chill_person_admin' - ]) + 'route' => 'chill_person_admin', + ]) ->setExtras([ - 'order' => 20 + 'order' => 20, ]); } public static function getMenuIds(): array { - return [ 'admin_section' ]; + return ['admin_section']; } } diff --git a/src/Bundle/ChillPersonBundle/Menu/HouseholdMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/HouseholdMenuBuilder.php index c6a76445d..feb465f8d 100644 --- a/src/Bundle/ChillPersonBundle/Menu/HouseholdMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/HouseholdMenuBuilder.php @@ -1,15 +1,22 @@ translator = $translator; } - public static function getMenuIds(): array - { - return [ 'household' ]; - } - public function buildMenu($menuId, MenuItem $menu, array $parameters): void { $household = $parameters['household']; @@ -32,32 +34,34 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface $menu->addChild($this->translator->trans('household.Household summary'), [ 'route' => 'chill_person_household_summary', 'routeParameters' => [ - 'household_id' => $household->getId() - ]]) - ->setExtras(['order' => 10]); + 'household_id' => $household->getId(), + ], ]) + ->setExtras(['order' => 10]); $menu->addChild($this->translator->trans('household.Accompanying period'), [ 'route' => 'chill_person_household_accompanying_period', 'routeParameters' => [ - 'household_id' => $household->getId() - ]]) - ->setExtras(['order' => 20]); + 'household_id' => $household->getId(), + ], ]) + ->setExtras(['order' => 20]); $menu->addChild($this->translator->trans('household.Addresses'), [ 'route' => 'chill_person_household_addresses', 'routeParameters' => [ - 'household_id' => $household->getId() - ]]) - ->setExtras(['order' => 30]); + 'household_id' => $household->getId(), + ], ]) + ->setExtras(['order' => 30]); $menu->addChild($this->translator->trans('household.Relationship'), [ 'route' => 'chill_person_household_relationship', 'routeParameters' => [ - 'household_id' => $household->getId() - ]]) + 'household_id' => $household->getId(), + ], ]) ->setExtras(['order' => 40]); - } - + public static function getMenuIds(): array + { + return ['household']; + } } diff --git a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php index 0c83d8a6a..ee359434b 100644 --- a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php @@ -1,20 +1,14 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ + +declare(strict_types=1); + namespace Chill\PersonBundle\Menu; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; @@ -31,18 +25,15 @@ use Symfony\Contracts\Translation\TranslatorInterface; * * - person details ; * - accompanying period (if `visible`) - * */ class PersonMenuBuilder implements LocalMenuBuilderInterface { /** - * * @var string 'visible' or 'hidden' */ protected $showAccompanyingPeriod; /** - * * @var TranslatorInterface */ protected $translator; @@ -62,52 +53,52 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface public function buildMenu($menuId, MenuItem $menu, array $parameters) { $menu->addChild($this->translator->trans('Person details'), [ - 'route' => 'chill_person_view', - 'routeParameters' => [ - 'person_id' => $parameters['person']->getId() - ] - ]) + 'route' => 'chill_person_view', + 'routeParameters' => [ + 'person_id' => $parameters['person']->getId(), + ], + ]) ->setExtras([ - 'order' => 50 + 'order' => 50, ]); $menu->addChild($this->translator->trans('household.person history'), [ - 'route' => 'chill_person_household_person_history', - 'routeParameters' => [ - 'person_id' => $parameters['person']->getId() - ] + 'route' => 'chill_person_household_person_history', + 'routeParameters' => [ + 'person_id' => $parameters['person']->getId(), + ], ]) - ->setExtras([ - 'order' => 99999 - ]); + ->setExtras([ + 'order' => 99999, + ]); $menu->addChild($this->translator->trans('Person duplicate'), [ - 'route' => 'chill_person_duplicate_view', - 'routeParameters' => [ - 'person_id' => $parameters['person']->getId() - ] + 'route' => 'chill_person_duplicate_view', + 'routeParameters' => [ + 'person_id' => $parameters['person']->getId(), + ], ]) - ->setExtras([ - 'order' => 99999 - ]); + ->setExtras([ + 'order' => 99999, + ]); - if ($this->showAccompanyingPeriod === 'visible' + if ('visible' === $this->showAccompanyingPeriod && $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person']) ) { $menu->addChild($this->translator->trans('Accompanying period list'), [ - 'route' => 'chill_person_accompanying_period_list', - 'routeParameters' => [ - 'person_id' => $parameters['person']->getId() - ] - ]) + 'route' => 'chill_person_accompanying_period_list', + 'routeParameters' => [ + 'person_id' => $parameters['person']->getId(), + ], + ]) ->setExtras([ - 'order' => 100 + 'order' => 100, ]); } } public static function getMenuIds(): array { - return [ 'person' ]; + return ['person']; } } diff --git a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php index 0b307204d..eff2e7a3a 100644 --- a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php @@ -1,35 +1,24 @@ +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Menu; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Chill\PersonBundle\Security\Authorization\PersonVoter; use Knp\Menu\MenuItem; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -use Chill\PersonBundle\Security\Authorization\PersonVoter; use Symfony\Component\Translation\TranslatorInterface; /** - * Class SectionMenuBuilder - * - * @package Chill\PersonBundle\Menu - * @author Julien Fastré + * Class SectionMenuBuilder. */ class SectionMenuBuilder implements LocalMenuBuilderInterface { @@ -37,55 +26,47 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface * @var AuthorizationCheckerInterface */ protected $authorizationChecker; - + /** * @var TranslatorInterface */ protected $translator; - + /** * SectionMenuBuilder constructor. - * - * @param AuthorizationCheckerInterface $authorizationChecker - * @param TranslatorInterface $translator */ public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator) { $this->authorizationChecker = $authorizationChecker; $this->translator = $translator; } - + /** * @param $menuId - * @param MenuItem $menu - * @param array $parameters */ public function buildMenu($menuId, MenuItem $menu, array $parameters) { if ($this->authorizationChecker->isGranted(PersonVoter::CREATE)) { $menu->addChild($this->translator->trans('Add a person'), [ - 'route' => 'chill_person_new' - ]) + 'route' => 'chill_person_new', + ]) ->setExtras([ 'order' => 10, - 'icons' => [ 'plus' ] + 'icons' => ['plus'], ]); } $menu->addChild($this->translator->trans('Create an accompanying course'), [ - 'route' => 'chill_person_accompanying_course_new' - ]) + 'route' => 'chill_person_accompanying_course_new', + ]) ->setExtras([ 'order' => 11, - 'icons' => [ 'plus' ] + 'icons' => ['plus'], ]); } - - /** - * @return array - */ + public static function getMenuIds(): array { - return [ 'section' ]; + return ['section']; } } diff --git a/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodNotificationRenderer.php b/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodNotificationRenderer.php index ca76fbd5c..c67c3d8a8 100644 --- a/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodNotificationRenderer.php +++ b/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodNotificationRenderer.php @@ -1,5 +1,14 @@ getRelatedEntityClass() == AccompanyingPeriod::class; - } - public function getTemplate() { return 'ChillPersonBundle:AccompanyingPeriod:showInNotification.html.twig'; @@ -21,4 +25,9 @@ final class AccompanyingPeriodNotificationRenderer { return ['notification' => $notification]; } + + public function supports(Notification $notification) + { + return $notification->getRelatedEntityClass() === AccompanyingPeriod::class; + } } diff --git a/src/Bundle/ChillPersonBundle/Privacy/AccompanyingPeriodPrivacyEvent.php b/src/Bundle/ChillPersonBundle/Privacy/AccompanyingPeriodPrivacyEvent.php index e8610b4c1..7f8da3671 100644 --- a/src/Bundle/ChillPersonBundle/Privacy/AccompanyingPeriodPrivacyEvent.php +++ b/src/Bundle/ChillPersonBundle/Privacy/AccompanyingPeriodPrivacyEvent.php @@ -1,5 +1,14 @@ . */ -use Symfony\Component\EventDispatcher\Event; -use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Symfony\Component\EventDispatcher\Event; class AccompanyingPeriodPrivacyEvent extends Event { public const ACCOMPANYING_PERIOD_PRIVACY_EVENT = 'chill_person.accompanying_period_privacy_event'; - protected AccompanyingPeriod $period; - protected array $args; + protected AccompanyingPeriod $period; + public function __construct($period, $args = []) { $this->period = $period; $this->args = $args; } - public function getPeriod(): AccompanyingPeriod - { - return $this->period; - } - public function getArgs(): array { return $this->args; } + + public function getPeriod(): AccompanyingPeriod + { + return $this->period; + } } diff --git a/src/Bundle/ChillPersonBundle/Privacy/PrivacyEvent.php b/src/Bundle/ChillPersonBundle/Privacy/PrivacyEvent.php index 65304f96a..0bfd7a435 100644 --- a/src/Bundle/ChillPersonBundle/Privacy/PrivacyEvent.php +++ b/src/Bundle/ChillPersonBundle/Privacy/PrivacyEvent.php @@ -1,5 +1,14 @@ . */ -use Symfony\Component\EventDispatcher\Event; use Chill\PersonBundle\Entity\Person; +use Symfony\Component\EventDispatcher\Event; +use function count; /** - * Class PrivacyEvent + * Class PrivacyEvent. * * Array $args expects arguments with the following keys: 'element_class', 'element_id', 'action' * By default, action is set to 'show' - * - * @package Chill\PersonBundle\Privacy */ class PrivacyEvent extends Event { - const PERSON_PRIVACY_EVENT = 'chill_person.privacy_event'; - - /** - * @var Person - */ - private $person; - + public const PERSON_PRIVACY_EVENT = 'chill_person.privacy_event'; + /** * @var array */ private $args; - + + /** + * @var Person + */ + private $person; + /** * @var array */ private $persons; - + /** * PrivacyEvent constructor. - * - * @param Person $person - * @param array $args */ - public function __construct(Person $person, array $args = array('action' => 'show')) + public function __construct(Person $person, array $args = ['action' => 'show']) { $this->person = $person; $this->args = $args; - $this->persons = array(); + $this->persons = []; } - - /** - * @return Person - */ - public function getPerson() - { - return $this->person; - } - - /** - * @param Person $person - */ + public function addPerson(Person $person) { $this->persons[] = $person; - + return $this; } - - /** - * @return array $persons - */ - public function getPersons() - { - return $this->persons; - } - - /** - * @return bool - */ - public function hasPersons() - { - return count($this->persons) >= 1; - } - + /** * @return array */ @@ -106,5 +84,28 @@ class PrivacyEvent extends Event { return $this->args; } - -} \ No newline at end of file + + /** + * @return Person + */ + public function getPerson() + { + return $this->person; + } + + /** + * @return array $persons + */ + public function getPersons() + { + return $this->persons; + } + + /** + * @return bool + */ + public function hasPersons() + { + return count($this->persons) >= 1; + } +} diff --git a/src/Bundle/ChillPersonBundle/Privacy/PrivacyEventSubscriber.php b/src/Bundle/ChillPersonBundle/Privacy/PrivacyEventSubscriber.php index c37fc67b2..5018743dc 100644 --- a/src/Bundle/ChillPersonBundle/Privacy/PrivacyEventSubscriber.php +++ b/src/Bundle/ChillPersonBundle/Privacy/PrivacyEventSubscriber.php @@ -1,5 +1,14 @@ . */ +use Chill\PersonBundle\Entity\Person; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent; +use function array_map; class PrivacyEventSubscriber implements EventSubscriberInterface { - /** * @var LoggerInterface */ @@ -43,8 +51,6 @@ class PrivacyEventSubscriber implements EventSubscriberInterface /** * PrivacyEventSubscriber constructor. - * - * @param LoggerInterface $logger */ public function __construct(LoggerInterface $logger, TokenStorageInterface $token) { @@ -56,11 +62,11 @@ class PrivacyEventSubscriber implements EventSubscriberInterface { return [ PrivacyEvent::PERSON_PRIVACY_EVENT => [ - ['onPrivacyEvent'] + ['onPrivacyEvent'], ], AccompanyingPeriodPrivacyEvent::ACCOMPANYING_PERIOD_PRIVACY_EVENT => [ - ['onAccompanyingPeriodPrivacyEvent'] - ] + ['onAccompanyingPeriodPrivacyEvent'], + ], ]; } @@ -69,11 +75,11 @@ class PrivacyEventSubscriber implements EventSubscriberInterface $involved = $this->getInvolved(); $involved['period_id'] = $event->getPeriod()->getId(); $involved['persons'] = $event->getPeriod()->getPersons() - ->map(function(Person $p) { return $p->getId(); }) + ->map(static function (Person $p) { return $p->getId(); }) ->toArray(); $this->logger->notice( - "[Privacy Event] An accompanying period has been viewed", + '[Privacy Event] An accompanying period has been viewed', array_merge($involved, $event->getArgs()) ); } @@ -92,14 +98,14 @@ class PrivacyEventSubscriber implements EventSubscriberInterface $involved['person_id'] = $event->getPerson()->getId(); if ($event->hasPersons()) { - $involved['persons'] = \array_map( - function(Person $p) { return $p->getId(); }, + $involved['persons'] = array_map( + static function (Person $p) { return $p->getId(); }, $event->getPersons() ); } $this->logger->notice( - "[Privacy Event] A Person Folder has been viewed", + '[Privacy Event] A Person Folder has been viewed', array_merge($involved, $event->getArgs()) ); } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php index d9fa5b6e3..1f432e4e0 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkGoalRepository.php @@ -1,5 +1,14 @@ repository = $entityManager->getRepository(AccompanyingPeriodWork::class); } + public function countByAccompanyingPeriod(AccompanyingPeriod $period): int + { + return $this->repository->countByAccompanyingPeriod($period); + } + + public function countBySocialActionWithDescendants(SocialAction $action): int + { + $qb = $this->buildQueryBySocialActionWithDescendants($action); + $qb->select('COUNT(g)'); + + return $qb + ->getQuery() + ->getSingleScalarResult(); + } + public function find($id): ?AccompanyingPeriodWork { return $this->repository->find($id); @@ -32,17 +56,10 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function findOneBy(array $criteria): ?AccompanyingPeriodWork - { - return $this->findOneBy($criteria); - } - - public function getClassName() - { - return AccompanyingPeriodWork::class; - } - /** + * @param mixed|null $orderBy + * @param mixed|null $limit + * @param mixed|null $offset * * @return AccompanyingPeriodWork[] */ @@ -51,9 +68,14 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository return $this->repository->findByAccompanyingPeriod($period, $orderBy, $limit, $offset); } - public function countByAccompanyingPeriod(AccompanyingPeriod $period): int + public function findOneBy(array $criteria): ?AccompanyingPeriodWork { - return $this->repository->countByAccompanyingPeriod($period); + return $this->repository->findOneBy($criteria); + } + + public function getClassName() + { + return AccompanyingPeriodWork::class; } public function toDelete() @@ -62,29 +84,17 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository $qb->select('g'); foreach ($orderBy as $sort => $order) { - $qb->addOrderBy('g.'.$sort, $order); + $qb->addOrderBy('g.' . $sort, $order); } return $qb ->setMaxResults($limit) ->setFirstResult($offset) ->getQuery() - ->getResult() - ; + ->getResult(); } - public function countBySocialActionWithDescendants(SocialAction $action): int - { - $qb = $this->buildQueryBySocialActionWithDescendants($action); - $qb->select('COUNT(g)'); - - return $qb - ->getQuery() - ->getSingleScalarResult() - ; - } - - protected function buildQueryBySocialActionWithDescendants(SocialAction $action): QueryBuilder + private function buildQueryBySocialActionWithDescendants(SocialAction $action): QueryBuilder { $actions = $action->getDescendantsWithThis(); @@ -92,6 +102,7 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository $orx = $qb->expr()->orX(); $i = 0; + foreach ($actions as $action) { $orx->add(":action_{$i} MEMBER OF g.socialActions"); $qb->setParameter("action_{$i}", $action); diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php index 5c1525b52..ea5b1feff 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php @@ -1,25 +1,14 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; @@ -29,10 +18,10 @@ use Doctrine\ORM\Query\ResultSetMappingBuilder; final class ClosingMotiveRepository { - private EntityRepository $repository; - private EntityManagerInterface $entityManager; + private EntityRepository $repository; + public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; @@ -47,18 +36,18 @@ final class ClosingMotiveRepository $rsm = new ResultSetMappingBuilder($this->entityManager); $rsm->addRootEntityFromClassMetadata($this->repository->getClassName(), 'cm'); - $sql = "SELECT " . (string) $rsm . " + $sql = 'SELECT ' . (string) $rsm . ' FROM chill_person_accompanying_period_closingmotive AS cm WHERE - active IS TRUE "; + active IS TRUE '; if ($onlyLeaf) { - $sql .= "AND cm.id NOT IN ( + $sql .= 'AND cm.id NOT IN ( SELECT DISTINCT parent_id FROM chill_person_accompanying_period_closingmotive WHERE parent_id IS NOT NULL - )"; + )'; } - $sql .= " ORDER BY cm.ordering ASC"; + $sql .= ' ORDER BY cm.ordering ASC'; return $this ->entityManager diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php index b3a20780e..0da6fc662 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php @@ -1,25 +1,14 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php index 596bfc2ee..5d7c10f7e 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php @@ -1,25 +1,14 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php index 3923126aa..8eee0e9e4 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php @@ -1,30 +1,19 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; -use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; final class ResourceRepository { diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php index c8cca82f5..965231e3f 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php @@ -1,22 +1,39 @@ accompanyingPeriodRepository = $accompanyingPeriodRepository; $this->security = $security; $this->authorizationHelper = $authorizationHelper; @@ -27,13 +44,16 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC Person $person, string $role, ?array $orderBy = [], - int $limit = null, - int $offset = null + ?int $limit = null, + ?int $offset = null ): array { $qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap'); $scopes = $this->authorizationHelper - ->getReachableCircles($this->security->getUser(), $role, - $this->centerResolverDispatcher->resolveCenter($person)); + ->getReachableCircles( + $this->security->getUser(), + $role, + $this->centerResolverDispatcher->resolveCenter($person) + ); if (0 === count($scopes)) { return []; @@ -57,20 +77,18 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC ->setParameter('draft', AccompanyingPeriod::STEP_DRAFT) ->setParameter('person', $person) ->setParameter('user', $this->security->getUser()) - ->setParameter('creator', $this->security->getUser()) - ; + ->setParameter('creator', $this->security->getUser()); // add join condition for scopes $orx = $qb->expr()->orX( $qb->expr()->eq('ap.step', ':draft') ); foreach ($scopes as $key => $scope) { - $orx->add($qb->expr()->isMemberOf(':scope_'.$key, 'ap.scopes')); - $qb->setParameter('scope_'.$key, $scope); + $orx->add($qb->expr()->isMemberOf(':scope_' . $key, 'ap.scopes')); + $qb->setParameter('scope_' . $key, $scope); } $qb->andWhere($orx); return $qb->getQuery()->getResult(); } - } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php index a5798d3e8..778e8d8fe 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php @@ -1,5 +1,14 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index c9e8397a8..b21431554 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -1,25 +1,14 @@ , - * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ +declare(strict_types=1); + namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\AccompanyingPeriod; @@ -36,6 +25,12 @@ final class AccompanyingPeriodRepository implements ObjectRepository { $this->repository = $entityManager->getRepository(AccompanyingPeriod::class); } + + public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder + { + return $this->repository->createQueryBuilder($alias, $indexBy); + } + public function find($id): ?AccompanyingPeriod { return $this->repository->find($id); @@ -59,11 +54,6 @@ final class AccompanyingPeriodRepository implements ObjectRepository return $this->findOneBy($criteria); } - public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder - { - return $this->repository->createQueryBuilder($alias, $indexBy); - } - public function getClassName() { return AccompanyingPeriod::class; diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepository.php index 38236df14..0649dae1c 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepository.php @@ -1,22 +1,31 @@ security = $security; } + public function addACL(QueryBuilder $qb, string $alias = 'h'): QueryBuilder + { + $centers = $this->authorizationHelper->getReachableCenters( + $this->security->getUser(), + HouseholdVoter::SHOW + ); + + if ([] === $centers) { + return $qb + ->andWhere("'FALSE' = 'TRUE'"); + } + + $qb + ->join($alias . '.members', 'members') + ->join('members.person', 'person') + ->andWhere( + $qb->expr()->in('person.center', ':centers') + ) + ->setParameter('centers', $centers); + + return $qb; + } + + public function buildQueryByAddressReference(AddressReference $addressReference): QueryBuilder + { + $qb = $this->em->createQueryBuilder(); + $qb + ->select('h') + ->from(Household::class, 'h') + ->join('h.addresses', 'address') + ->where( + $qb->expr()->eq('address.addressReference', ':reference') + ) + ->setParameter(':reference', $addressReference) + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte('address.validFrom', ':today'), + $qb->expr()->orX( + $qb->expr()->isNull('address.validTo'), + $qb->expr()->gt('address.validTo', ':today') + ) + ) + ) + ->setParameter('today', new DateTime('today')); + + return $qb; + } + public function countByAddressReference(AddressReference $addressReference): int { $qb = $this->buildQueryByAddressReference($addressReference); @@ -51,53 +108,4 @@ final class HouseholdACLAwareRepository implements HouseholdACLAwareRepositoryIn ->getQuery() ->getResult(); } - - public function buildQueryByAddressReference(AddressReference $addressReference): QueryBuilder - { - $qb = $this->em->createQueryBuilder(); - $qb - ->select('h') - ->from(Household::class, 'h') - ->join('h.addresses', 'address') - ->where( - $qb->expr()->eq('address.addressReference', ':reference') - ) - ->setParameter(':reference', $addressReference) - ->andWhere( - $qb->expr()->andX( - $qb->expr()->lte('address.validFrom', ':today'), - $qb->expr()->orX( - $qb->expr()->isNull('address.validTo'), - $qb->expr()->gt('address.validTo', ':today') - ) - ) - ) - ->setParameter('today', new \DateTime('today')) - ; - - return $qb; - } - - public function addACL(QueryBuilder $qb, string $alias = 'h'): QueryBuilder - { - $centers = $this->authorizationHelper->getReachableCenters( - $this->security->getUser(), - HouseholdVoter::SHOW - ); - - if ([] === $centers) { - return $qb - ->andWhere("'FALSE' = 'TRUE'"); - } - - $qb - ->join($alias.'.members', 'members') - ->join('members.person', 'person') - ->andWhere( - $qb->expr()->in('person.center', ':centers') - ) - ->setParameter('centers', $centers); - - return $qb; - } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepositoryInterface.php index 56a927ae5..e13d8b888 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdACLAwareRepositoryInterface.php @@ -1,5 +1,14 @@ repository = $entityManager->getRepository(HouseholdMembers::class); + $this->repository = $entityManager->getRepository(HouseholdMember::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php index d9ec6c35e..8becf4409 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php @@ -1,5 +1,14 @@ repository = $entityManager->getRepository(Household::class); $this->em = $entityManager; } + public function countByAccompanyingPeriodParticipation(Person $person) + { + return $this->buildQueryByAccompanyingPeriodParticipation($person, true); + } + public function find($id) { return $this->repository->find($id); @@ -35,6 +71,11 @@ final class HouseholdRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } + public function findByAccompanyingPeriodParticipation(Person $person, int $limit, int $offset) + { + return $this->buildQueryByAccompanyingPeriodParticipation($person, false, $limit, $offset); + } + public function findOneBy(array $criteria) { return $this->findOneBy($criteria); @@ -45,16 +86,6 @@ final class HouseholdRepository implements ObjectRepository return Household::class; } - public function countByAccompanyingPeriodParticipation(Person $person) - { - return $this->buildQueryByAccompanyingPeriodParticipation($person, true); - } - - public function findByAccompanyingPeriodParticipation(Person $person, int $limit, int $offset) - { - return $this->buildQueryByAccompanyingPeriodParticipation($person, false, $limit, $offset); - } - private function buildQueryByAccompanyingPeriodParticipation(Person $person, bool $isCount = false, int $limit = 50, int $offset = 0) { $rsm = new ResultSetMappingBuilder($this->em); @@ -62,14 +93,14 @@ final class HouseholdRepository implements ObjectRepository if ($isCount) { $rsm->addScalarResult('count', 'count'); - $sql = \strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [ + $sql = strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [ '{select}' => 'COUNT(households.*) AS count', - '{limits}' => '' + '{limits}' => '', ]); } else { - $sql = \strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [ + $sql = strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [ '{select}' => $rsm->generateSelectClause(['h' => 'households']), - '{limits}' => "OFFSET {$offset} LIMIT {$limit}" + '{limits}' => "OFFSET {$offset} LIMIT {$limit}", ]); } $native = $this->em->createNativeQuery($sql, $rsm); @@ -77,28 +108,8 @@ final class HouseholdRepository implements ObjectRepository if ($isCount) { return $native->getSingleScalarResult(); - } else { - return $native->getResult(); } - } - private CONST SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION = <<getResult(); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/PersonHouseholdAddressRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/PersonHouseholdAddressRepository.php index d0676d172..2cd370260 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/PersonHouseholdAddressRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/PersonHouseholdAddressRepository.php @@ -1,5 +1,14 @@ repository->find($id, $lockMode, $lockVersion); } - public function findOneBy(array $criteria, array $orderBy = null): ?PersonHouseholdAddress - { - return $this->repository->findOneBy($criteria, $orderBy); - } - /** * @return PersonHouseholdAddress[] */ @@ -35,14 +39,23 @@ final class PersonHouseholdAddressRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return PersonHouseholdAddress[] */ - public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function getClassName() { + public function findOneBy(array $criteria, ?array $orderBy = null): ?PersonHouseholdAddress + { + return $this->repository->findOneBy($criteria, $orderBy); + } + + public function getClassName() + { return PersonHouseholdAddress::class; } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php index 58a0995d6..2ceab9b9e 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php @@ -1,5 +1,14 @@ repository = $entityManager->getRepository(Position::class); } + /** + * @param mixed $id + * + * @return Position + */ + public function find($id) + { + return $this->repository->find($id); + } + /** * @return Position[] */ @@ -26,6 +45,17 @@ final class PositionRepository implements ObjectRepository return $this->repository->findAll(); } + /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return Position[] + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + /** * @return Position[] */ @@ -38,14 +68,6 @@ final class PositionRepository implements ObjectRepository ->getResult(); } - /** - * @return Position[] - */ - public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array - { - return $this->repository->findBy($criteria, $orderBy, $limit, $offset); - } - /** * @return Position[] */ @@ -54,14 +76,6 @@ final class PositionRepository implements ObjectRepository return $this->repository->findOneBy($criteria); } - /** - * @return Position - */ - public function find($id) - { - return $this->repository->find($id); - } - public function getClassName() { return Position::class; diff --git a/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepository.php b/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepository.php index 1a69d1319..58b4aa7fa 100644 --- a/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepository.php @@ -1,12 +1,20 @@ repository = $entityManager->getRepository(MaritalStatus::class); } + public function find($id): ?MaritalStatus { return $this->repository->find($id); diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php index cd4f8c9ce..972264282 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php @@ -1,29 +1,45 @@ authorizationHelper = $authorizationHelper; } + public function buildAuthorizedQuery( + ?string $default = null, + ?string $firstname = null, + ?string $lastname = null, + ?DateTimeInterface $birthdate = null, + ?DateTimeInterface $birthdateBefore = null, + ?DateTimeInterface $birthdateAfter = null, + ?string $gender = null, + ?string $countryCode = null, + ?string $phonenumber = null, + ?string $city = null + ): SearchApiQuery { + $query = $this->createSearchQuery( + $default, + $firstname, + $lastname, + $birthdate, + $birthdateBefore, + $birthdateAfter, + $gender, + $countryCode, + $phonenumber + ); + + return $this->addAuthorizations($query); + } + + public function countBySearchCriteria( + ?string $default = null, + ?string $firstname = null, + ?string $lastname = null, + ?DateTimeInterface $birthdate = null, + ?DateTimeInterface $birthdateBefore = null, + ?DateTimeInterface $birthdateAfter = null, + ?string $gender = null, + ?string $countryCode = null, + ?string $phonenumber = null, + ?string $city = null + ): int { + $query = $this->buildAuthorizedQuery( + $default, + $firstname, + $lastname, + $birthdate, + $birthdateBefore, + $birthdateAfter, + $gender, + $countryCode, + $phonenumber, + $city + ); + + return $this->fetchQueryCount($query); + } + /** - * @return array|Person[] + * Create a search query without ACL. + * * @throws NonUniqueResultException * @throws ParsingException */ + public function createSearchQuery( + ?string $default = null, + ?string $firstname = null, + ?string $lastname = null, + ?DateTimeInterface $birthdate = null, + ?DateTimeInterface $birthdateBefore = null, + ?DateTimeInterface $birthdateAfter = null, + ?string $gender = null, + ?string $countryCode = null, + ?string $phonenumber = null, + ?string $city = null + ): SearchApiQuery { + $query = new SearchApiQuery(); + $query + ->setFromClause('chill_person_person AS person'); + + $pertinence = []; + $pertinenceArgs = []; + $orWhereSearchClause = []; + $orWhereSearchClauseArgs = []; + + if ('' !== $default) { + foreach (explode(' ', $default) as $str) { + $pertinence[] = + 'STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical) + ' . + "(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int + " . + "(EXISTS (SELECT 1 FROM unnest(string_to_array(fullnamecanonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int + " . + '(starts_with(LOWER(UNACCENT(lastname)), UNACCENT(LOWER(?))))::int'; + array_push($pertinenceArgs, $str, $str, $str, $str); + + $orWhereSearchClause[] = + '(LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ' . + "person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%' )"; + array_push($orWhereSearchClauseArgs, $str, $str); + } + + $query->andWhereClause( + implode(' OR ', $orWhereSearchClause), + $orWhereSearchClauseArgs + ); + } else { + $pertinence = ['1']; + $pertinenceArgs = []; + } + $query + ->setSelectPertinence(implode(' + ', $pertinence), $pertinenceArgs); + + if (null !== $birthdate) { + $query->andWhereClause( + 'person.birthdate = ?::date', + [$birthdate->format('Y-m-d')] + ); + } + + if (null !== $firstname) { + $query->andWhereClause( + "UNACCENT(LOWER(person.firstname)) LIKE '%' || UNACCENT(LOWER(?)) || '%'", + [$firstname] + ); + } + + if (null !== $lastname) { + $query->andWhereClause( + "UNACCENT(LOWER(person.lastname)) LIKE '%' || UNACCENT(LOWER(?)) || '%'", + [$lastname] + ); + } + + if (null !== $birthdateBefore) { + $query->andWhereClause( + 'person.birthdate <= ?::date', + [$birthdateBefore->format('Y-m-d')] + ); + } + + if (null !== $birthdateAfter) { + $query->andWhereClause( + 'person.birthdate >= ?::date', + [$birthdateAfter->format('Y-m-d')] + ); + } + + if (null !== $phonenumber) { + $query->andWhereClause( + "person.phonenumber LIKE '%' || ? || '%' OR person.mobilenumber LIKE '%' || ? || '%' OR pp.phonenumber LIKE '%' || ? || '%'", + [$phonenumber, $phonenumber, $phonenumber] + ); + $query->setFromClause($query->getFromClause() . ' LEFT JOIN chill_person_phone pp ON pp.person_id = person.id'); + } + + if (null !== $city) { + $query->setFromClause($query->getFromClause() . ' ' . + 'JOIN view_chill_person_current_address vcpca ON vcpca.person_id = person.id ' . + 'JOIN chill_main_address cma ON vcpca.address_id = cma.id ' . + 'JOIN chill_main_postal_code cmpc ON cma.postcode_id = cmpc.id'); + + foreach (explode(' ', $city) as $cityStr) { + $query->andWhereClause( + "(UNACCENT(LOWER(cmpc.label)) LIKE '%' || UNACCENT(LOWER(?)) || '%' OR cmpc.code LIKE '%' || UNACCENT(LOWER(?)) || '%')", + [$cityStr, $city] + ); + } + } + + if (null !== $countryCode) { + $query->setFromClause($query->getFromClause() . ' JOIN country ON person.nationality_id = country.id'); + $query->andWhereClause('country.countrycode = UPPER(?)', [$countryCode]); + } + + if (null !== $gender) { + $query->andWhereClause('person.gender = ?', [$gender]); + } + + return $query; + } + + public function fetchQueryCount(SearchApiQuery $query): int + { + $rsm = new Query\ResultSetMapping(); + $rsm->addScalarResult('c', 'c'); + + $nql = $this->em->createNativeQuery($query->buildQuery(true), $rsm); + $nql->setParameters($query->buildParameters(true)); + + return $nql->getSingleScalarResult(); + } + + /** + * @return array|Person[] + */ + public function fetchQueryPerson(SearchApiQuery $query, ?int $start = 0, ?int $limit = 50): array + { + $rsm = new Query\ResultSetMappingBuilder($this->em); + $rsm->addRootEntityFromClassMetadata(Person::class, 'person'); + + $query->addSelectClause($rsm->generateSelectClause()); + + $nql = $this->em->createNativeQuery( + $query->buildQuery() . ' ORDER BY pertinence DESC OFFSET ? LIMIT ?', + $rsm + )->setParameters(array_merge($query->buildParameters(), [$start, $limit])); + + return $nql->getResult(); + } + + /** + * @throws NonUniqueResultException + * @throws ParsingException + * + * @return array|Person[] + */ public function findBySearchCriteria( int $start, int $limit, bool $simplify = false, - string $default = null, - string $firstname = null, - string $lastname = null, - ?\DateTime $birthdate = null, - ?\DateTime $birthdateBefore = null, - ?\DateTime $birthdateAfter = null, - string $gender = null, - string $countryCode = null + ?string $default = null, + ?string $firstname = null, + ?string $lastname = null, + ?DateTimeInterface $birthdate = null, + ?DateTimeInterface $birthdateBefore = null, + ?DateTimeInterface $birthdateAfter = null, + ?string $gender = null, + ?string $countryCode = null, + ?string $phonenumber = null, + ?string $city = null ): array { - $qb = $this->createSearchQuery($default, $firstname, $lastname, - $birthdate, $birthdateBefore, $birthdateAfter, $gender, - $countryCode); - $this->addACLClauses($qb, 'p'); - - return $this->getQueryResult($qb, 'p', $simplify, $limit, $start); - } - - /** - * Helper method to prepare and return the search query for PersonACL. - * - * This method replace the select clause with required parameters, depending on the - * "simplify" parameter. It also add query limits. - * - * The given alias must represent the person alias. - * - * @return array|Person[] - */ - public function getQueryResult(QueryBuilder $qb, string $alias, bool $simplify, int $limit, int $start): array - { - if ($simplify) { - $qb->select( - $alias.'.id', - $qb->expr()->concat( - $alias.'.firstName', - $qb->expr()->literal(' '), - $alias.'.lastName' - ).'AS text' - ); - } else { - $qb->select($alias); - } - - $qb - ->setMaxResults($limit) - ->setFirstResult($start); - - //order by firstname, lastname - $qb - ->orderBy($alias.'.firstName') - ->addOrderBy($alias.'.lastName'); - - if ($simplify) { - return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY); - } else { - return $qb->getQuery()->getResult(); - } - } - - public function countBySearchCriteria( - string $default = null, - string $firstname = null, - string $lastname = null, - ?\DateTime $birthdate = null, - ?\DateTime $birthdateBefore = null, - ?\DateTime $birthdateAfter = null, - string $gender = null, - string $countryCode = null - ): int { - $qb = $this->createSearchQuery($default, $firstname, $lastname, - $birthdate, $birthdateBefore, $birthdateAfter, $gender, - $countryCode); - $this->addACLClauses($qb, 'p'); - - return $this->getCountQueryResult($qb,'p'); - } - - /** - * Helper method to prepare and return the count for search query - * - * This method replace the select clause with required parameters, depending on the - * "simplify" parameter. - * - * The given alias must represent the person alias in the query builder. - */ - public function getCountQueryResult(QueryBuilder $qb, $alias): int - { - $qb->select('COUNT('.$alias.'.id)'); - - return $qb->getQuery()->getSingleScalarResult(); - } - - public function findBySimilaritySearch(string $pattern, int $firstResult, - int $maxResult, bool $simplify = false) - { - $qb = $this->createSimilarityQuery($pattern); - $this->addACLClauses($qb, 'sp'); - - return $this->getQueryResult($qb, 'sp', $simplify, $maxResult, $firstResult); - } - - public function countBySimilaritySearch(string $pattern) - { - $qb = $this->createSimilarityQuery($pattern); - $this->addACLClauses($qb, 'sp'); - - return $this->getCountQueryResult($qb, 'sp'); - } - - /** - * Create a search query without ACL - * - * The person alias is a "p" - * - * @param string|null $default - * @param string|null $firstname - * @param string|null $lastname - * @param \DateTime|null $birthdate - * @param \DateTime|null $birthdateBefore - * @param \DateTime|null $birthdateAfter - * @param string|null $gender - * @param string|null $countryCode - * @return QueryBuilder - * @throws NonUniqueResultException - * @throws ParsingException - */ - public function createSearchQuery( - string $default = null, - string $firstname = null, - string $lastname = null, - ?\DateTime $birthdate = null, - ?\DateTime $birthdateBefore = null, - ?\DateTime $birthdateAfter = null, - string $gender = null, - string $countryCode = null - ): QueryBuilder { - - if (!$this->security->getUser() instanceof User) { - throw new \RuntimeException("Search must be performed by a valid user"); - } - $qb = $this->em->createQueryBuilder(); - $qb->from(Person::class, 'p'); - - if (NULL !== $firstname) { - $qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.firstName))', ':firstname')) - ->setParameter('firstname', '%'.$firstname.'%'); - } - - if (NULL !== $lastname) { - $qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.lastName))', ':lastname')) - ->setParameter('lastname', '%'.$lastname.'%'); - } - - if (NULL !== $birthdate) { - $qb->andWhere($qb->expr()->eq('p.birthdate', ':birthdate')) - ->setParameter('birthdate', $birthdate); - } - - if (NULL !== $birthdateAfter) { - $qb->andWhere($qb->expr()->gt('p.birthdate', ':birthdateafter')) - ->setParameter('birthdateafter', $birthdateAfter); - } - - if (NULL !== $birthdateBefore) { - $qb->andWhere($qb->expr()->lt('p.birthdate', ':birthdatebefore')) - ->setParameter('birthdatebefore', $birthdateBefore); - } - - if (NULL !== $gender) { - $qb->andWhere($qb->expr()->eq('p.gender', ':gender')) - ->setParameter('gender', $gender); - } - - if (NULL !== $countryCode) { - try { - $country = $this->countryRepository->findOneBy(['countryCode' => $countryCode]); - } catch (NoResultException $ex) { - throw new ParsingException('The country code "'.$countryCode.'" ' - . ', used in nationality, is unknow', 0, $ex); - } catch (NonUniqueResultException $e) { - throw $e; - } - - $qb->andWhere($qb->expr()->eq('p.nationality', ':nationality')) - ->setParameter('nationality', $country); - } - - if (NULL !== $default) { - $grams = explode(' ', $default); - - foreach($grams as $key => $gram) { - $qb->andWhere($qb->expr() - ->like('p.fullnameCanonical', 'UNACCENT(LOWER(:default_'.$key.'))')) - ->setParameter('default_'.$key, '%'.$gram.'%'); - } - } - - return $qb; - } - - private function addACLClauses(QueryBuilder $qb, string $personAlias): void - { - // restrict center for security - $reachableCenters = $this->authorizationHelper - ->getReachableCenters($this->security->getUser(), 'CHILL_PERSON_SEE'); - $qb->andWhere( - $qb->expr()->orX( - $qb->expr() - ->in($personAlias.'.center', ':centers'), - $qb->expr() - ->isNull($personAlias.'.center') - ) + $query = $this->buildAuthorizedQuery( + $default, + $firstname, + $lastname, + $birthdate, + $birthdateBefore, + $birthdateAfter, + $gender, + $countryCode, + $phonenumber, + $city ); - $qb->setParameter('centers', $reachableCenters); + + return $this->fetchQueryPerson($query); } - /** - * Create a query for searching by similarity. - * - * The person alias is "sp". - * - * @param $pattern - * @return QueryBuilder - */ - public function createSimilarityQuery($pattern): QueryBuilder + private function addAuthorizations(SearchApiQuery $query): SearchApiQuery { - $qb = $this->em->createQueryBuilder(); + $authorizedCenters = $this->authorizationHelper + ->getReachableCenters($this->security->getUser(), PersonVoter::SEE); - $qb->from(Person::class, 'sp'); - - $grams = explode(' ', $pattern); - - foreach($grams as $key => $gram) { - $qb->andWhere('STRICT_WORD_SIMILARITY_OPS(:default_'.$key.', sp.fullnameCanonical) = TRUE') - ->setParameter('default_'.$key, '%'.$gram.'%'); - - // remove the perfect matches - $qb->andWhere($qb->expr() - ->notLike('sp.fullnameCanonical', 'UNACCENT(LOWER(:not_default_'.$key.'))')) - ->setParameter('not_default_'.$key, '%'.$gram.'%'); + if ([] === $authorizedCenters) { + return $query->andWhereClause('FALSE = TRUE', []); } - return $qb; + return $query + ->andWhereClause( + strtr( + 'person.center_id IN ({{ center_ids }})', + [ + '{{ center_ids }}' => implode( + ', ', + array_fill(0, count($authorizedCenters), '?') + ), + ] + ), + array_map(static function (Center $c) {return $c->getId(); }, $authorizedCenters) + ); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php index 8e83da03d..72d4682c8 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php @@ -1,50 +1,64 @@ repository->createQueryBuilder('pnd'); $qb->select('pnd') - ->where('pnd.person1 = :person OR pnd.person2 = :person') - ; + ->where('pnd.person1 = :person OR pnd.person2 = :person'); $qb->setParameter('person', $person); $result = $qb->getQuery()->getResult(); diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php index 0235ccc13..57cea2d55 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php @@ -1,31 +1,25 @@ + +/** + * Chill is a software for social workers * - * 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 . + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ -namespace Chill\PersonBundle\Repository; +declare(strict_types=1); +namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; -use UnexpectedValueException; - +use Exception; +use function count; +use function in_array; +use function str_replace; final class PersonRepository implements ObjectRepository { @@ -36,65 +30,9 @@ final class PersonRepository implements ObjectRepository $this->repository = $entityManager->getRepository(Person::class); } - public function find($id, $lockMode = null, $lockVersion = null): ?Person - { - return $this->repository->find($id, $lockMode, $lockVersion); - } - - public function findByIds($ids): array - { - return $this->repository->findBy(['id' => $ids]); - } - - public function findAll() - { - return $this->repository->findAll(); - } - - public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) - { - return $this->repository->findBy($criteria, $orderBy, $limit, $offset); - } - - public function findOneBy(array $criteria) - { - return $this->repository->findOneBy($criteria); - } - - public function getClassName() - { - return Person::class; - } - - /** - * @param $centers - * @param $firstResult - * @param $maxResults - * @return mixed - * @throws \Exception - */ - public function findByPhone( - string $phonenumber, - $centers, - $firstResult, - $maxResults, - array $only = ['mobile', 'phone'] - ) { - $qb = $this->repository->createQueryBuilder('p'); - $qb->select('p'); - - $this->addByCenters($qb, $centers); - $this->addPhoneNumber($qb, $phonenumber, $only); - - $qb->setFirstResult($firstResult) - ->setMaxResults($maxResults) - ; - - return $qb->getQuery()->getResult(); - } - /** * @param $centers + * * @throws \Doctrine\ORM\NoResultException * @throws \Doctrine\ORM\NonUniqueResultException */ @@ -112,50 +50,103 @@ final class PersonRepository implements ObjectRepository return $qb->getQuery()->getSingleScalarResult(); } - /** - * @throws \Exception - */ - protected function addPhoneNumber(QueryBuilder $qb, string $phonenumber, array $only): void + public function find($id, $lockMode = null, $lockVersion = null): ?Person { - if (count($only) === 0) { - throw new \Exception("No array field to search"); - } - - $phonenumber = $this->parsePhoneNumber($phonenumber); - - $orX = $qb->expr()->orX(); - - if (\in_array('mobile', $only)) { - $orX->add($qb->expr()->like("REPLACE(p.mobilenumber, ' ', '')", ':phonenumber')); - } - if (\in_array('phone', $only)) { - $orX->add($qb->expr()->like("REPLACE(p.phonenumber, ' ', '')", ':phonenumber')); - } - - $qb->andWhere($orX); - - $qb->setParameter('phonenumber', '%'.$phonenumber.'%'); + return $this->repository->find($id, $lockMode, $lockVersion); } - - /** - * @param $phonenumber - * @return string - */ - protected function parsePhoneNumber(string $phonenumber): string + public function findAll() { - return \str_replace(' ', '', $phonenumber); + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findByIds($ids): array + { + return $this->repository->findBy(['id' => $ids]); } /** - * @param QueryBuilder $qb - * @param array $centers + * @param $centers + * @param $firstResult + * @param $maxResults + * + * @throws Exception + * + * @return mixed */ - protected function addByCenters(QueryBuilder $qb, array $centers): void + public function findByPhone( + string $phonenumber, + $centers, + $firstResult, + $maxResults, + array $only = ['mobile', 'phone'] + ) { + $qb = $this->repository->createQueryBuilder('p'); + $qb->select('p'); + + $this->addByCenters($qb, $centers); + $this->addPhoneNumber($qb, $phonenumber, $only); + + $qb->setFirstResult($firstResult) + ->setMaxResults($maxResults); + + return $qb->getQuery()->getResult(); + } + + public function findOneBy(array $criteria) + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName() + { + return Person::class; + } + + private function addByCenters(QueryBuilder $qb, array $centers): void { if (count($centers) > 0) { $qb->andWhere($qb->expr()->in('p.center', ':centers')); $qb->setParameter('centers', $centers); } } + + /** + * @throws Exception + */ + private function addPhoneNumber(QueryBuilder $qb, string $phonenumber, array $only): void + { + if (count($only) === 0) { + throw new Exception('No array field to search'); + } + + $phonenumber = $this->parsePhoneNumber($phonenumber); + + $orX = $qb->expr()->orX(); + + if (in_array('mobile', $only, true)) { + $orX->add($qb->expr()->like("REPLACE(p.mobilenumber, ' ', '')", ':phonenumber')); + } + + if (in_array('phone', $only, true)) { + $orX->add($qb->expr()->like("REPLACE(p.phonenumber, ' ', '')", ':phonenumber')); + } + + $qb->andWhere($orX); + + $qb->setParameter('phonenumber', '%' . $phonenumber . '%'); + } + + /** + * @param $phonenumber + */ + private function parsePhoneNumber(string $phonenumber): string + { + return str_replace(' ', '', $phonenumber); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationRepository.php b/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationRepository.php new file mode 100644 index 000000000..ddf82ca8d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationRepository.php @@ -0,0 +1,52 @@ +repository = $entityManager->getRepository(Relation::class); + } + + public function find($id): ?Relation + { + return $this->repository->find($id); + } + + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?Relation + { + return $this->findOneBy($criteria); + } + + public function getClassName(): string + { + return Relation::class; + } +} diff --git a/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationshipRepository.php b/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationshipRepository.php new file mode 100644 index 000000000..9549a4564 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationshipRepository.php @@ -0,0 +1,92 @@ +repository = $em->getRepository(Relationship::class); + $this->em = $em; + } + + public function countByPerson(Person $person): int + { + return $this->buildQueryByPerson($person) + ->select('COUNT(p)') + ->getQuery() + ->getSingleScalarResult(); + } + + public function find($id): ?Relationship + { + return $this->repository->find($id); + } + + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + /** + * @return array|Relationship[] + */ + public function findByPerson(Person $person): array + { + return $this->buildQueryByPerson($person) + ->select('r') + ->getQuery() + ->getResult(); + } + + public function findOneBy(array $criteria): ?Relationship + { + return $this->findOneBy($criteria); + } + + public function getClassName(): string + { + return Relationship::class; + } + + private function buildQueryByPerson(Person $person): QueryBuilder + { + $qb = $this->em->createQueryBuilder(); + $qb + ->from(Relationship::class, 'r') + ->where( + $qb->expr()->orX( + $qb->expr()->eq('r.fromPerson', ':person'), + $qb->expr()->eq('r.toPerson', ':person') + ) + ) + ->setParameter('person', $person); + + return $qb; + } +} diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php index d0f11c2d3..cb651b9ae 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php @@ -1,5 +1,14 @@ */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array @@ -49,6 +61,4 @@ final class EvaluationRepository implements ObjectRepository { return Evaluation::class; } - - } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php index 3c96c837c..ec74b1919 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php @@ -1,5 +1,14 @@ repository = $entityManager->getRepository(Goal::class); } + public function countBySocialActionWithDescendants(SocialAction $action): int + { + $qb = $this->buildQueryBySocialActionWithDescendants($action); + $qb->select('COUNT(g)'); + + return $qb + ->getQuery() + ->getSingleScalarResult(); + } + public function find($id, ?int $lockMode = null, ?int $lockVersion = null): ?Goal { return $this->repository->find($id, $lockMode, $lockVersion); @@ -32,6 +51,9 @@ final class GoalRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * * @return array */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array @@ -40,6 +62,9 @@ final class GoalRepository implements ObjectRepository } /** + * @param mixed|null $orderBy + * @param mixed|null $limit + * @param mixed|null $offset * * @return Goal[] */ @@ -49,44 +74,16 @@ final class GoalRepository implements ObjectRepository $qb->select('g'); foreach ($orderBy as $sort => $order) { - $qb->addOrderBy('g.'.$sort, $order); + $qb->addOrderBy('g.' . $sort, $order); } return $qb ->setMaxResults($limit) ->setFirstResult($offset) ->getQuery() - ->getResult() - ; + ->getResult(); } - public function countBySocialActionWithDescendants(SocialAction $action): int - { - $qb = $this->buildQueryBySocialActionWithDescendants($action); - $qb->select('COUNT(g)'); - - return $qb - ->getQuery() - ->getSingleScalarResult() - ; - } - - protected function buildQueryBySocialActionWithDescendants(SocialAction $action): QueryBuilder - { - $actions = $action->getDescendantsWithThis(); - - $qb = $this->repository->createQueryBuilder('g'); - - $orx = $qb->expr()->orX(); - $i = 0; - foreach ($actions as $action) { - $orx->add(":action_{$i} MEMBER OF g.socialActions"); - $qb->setParameter("action_{$i}", $action); - } - $qb->where($orx); - - return $qb; - } public function findOneBy(array $criteria, ?array $orderBy = null): ?Goal { return $this->repository->findOneBy($criteria, $orderBy); @@ -99,4 +96,22 @@ final class GoalRepository implements ObjectRepository { return Goal::class; } + + private function buildQueryBySocialActionWithDescendants(SocialAction $action): QueryBuilder + { + $actions = $action->getDescendantsWithThis(); + + $qb = $this->repository->createQueryBuilder('g'); + + $orx = $qb->expr()->orX(); + $i = 0; + + foreach ($actions as $action) { + $orx->add(":action_{$i} MEMBER OF g.socialActions"); + $qb->setParameter("action_{$i}", $action); + } + $qb->where($orx); + + return $qb; + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php index 33bd8f7ef..744c42577 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php @@ -1,10 +1,19 @@ repository = $entityManager->getRepository(Result::class); } + public function countByGoal(Goal $goal): int + { + $qb = $this->buildQueryByGoal($goal); + $qb->select('COUNT(r)'); + + return $qb + ->getQuery() + ->getSingleScalarResult(); + } + + public function countBySocialActionWithDescendants(SocialAction $action): int + { + $qb = $this->buildQueryBySocialActionWithDescendants($action); + $qb->select('COUNT(r)'); + + return $qb + ->getQuery() + ->getSingleScalarResult(); + } + public function find($id, ?int $lockMode = null, ?int $lockVersion = null): ?Result { return $this->repository->find($id, $lockMode, $lockVersion); @@ -33,6 +62,43 @@ final class ResultRepository implements ObjectRepository } /** + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return array + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + /** + * @param mixed|null $orderBy + * @param mixed|null $limit + * @param mixed|null $offset + * + * @return Result[] + */ + public function findByGoal(Goal $goal, $orderBy = null, $limit = null, $offset = null): array + { + $qb = $this->buildQueryByGoal($goal); + + foreach ($orderBy as $sort => $order) { + $qb->addOrderBy('r.' . $sort, $order); + } + + return $qb + ->select('r') + ->setMaxResults($limit) + ->setFirstResult($offset) + ->getQuery() + ->getResult(); + } + + /** + * @param mixed|null $orderBy + * @param mixed|null $limit + * @param mixed|null $offset * * @return Result[] */ @@ -42,92 +108,14 @@ final class ResultRepository implements ObjectRepository $qb->select('r'); foreach ($orderBy as $sort => $order) { - $qb->addOrderBy('r.'.$sort, $order); + $qb->addOrderBy('r.' . $sort, $order); } return $qb ->setMaxResults($limit) ->setFirstResult($offset) ->getQuery() - ->getResult() - ; - } - - public function countBySocialActionWithDescendants(SocialAction $action): int - { - $qb = $this->buildQueryBySocialActionWithDescendants($action); - $qb->select('COUNT(r)'); - - return $qb - ->getQuery() - ->getSingleScalarResult() - ; - } - - protected function buildQueryBySocialActionWithDescendants(SocialAction $action): QueryBuilder - { - $actions = $action->getDescendantsWithThis(); - - $qb = $this->repository->createQueryBuilder('r'); - - $orx = $qb->expr()->orX(); - $i = 0; - foreach ($actions as $action) { - $orx->add(":action_{$i} MEMBER OF r.socialActions"); - $qb->setParameter("action_{$i}", $action); - } - $qb->where($orx); - - return $qb; - } - protected function buildQueryByGoal(Goal $goal): QueryBuilder - { - $qb = $this->repository->createQueryBuilder('r'); - - $qb->where(":goal MEMBER OF r.goals") - ->setParameter('goal', $goal) - ; - - return $qb; - } - - /** - * @return Result[] - */ - public function findByGoal(Goal $goal, $orderBy = null, $limit = null, $offset = null): array - { - $qb = $this->buildQueryByGoal($goal); - - foreach ($orderBy as $sort => $order) { - $qb->addOrderBy('r.'.$sort, $order); - } - - return $qb - ->select('r') - ->setMaxResults($limit) - ->setFirstResult($offset) - ->getQuery() - ->getResult() - ; - } - - public function countByGoal(Goal $goal): int - { - $qb = $this->buildQueryByGoal($goal); - $qb->select('COUNT(r)'); - - return $qb - ->getQuery() - ->getSingleScalarResult() - ; - } - - /** - * @return array - */ - public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array - { - return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + ->getResult(); } public function findOneBy(array $criteria, ?array $orderBy = null): ?Result @@ -142,4 +130,32 @@ final class ResultRepository implements ObjectRepository { return Result::class; } + + private function buildQueryByGoal(Goal $goal): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('r'); + + $qb->where(':goal MEMBER OF r.goals') + ->setParameter('goal', $goal); + + return $qb; + } + + private function buildQueryBySocialActionWithDescendants(SocialAction $action): QueryBuilder + { + $actions = $action->getDescendantsWithThis(); + + $qb = $this->repository->createQueryBuilder('r'); + + $orx = $qb->expr()->orX(); + $i = 0; + + foreach ($actions as $action) { + $orx->add(":action_{$i} MEMBER OF r.socialActions"); + $qb->setParameter("action_{$i}", $action); + } + $qb->where($orx); + + return $qb; + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php index a81789c0a..fdf9d3b3f 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php @@ -1,5 +1,14 @@ */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php index 51ced6976..7c5983fd3 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php @@ -1,5 +1,12 @@ */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss index 2e5821659..f39f64eb2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss @@ -95,7 +95,6 @@ div.person-view { * Header custom for Accompanying Course */ - div.banner { div#header-accompanying_course-name { background: none repeat scroll 0 0 $chill-accourse-context; @@ -117,6 +116,37 @@ div.banner { color: $white; padding-top: 1em; padding-bottom: 1em; + + /// AccompanyingCourse: HeaderSlider Carousel + button.carousel-control-prev, + button.carousel-control-next { + width: 8%; + opacity: inherit; + } + button.carousel-control-prev { + left: unset; + right: 0; + } + span.to-social-issues, + span.to-persons-associated { + display: inline-block; + border-radius: 15px; + width: 24px; + height: 24px; + box-shadow: 0 0 3px 1px grey; + opacity: 0.8; + &:hover { + opacity: 1; + } + } + span.to-social-issues { + background-color: #4bafe8; + border-left: 12px solid #32749a; + } + span.to-persons-associated { + background-color: #16d9b4; + border-right: 12px solid #ffffff; + } } } @@ -203,42 +233,24 @@ div.household-resume { } } -/// Horizontal list of persons (Accourse resume page) -div.accompanyingcourse-resume { - div.associated-persons { - font-size: 110%; - span.household { - display: inline-block; - border-radius: 8px; - border: 1px solid $white; - &:hover { - border: 1px solid $chill-beige; - i { - display: inline-block; - } - } - &.no-household:hover { - border: 1px solid $white; - } - i { - color: $chill-beige; - display: none; - } - padding: 0.3em; - margin-right: 2px; - } - } -} +/* +* GENERIC PERSON STYLES +* miscellaneous +*/ -/// abbr.referrer { // still used ? font-size: 70%; padding-right: 0.4em; align-self: center; // in flex context } -.updatedBy { +.updatedBy, .createdBy { margin-top: 0.3rem; font-size: 0.9rem; font-style: italic; } + +.created-updated { + border: 1px solid black; + padding: 10px; +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss index cc56fcbda..daaae9e28 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss @@ -1,51 +1,6 @@ /// AccompanyingCourse Work list Page div.accompanying_course_work-list { - div.timeline { - width: 100%; - ul { - display: flex; - align-items: center; - justify-content: center; - - padding: 0; - list-style-type: none; - - > li { - flex-grow: 1; flex-shrink: 1; flex-basis: auto; - - div { - display: flex; - flex-direction: column; - align-items: center; - - &.date { - margin-bottom: 1em; - } - &.label { - border-top: 3px solid $chill-green; - - &:before { - content: ''; - display: inline-block; - position: relative; - width: 15px; - height: 15px; - top: -9px; - - background-color: $white; - border-radius: 12px; - border: 2px solid $chill-green; - } - &.no-label:before { - display: none; - } - } - } - } - } - } - div.objective_results { width: 100%; display: grid; @@ -69,8 +24,10 @@ div.accompanying_course_work-list { //&:nth-child(even) { background-color: $chill-llight-gray; } &.without-objectives {} &.with-objectives {} + } - + div.objective_results, + div.evaluations { h4.title_label { display: block; margin: 0.4em 0; @@ -87,26 +44,12 @@ div.accompanying_course_work-list { &.result_list { padding-left: 1em; margin-bottom: 0; - li { - padding-left: 0.3em; - &::marker { - /* - content: '→'; - font-weight: bold; - font-size: 120%; - */ - font-family: ForkAwesome; - content: '\f04b'; - font-size: 75%; - transform: rotate(45deg); - } - } } - &.goal_title li::marker { - color: $social-issue-color; + &.goal_title { + @include list_marker_triangle($social-issue-color); } - &.result_list li::marker { - color: $pink; + &.result_list { + @include list_marker_triangle($pink); } } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss index 7e9f13149..0330900da 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss @@ -1,7 +1,8 @@ /* -* BADGES PERSON AND THIRDPARTY +* BADGES USER, PERSON AND THIRDPARTY */ +span.badge-user, span.badge-person, span.badge-thirdparty { display: inline-block; @@ -16,6 +17,10 @@ span.badge-thirdparty { text-decoration: none; } } + +span.badge-user { + border-bottom-width: 1px; +} span.badge-person { border-bottom-color: $chill-green; } @@ -39,24 +44,48 @@ span.fa-holder { } /* -* BADGE_TITLE -* Display Title like a badge (with background-colored label) +* DASHBOARDS */ +/// Theses links apply on dashboards as parent tag. +/// They don't look like button, picto or simple text links +a.dashboard-link { + color: unset; + text-decoration: unset; + & > div.dashboard { + &:hover { + //box-shadow: 0 0 7px 0 $chill-gray; + //opacity: 0.8; + } + span.title_action { + &:hover { + background-color: $chill-ll-gray; + } + } + } +} + +div.dashboard { + font-weight: 700; + font-size: 1.5rem; + margin-bottom: 0.5rem; + line-height: 1.2; + span.like-h3 { + color: #334d5c; + } +} +div.dashboard, h2.badge-title { display: flex; flex-direction: row; width: 100%; color: $dark; - - a & { text-decoration: none; } // ?!? keep it ? - span.title_label { - border-radius: 0.35rem 0 0 0.35rem; color: $white; font-size: 80%; padding: 0.5em; padding-right: 0; + border-radius: 0.35rem 0 0 0.35rem; h3 { margin-bottom: 0.5rem; } @@ -64,33 +93,52 @@ h2.badge-title { span.title_action { flex-grow: 1; margin: 0 0 0 auto; - border-radius: 0 0.35rem 0.35rem 0; - background-color: $light; + background-color: $chill-llight-gray; padding: 0.2em 1em; + border-radius: 0 0.35rem 0.35rem 0; ul.small_in_title { - margin-top: 0.5em; font-size: 70%; - padding-left: 1rem; } } } -/// badge_title in AccompanyingCourse Work list Page +ul.small_in_title { + margin: 0; + //margin-top: 0.5em; + padding-left: 1rem; + &.evaluations { + @include list_marker_triangle($orange); + } +} +ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2 + @include media-breakpoint-only(sm) { + columns: 2; -webkit-columns: 2; -moz-columns: 2; + } + @include media-breakpoint-up(lg) { + columns: 2; -webkit-columns: 2; -moz-columns: 2; + } +} + + + +/// dashboard_like_badge in AccompanyingCourse Work list Page div.accompanying_course_work-list { + div.dashboard, h2.badge-title { span.title_label { // Calculate same color then border:groove background-color: shade-color($social-action-color, 34%); } span.title_action { - @include badge_title($social-action-color); + @include dashboard_like_badge($social-action-color); } } } -/// badge_title in Activities on resume page -div.activity-list { +/// dashboard_like_badge in Activities on resume page +div[class*='activity-'] { + div.dashboard, h2.badge-title { span.title_label { // Calculate same color then border:groove @@ -100,7 +148,7 @@ div.activity-list { } } span.title_action { - @include badge_title($activity-color); + @include dashboard_like_badge($activity-color); } span.title_label { div.duration { @@ -117,3 +165,20 @@ div.activity-list { } } } + +/* +* Pill badge by entity +*/ +.badge { + &.bg-person { + //@extend .$chill-ll-gray; + background-color: $chill-ll-gray; + color: $chill-green; + } + &.bg-user { + //@extend .$chill-ll-gray; + background-color: $chill-ll-gray; + color: $chill-blue; + } +} + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss index 0229f53b1..2383e43b1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss @@ -18,12 +18,6 @@ div.accompanyingcourse-list { //&:nth-child(2) { flex-direction: row; } //&:last-child { flex-direction: column; } } - div.title h3 { - font-weight: 700; - font-size: 100%; - font-family: 'Open Sans'; - } - div.list {} } /// Search Page (list_with_period.html.twig) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss index e995f97eb..228f10178 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss @@ -4,7 +4,7 @@ /// @mixin chill_badge($color) { - text-transform: capitalize !important; + //text-transform: capitalize !important; font-weight: 500 !important; border-left: 20px groove $color; &:before { @@ -27,13 +27,29 @@ } /// -/// Generic mixin for titles like badge -// define visual badge used in title area +/// Mixin for dashboards (with design like badge_social) /// -@mixin badge_title($color) { +@mixin dashboard_like_badge($color) { @include chill_badge($color); &:before { margin: 0 0.3em 0 -1.05em; } } + +/// +/// Make list with items marker like a colored triangle +/// + +@mixin list_marker_triangle($color) { + & > li { + padding-left: 0.3em; + &::marker { + font-family: ForkAwesome; + content: '\f04b'; + font-size: 75%; + transform: rotate(45deg); + color: $color; + } + } +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/lib/household.js b/src/Bundle/ChillPersonBundle/Resources/public/lib/household.js index 52754fa97..c96694dcb 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/lib/household.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/lib/household.js @@ -1,4 +1,4 @@ -import { fetchResults } from 'ChillMainAssets/lib/api/download.js'; +import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods.js'; const fetchHouseholdByAddressReference = async (reference) => { const url = `/api/1.0/person/household/by-address-reference/${reference.id}.json` diff --git a/src/Bundle/ChillPersonBundle/Resources/public/page/accompanying_course_index/masonry.js b/src/Bundle/ChillPersonBundle/Resources/public/page/accompanying_course_index/masonry.js new file mode 100644 index 000000000..e2dadd2a9 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/page/accompanying_course_index/masonry.js @@ -0,0 +1,6 @@ +import Masonry from 'masonry-layout/masonry'; + +let elem = document.querySelector('#dashboards'); +let msnry = new Masonry( elem, { + // options +}); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/page/person/index.js b/src/Bundle/ChillPersonBundle/Resources/public/page/person/index.js index d641e3b36..28ae3380a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/page/person/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/page/person/index.js @@ -7,7 +7,6 @@ const personAcceptEmail = document.getElementById("personAcceptEmail"); const personPhoneNumber = document.getElementById("personPhoneNumber"); const personAcceptSMS = document.getElementById("personAcceptSMS"); - new ShowHide({ froms: [maritalStatus], container: [maritalStatusDate], @@ -24,21 +23,24 @@ new ShowHide({ event_name: 'change' }); -new ShowHide({ - froms: [personEmail], - container: [personAcceptEmail], - test: function(froms) { - for (let f of froms.values()) { - for (let input of f.querySelectorAll('input').values()) { - if (input.value) { - return true +if (personAcceptEmail) { + new ShowHide({ + froms: [personEmail], + container: [personAcceptEmail], + test: function(froms) { + for (let f of froms.values()) { + for (let input of f.querySelectorAll('input').values()) { + if (input.value) { + return true + } } } - } - return false; - }, - event_name: 'input' -}); + return false; + }, + event_name: 'input' + }); +} + new ShowHide({ froms: [personPhoneNumber], diff --git a/src/Bundle/ChillPersonBundle/Resources/public/page/vis/index.js b/src/Bundle/ChillPersonBundle/Resources/public/page/vis/index.js deleted file mode 100644 index f11e930c0..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/page/vis/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import vis from 'vis-network/dist/vis-network.min'; - -require('./scss/vis.scss'); - -// create an array with nodes -let nodes = new vis.DataSet([ - { id: 1, label: "Node 1" }, - { id: 2, label: "Node 2" }, - { id: 3, label: "Node 3" }, - { id: 4, label: "Node 4" }, - { id: 5, label: "Node 5", cid: 1 }, -]); - -// create an array with edges -let edges = new vis.DataSet([ - { from: 1, to: 3 }, - { from: 1, to: 2 }, - { from: 2, to: 4 }, - { from: 2, to: 5 }, - { from: 3, to: 3 }, -]); - -// create a network -let container = document.getElementById("graph-relationship"); -let data = { - nodes: nodes, - edges: edges, -}; -let options = {}; - -// -let network = new vis.Network(container, data, options); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/page/vis/scss/vis.scss b/src/Bundle/ChillPersonBundle/Resources/public/page/vis/scss/vis.scss deleted file mode 100644 index 3d29c47ce..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/page/vis/scss/vis.scss +++ /dev/null @@ -1,5 +0,0 @@ -div#graph-relationship { - margin: 2em auto; - height: 500px; - border: 1px solid lightgray; -} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue index 9c65137bd..30a8bbf2a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue @@ -16,16 +16,15 @@ -
    + - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js index 52f3f6c36..8e4134205 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js @@ -1,4 +1,4 @@ -import { fetchResults } from 'ChillMainAssets/lib/api/download.js'; +import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods.js'; /* * Endpoint v.2 chill_api_single_accompanying_course__entity @@ -15,44 +15,17 @@ const getAccompanyingCourse = (id) => { }); }; -/* -* Endpoint v.2 chill_api_single_accompanying_course__entity -* method PATCH, patch AccompanyingCourse Instance -* -* @id integer - id of accompanyingCourse -* @body Object - dictionary with changes to post -*/ -const patchAccompanyingCourse = (id, body) => { - //console.log('body', body); - const url = `/api/1.0/person/accompanying-course/${id}.json`; - return fetch(url, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify(body) - }) - .then(response => { - if (response.ok) { return response.json(); } - console.log(response); - throw { msg: 'Error while updating AccompanyingPeriod Course.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); +const getUsers = () => { + const url = `/api/1.0/main/user.json`; + + return fetchResults(url); }; -/* -* Endpoint to change 'DRAFT' step to 'CONFIRMED' -*/ -const confirmAccompanyingCourse = (id) => { - const url = `/api/1.0/person/accompanying-course/${id}/confirm.json` - return fetch(url, { - method: 'POST', - headers: {'Content-Type': 'application/json;charset=utf-8'} - }) - .then(response => { - if (response.ok) { return response.json(); } - throw { msg: 'Error while confirming AccompanyingPeriod Course.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); -}; +const getReferrersSuggested = (course) => { + const url = `/api/1.0/person/accompanying-course/${course.id}/referrers-suggested.json`; + + return fetchResults(url); +} /* * Endpoint @@ -66,114 +39,6 @@ const getSocialIssues = () => { }); }; -/* -* Endpoint v.2 chill_api_single_accompanying_course_participation, -* method POST/DELETE, add/close a participation to the accompanyingCourse -* -* @id integer - id of accompanyingCourse -* @payload integer - id of person -* @method string - POST or DELETE -*/ -const postParticipation = (id, payload, method) => { - const body = { type: payload.type, id: payload.id }; - const url = `/api/1.0/person/accompanying-course/${id}/participation.json`; - return fetch(url, { - method: method, - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify(body) - }) - .then(response => { - if (response.ok) { return response.json(); } - throw { msg: 'Error while sending AccompanyingPeriod Course participation.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); -}; - -/* -* Endpoint v.2 chill_api_single_accompanying_course_requestor, -* method POST/DELETE, add/close a requestor to the accompanyingCourse -* -* @id integer - id of accompanyingCourse -* @payload object of type person|thirdparty -* @method string - POST or DELETE -*/ -const postRequestor = (id, payload, method) => { - //console.log('payload', payload); - const body = (payload)? { type: payload.type, id: payload.id } : {}; - //console.log('body', body); - const url = `/api/1.0/person/accompanying-course/${id}/requestor.json`; - return fetch(url, { - method: method, - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify(body) - }) - .then(response => { - if (response.ok) { return response.json(); } - throw { msg: 'Error while sending AccompanyingPeriod Course requestor', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); -}; - -/* -* Endpoint v.2 chill_api_single_accompanying_course_resource, -* method POST/DELETE, add/remove a resource to the accompanyingCourse -* -* @id integer - id of accompanyingCourse -* @payload object of type person|thirdparty -* @method string - POST or DELETE -*/ -const postResource = (id, payload, method) => { - //console.log('payload', payload); - const body = { type: "accompanying_period_resource" }; - switch (method) { - case 'DELETE': - body['id'] = payload.id; - break; - default: - body['resource'] = { type: payload.type, id: payload.id }; - } - //console.log('body', body); - const url = `/api/1.0/person/accompanying-course/${id}/resource.json`; - return fetch(url, { - method: method, - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify(body) - }) - .then(response => { - if (response.ok) { return response.json(); } - throw { msg: 'Error while sending AccompanyingPeriod Course resource.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); -}; - -/* -* Endpoint to Add/remove SocialIssue -*/ -const postSocialIssue = (id, body, method) => { - //console.log('api body and method', body, method); - const url = `/api/1.0/person/accompanying-course/${id}/socialissue.json`; - return fetch(url, { - method: method, - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify(body) - }) - .then(response => { - if (response.ok) { return response.json(); } - throw { msg: 'Error while updating SocialIssue.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); -}; - -const getUsers = () => { - const url = `/api/1.0/main/user.json`; - - return fetchResults(url); -}; - const whoami = () => { const url = `/api/1.0/main/whoami.json`; return fetch(url) @@ -183,74 +48,10 @@ const whoami = () => { }); }; -const getListOrigins = () => { - const url = `/api/1.0/person/accompanying-period/origin.json`; - return fetch(url) - .then(response => { - if (response.ok) { return response.json(); } - throw { msg: 'Error while retriving origin\'s list.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); -}; - -const addScope = (id, scope) => { - const url = `/api/1.0/person/accompanying-course/${id}/scope.json`; - console.log(url); - console.log(scope); - - return fetch(url, { - method: 'POST', - body: JSON.stringify({ - id: scope.id, - type: scope.type, - }), - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - }) - .then(response => { - if (response.ok) { return response.json(); } - throw { msg: 'Error while adding scope', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); -}; - -const removeScope = (id, scope) => { - const url = `/api/1.0/person/accompanying-course/${id}/scope.json`; - - return fetch(url, { - method: 'DELETE', - body: JSON.stringify({ - id: scope.id, - type: scope.type, - }), - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - }) - .then(response => { - if (response.ok) { return response.json(); } - throw { msg: 'Error while adding scope', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; - }); -}; - -const getReferrersSuggested = (course) => { - const url = `/api/1.0/person/accompanying-course/${course.id}/referrers-suggested.json`; - - return fetchResults(url); -} - export { - getAccompanyingCourse, - patchAccompanyingCourse, - confirmAccompanyingCourse, - getSocialIssues, - postParticipation, - postRequestor, - postResource, - getUsers, whoami, - getListOrigins, - postSocialIssue, - addScope, - removeScope, + getSocialIssues, + getAccompanyingCourse, + getUsers, getReferrersSuggested, }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue index 84e7fb2c6..7e3b45103 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue @@ -11,7 +11,7 @@ {{ $t('course.step.draft') }} - + {{ $t('course.step.active') }} @@ -22,20 +22,35 @@ {{ $t('course.open_at') }}{{ $d(accompanyingCourse.openingDate.datetime, 'text') }} - {{ $t('course.referrer') }}: {{ accompanyingCourse.user.username }} + {{ $t('course.referrer') }}: {{ accompanyingCourse.user.text }} + + + + + + {{ $t('course.step.closed') }} + + + + {{ $d(accompanyingCourse.openingDate.datetime, 'text') }} - {{ $d(accompanyingCourse.closingDate.datetime, 'text') }} + + + {{ $t('course.referrer') }}: {{ accompanyingCourse.user.text }} -
    - - -
    + + +
    + + + @@ -43,12 +58,14 @@ + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue index fca3cee20..39fe50acc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue @@ -21,6 +21,7 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/ButtonLocation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/ButtonLocation.vue index b9e52f300..344119c44 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/ButtonLocation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/ButtonLocation.vue @@ -59,7 +59,15 @@ export default { locationStatusTo: 'person', personId: this.person.id }; - this.$store.dispatch('updateLocation', payload); + this.$store.dispatch('updateLocation', payload) + .catch(({name, violations}) => { + if (name === 'ValidationException' || name === 'AccessException') { + violations.forEach((violation) => this.$toast.open({message: violation})); + } else { + this.$toast.open({message: 'An error occurred'}) + } + }); + window.location.assign('#section-20'); this.modal.showModal = false; } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue index 1bff3f44d..7f5b04f99 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue @@ -83,12 +83,24 @@ export default { }, methods: { submitform() { - //console.log('submit'); - this.$store.dispatch('postFirstComment', this.formdata); + this.$store.dispatch('postFirstComment', this.formdata) + .catch(({name, violations}) => { + if (name === 'ValidationException' || name === 'AccessException') { + violations.forEach((violation) => this.$toast.open({message: violation})); + } else { + this.$toast.open({message: 'An error occurred'}) + } + }); }, removeComment() { - //console.log('remove'); - this.$store.dispatch('postFirstComment', {}); + this.$store.dispatch('postFirstComment', {}) + .catch(({name, violations}) => { + if (name === 'ValidationException' || name === 'AccessException') { + violations.forEach((violation) => this.$toast.open({message: violation})); + } else { + this.$toast.open({message: 'An error occurred'}) + } + }); } } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue index f23a7c9e5..d3e0ae4d7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue @@ -10,7 +10,7 @@
    {{ $t('confirm.alert_validation') }}
    diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue index fd3f59cef..dd6d6c70c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue @@ -5,7 +5,7 @@ addId : false, addEntity: false, addLink: false, - addHouseholdLink: true, + addHouseholdLink: false, addAltNames: true, addAge : true, hLevel : 3, @@ -20,14 +20,15 @@ v-if="hasCurrentHouseholdAddress" v-bind:person="participation.person"> +
  • + + + +
  • -
  • @@ -50,7 +49,7 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue index 41fd85f0e..5046e641d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue @@ -30,7 +30,7 @@ + + + + \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js index ce5e2d9c0..8633c003d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js @@ -2,7 +2,8 @@ import { createStore } from 'vuex'; import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js'; import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js'; -import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js'; +// import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js'; +import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods'; const debug = process.env.NODE_ENV !== 'production'; @@ -12,6 +13,7 @@ const store = createStore({ accompanyingCourse: window.accompanyingCourse, socialIssues: window.accompanyingCourse.socialIssues, socialIssuePicked: null, + socialIssuesOther: [], socialActionsReachables: [], socialActionPicked: null, personsPicked: window.accompanyingCourse.participations.filter(p => p.endDate == null) @@ -26,7 +28,6 @@ const store = createStore({ }, getters: { hasSocialActionPicked(state) { - console.log(state.socialActionPicked); return null !== state.socialActionPicked; }, hasSocialIssuePicked(state) { @@ -73,27 +74,43 @@ const store = createStore({ }, mutations: { setSocialActionsReachables(state, actions) { - console.log('set social action reachables'); - console.log(actions); + // console.log('set social action reachables'); + // console.log(actions); state.socialActionsReachables = actions; }, setSocialAction(state, socialAction) { - console.log('socialAction', socialAction); + // console.log('socialAction', socialAction); state.socialActionPicked = socialAction; }, setSocialIssue(state, socialIssueId) { - console.log('set social issue', socialIssueId); + // console.log('set social issue', socialIssueId); if (socialIssueId === null) { state.socialIssuePicked = null; } else { let mapped = state.socialIssues .find(e => e.id === socialIssueId); - console.log('mapped', mapped); state.socialIssuePicked = mapped; - console.log('social issue setted', state.socialIssuePicked); + // console.log('social issue setted', state.socialIssuePicked); } }, + addIssueInList(state, issue) { + //console.log('add issue list', issue.id); + state.socialIssues.push(issue); + }, + updateIssuesOther(state, payload) { + //console.log('update issues other'); + state.socialIssuesOther = payload; + }, + removeIssueInOther(state, issue) { + //console.log('remove issue other', issue.id); + state.socialIssuesOther = state.socialIssuesOther.filter( + (i) => i.id !== issue.id + ); + }, + updateSelected(state, payload) { + state.socialIssueSelected = payload; + }, setIsLoadingSocialActions(state, s) { state.isLoadingSocialActions = s; }, @@ -131,8 +148,6 @@ const store = createStore({ findSocialActionsBySocialIssue(socialIssueId).then( (response) => { - console.log(response); - console.log(response.results); commit('setSocialIssue', socialIssueId); commit('setSocialActionsReachables', response.results); commit('setIsLoadingSocialActions', false); @@ -142,34 +157,34 @@ const store = createStore({ }); }, submit({ commit, getters, state }) { - console.log('submit'); - let - payload = getters.buildPayloadCreate, - errors = []; - + let payload = getters.buildPayloadCreate; + const url = `/api/1.0/person/accompanying-course/${state.accompanyingCourse.id}/work.json`; commit('setPostingWork'); - create(state.accompanyingCourse.id, payload) - .then( ({status, data}) => { - console.log('created return', { status, data}); - if (status === 200) { - console.log('created, nothing to do here any more. Bye-bye!'); - window.location.assign(`/fr/person/accompanying-period/work/${data.id}/edit`); - } else if (status === 422) { - console.log(data); - for (let i in data.violations) { - console.log(i); - console.log(data.violations[i].title); - errors.push(data.violations[i].title); - } - console.log('errors after reseponse handling', errors); - console.log({errors, cancel_posting: true}); - commit('addErrors', { errors, cancel_posting: true }); - } + makeFetch('POST', url, payload) + .then((response) => { + window.location.assign(`/fr/person/accompanying-period/work/${response.id}/edit`) + }) + .catch((error) => { + throw error; }); }, + fetchOtherSocialIssues({commit}) { + const url = `/api/1.0/person/social-work/social-issue.json`; + return makeFetch('GET', url) + .then((response) => { + commit('updateIssuesOther', response.results); + }) + .catch((error) => { + throw error; + }) + } }, + + }); +store.dispatch('fetchOtherSocialIssues'); + export { store }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index c99685680..0bdb91ab5 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -44,9 +44,9 @@
    -
    - +
    {{ g.goal.title.fr }} +
    @@ -61,10 +61,9 @@

    {{ $t('available_goals_text') }}

    -
      -
    • - - {{ g.title.fr }} +
        +
      • + {{ g.title.fr }}
    @@ -84,7 +83,7 @@
    -

    {{ $t('Evaluations') }}

    +

    {{ $t('Evaluations') }} - {{ $t('Forms') }} - {{ $t('Post') }}

    @@ -98,10 +97,9 @@

    {{ $t('available_evaluations_text') }}

    -
      -
    • - - {{ e.title.fr }} +
        +
      • + {{ e.title.fr }}
    @@ -119,9 +117,9 @@

    {{ $t('persons_involved') }}

    -
      +
      • - +
      @@ -197,6 +195,18 @@
    +
    + + + +
    +

    {{ $t('fix_these_errors') }}

      @@ -232,6 +242,7 @@ import AddEvaluation from './components/AddEvaluation.vue'; import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue'; import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'; import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue'; +import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue'; const i18n = { messages: { @@ -247,6 +258,8 @@ const i18n = { add_objectif: "Ajouter un motif - objectif - dispositif", add_an_objective: "Ajouter un objectif", Evaluations: "Évaluations", + Forms: "Formulaires", + Post: "Courriers", add_an_evaluation: "Ajouter une évaluation", persons_involved: "Usagers concernés", handling_thirdparty: "Tiers traitant", @@ -276,6 +289,7 @@ export default { AddPersons, PersonRenderBox, AddressRenderBox, + PickTemplate, }, i18n, data() { @@ -318,6 +332,7 @@ export default { 'thirdParties', 'isPosting', 'errors', + 'templatesAvailablesForAction', ]), ...mapGetters([ 'hasResultsForAction', @@ -386,7 +401,7 @@ export default { this.$store.commit('removeGoal', g); }, addEvaluation(e) { - this.$store.commit('addEvaluation', e); + this.$store.dispatch('addEvaluation', e); }, toggleAddEvaluation() { this.showAddEvaluation = !this.showAddEvaluation; @@ -414,6 +429,10 @@ export default { submit() { this.$store.dispatch('submit'); }, + beforeGenerateTemplate() { + console.log('before generate'); + return Promise.resolve(); + } } }; @@ -495,8 +514,8 @@ div#workEditor { & > div.results_without_objective { background: repeating-linear-gradient( 45deg, - $gray-500, - $gray-500 10px, + $gray-200, + $gray-200 10px, $gray-100 10px, $gray-100 20px ); @@ -535,31 +554,11 @@ div#workEditor { font-size: 85%; } - ul.list-evaluations, - ul.list-objectives, - ul.list-results { - - list-style-type: none; - padding: 0; - - li { - margin: 0.5rem; - - &.badge { - padding-bottom: 0; - padding-top: 0; - padding-left: 0; - } - } - } - i.fa { padding: 0.25rem; color: $white; - &.fa-plus { background-color: $green; } - &.fa-times { background-color: $red; } - &.fa-pencil { background-color: $orange; } + &.fa-times { color: $red; } } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue index 9dee7693a..60aa1cd2e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue @@ -1,9 +1,10 @@