Merge remote-tracking branch 'origin/master' into fix/236-update-social-work-metadata-importer

This commit is contained in:
Julien Fastré 2021-12-13 11:01:19 +01:00
commit fdae6c106a
1525 changed files with 89991 additions and 72132 deletions

2
.gitignore vendored
View File

@ -20,3 +20,5 @@ docs/build/
.phpunit.result.cache
###< phpunit/phpunit ###
/.php-cs-fixer.cache
/.idea/

View File

@ -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/

View File

@ -0,0 +1,24 @@
# Description of changes
<!--
describe here the change of your MR. It can be either a text, or a bullet list
for changes
-->
# Issues related
<!--
list the issues related to this MR.
It may be client issues, or dev issues
-->
* ...
* ...
# Tests
<!-- Describe tests if any, or why no tests -->

79
.php_cs.dist.php Normal file
View File

@ -0,0 +1,79 @@
<?php
/**
* 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);
/** @var \drupol\PhpCsFixerConfigsPhp\Config\Php73 $config */
$config = require __DIR__ . '/tests/app/vendor/drupol/php-conventions/config/php73/php_cs_fixer.config.php';
$config
->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);

View File

@ -11,11 +11,162 @@ and this project adheres to
## Unreleased
<!-- write down unreleased development here -->
* [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
<!--
<!--
Coming soon...
@ -113,4 +267,4 @@ DO NOT ADD unreleased items here. Add them under "Unreleased" title
## Stable releases
No stable releases for v2+

View File

@ -56,28 +56,28 @@ Arborescence:
Comment s'échaffaudent les styles dans Chill ?
1. l'entrypoint **mod_bootstrap** (module bootstrap) est le premier niveau. Toutes les parties(modules) de bootstrap sont convoquées dans le fichier ```bootstrap.js``` situé dans ```ChillMainBundle/Resources/public/module/bootstrap```.
* Au début, ce fichier importe le fichier ```variables.scss``` qui détermine la plupart des réglages bootstrap tels qu'on les a personnalisés. Ce fichier surcharge l'original, et de nombreuses variables y sont adaptées pour Chill.
1. l'entrypoint **mod_bootstrap** (module bootstrap) est le premier niveau. Toutes les parties(modules) de bootstrap sont convoquées dans le fichier ```bootstrap.js``` situé dans ```ChillMainBundle/Resources/public/module/bootstrap```.
* Au début, ce fichier importe le fichier ```variables.scss``` qui détermine la plupart des réglages bootstrap tels qu'on les a personnalisés. Ce fichier surcharge l'original, et de nombreuses variables y sont adaptées pour Chill.
* On veillera à ce qu'on puisse toujours comparer ce fichier à l'original de bootstrap. En cas de mise à jour de bootstrap, il faudra générer un diff, et adapter ce diff sur le fichier variable de la nouvelle version.
* A la fin on importe le fichier ```custom.scss```, qui comprends des adaptations de bootstrap pour le préparer à notre thème Chill.
* A la fin on importe le fichier ```custom.scss```, qui comprends des adaptations de bootstrap pour le préparer à notre thème Chill.
* ce ```custom.scss``` peut être splitté en plus petits fichiers avec des ```@import 'custom/...'```
* L'idée est que cette première couche bootstrap règle un partie importante des styles de l'application, en particulier ce qui touche aux position du layout, aux points de bascules responsive, aux marges et écarts appliqués par défauts aux éléments qu'on manipule.
2. l'entrypoint **chill** est le second niveau. Il contient le thème Chill qui est reconnaissable à l'application.
* Chaque bundle a un dossier ```Resources/public/chill``` dans lequel on peut trouver une feuille sass principale, qui est éventuellement splittée avec des ```@imports```. Toutes ces feuilles sont compilées dans un unique entrypoint Chill, c'est le thème de l'application. Celui-ci surcharge bootstrap.
* La feuille chillmain.scss devrait contenir les cascades de styles les plus générales, celles qui sont appliquées à de nombreux endroits de l'application.
* La feuille chillmain.scss devrait contenir les cascades de styles les plus générales, celles qui sont appliquées à de nombreux endroits de l'application.
* La feuille chillperson.scss va aussi retrouver des styles propres aux différents contextes des personnes: person, household et accompanyingcourse.
* Certains bundles plus secondaires ne contiennent que des styles spécifiques à leur fonctionnement.
3. les entrypoints **vue_** sont utilisés pour des composants vue. Les fichiers vue peuvent contenir un bloc de styles scss. Ce sont des styles qui ne concernent que le composant et son héritage, le tag ```scoped``` précise justement sa portée (voir la doc).
4. les entrypoints **page_** sont utilisés pour ajouter des assets spécifiques à certaines pages, le plus souvent des scripts et des styles.
4. les entrypoints **page_** sont utilisés pour ajouter des assets spécifiques à certaines pages, le plus souvent des scripts et des styles.
## Taguer du code html et construire la cascade de styles
L'exemple suivant montre comment taguer sans excès un élément de code. On remarque que:
* il n'est pas nécessaire de taguer toutes les classes intérieures,
* il n'est pas nécessaire de taguer toutes les classes intérieures,
* il ne faut pas répéter la classe parent dans toutes les classes enfants. La cascade sass va permettre de saisir le html avec souplesse sans alourdir la structure des balises.
* souvent la première classe sera déclinée par plusieurs classes qui commencent de la même manière: ```bloc-dark``` ajoute juste la version sombre de ```bloc```, on ne met pas ```bloc dark```, car on ne souhaite pas que la classe ```dark``` de ```bloc``` interagisse avec la même classe ```dark``` de ```table```. On aura donc un élément ```bloc bloc-dark``` et un élément ```table table-dark```.
@ -94,11 +94,11 @@ L'exemple suivant montre comment taguer sans excès un élément de code. On rem
</div>
```
Finalement, il importe ici de définir ce qu'est un bloc, ce qu'est une zone d'actions et ce qu'est un bouton. Ces 3 éléments existent de manière autonome, ce sont les seuls qu'on tagge.
Finalement, il importe ici de définir ce qu'est un bloc, ce qu'est une zone d'actions et ce qu'est un bouton. Ces 3 éléments existent de manière autonome, ce sont les seuls qu'on tagge.
Par exemple pour mettre un style au titre on précise juste h3 dans la cascade bloc.
```sass
```scss
div.bloc {
// un bloc générique, utilisé à plusieurs endroits
&.bloc-dark {
@ -113,12 +113,12 @@ div.bloc {
}
}
div.mon-bloc {
// des exceptions spécifiques à mon-bloc,
// des exceptions spécifiques à mon-bloc,
// qui sont des adaptations de bloc
}
ul.record_actions {
// va uniformiser tous les record_actions de l'application
// va uniformiser tous les record_actions de l'application
li {
//...
}
@ -260,7 +260,7 @@ Exemple:
address|chill_entity_render_box
```
Justification:
Justification:
* des éléments sont parfois personnalisés par installation (par exemple, le nom de chaque utilisateur sera suivi par le nom du service)
* pour rationaliser et rendre semblable les affichages
@ -270,13 +270,13 @@ A prevoir:
* toujours trois positions:
* inline
* block
* block
* item (dans un tableau, une ligne)
> block et item sont en fait la même option passée au render_box: render: bloc. Il y a aussi raw pour le inline, et label pour une titraille configurable avec des options.
> quand on passe loption render: bloc, on peut placer le render_box dans une boucle for plus large qui fonctionne avec la classe flex-table ou la classe flex-bloc, ce qui donnera un affichage en rangée (table) ou en blocs. [name=Mathieu]
> quand on passe loption render: bloc, on peut placer le render_box dans une boucle for plus large qui fonctionne avec la classe flex-table ou la classe flex-bloc, ce qui donnera un affichage en rangée (table) ou en blocs. [name=Mathieu]
#### En vue
@ -292,13 +292,13 @@ A chaque fois qu'on indique le nom d'une personne, un parcours, un ménage, il y
Ces éléments sont toujours proposé par des `render_box` par défaut. Des options permettent de les désactiver dans des cas particuliers
> à discuter, quelques réflexion:
> quelle est la logique qui domine pour les boutons ? on a symbolisé les 4 actions du crud par des couleurs: bleu(show) orange(edit) vert(create) et rouge(delete).
> Est-ce que c'est ça qui prime, et comment ça s'articule avec la logique des pictos ?
> à discuter, quelques réflexion:
> quelle est la logique qui domine pour les boutons ? on a symbolisé les 4 actions du crud par des couleurs: bleu(show) orange(edit) vert(create) et rouge(delete).
> Est-ce que c'est ça qui prime, et comment ça s'articule avec la logique des pictos ?
> Par exemple, il pourrait être logique d'utiliser l'oeil bleu pour voir l'objet, qu'il s'agisse d'une personne ou d'un parcours, ce serait plutôt le contexte, et l'infobulle (title) qui préciserait le contexte.
> Je pense que les pictos de boutons doivent faire référence à l'action, mais pas à l'objet. Autrement dit je n'utiliserais jamais l'icone du ménage ou du parcours dans les boutons.
> Pour représenter les ménages et les parcours, je pense qu'il faudrait trouver autre chose que forkawesome. Si c'est des pictos, trouver un motif différents et de tailles différente. Réfléchir à un couplage picto-couleur-forme différent, qui exprime le contexte et qui se distingue bien des boutons.
> Idem pour les badges, il faut une palette de badge qui couvre tous les besoins: socialIssue, socialActions, socialReason, members, etc. [name=Mathieu]
> Idem pour les badges, il faut une palette de badge qui couvre tous les besoins: socialIssue, socialActions, socialReason, members, etc. [name=Mathieu]
### Formulaires
@ -350,7 +350,7 @@ A chaque fois qu'un élément est créé par un formulaire, un message flash doi
> "L'élément a été créé"
Le nom de l'élément peut être remplacé par quelque chose de plus pertinent:
Le nom de l'élément peut être remplacé par quelque chose de plus pertinent:
> * L'activité a été créée
> * Le rendez-vous a été créé
@ -362,7 +362,7 @@ Le nom de l'élément peut être remplacé par quelque chose de plus pertinent:
A chaque fois qu'un élément est enregistré, un message flash doit apparaitre:
> * Les données ont été modifiées
>
>
#### Erreur sur un formulaire (erreur de validation)
@ -377,7 +377,36 @@ Les erreurs doivent apparaitre attachée au champ qui les concerne. Toutefois, i
A chaque fois qu'un lien est indiqué, vérifier si on ne doit pas utiliser la fonction `chill_return_path`, `chill_forward_return_path` ou `chill_return_path_or`.
* depuis la page liste, vers l'ouverture d'un élément, ou le bouton création => utiliser `chill_path_add_return_path`
* dans ces pages d'éditions,
* dans ces pages d'éditions,
* utiliser `chill_return_path_or` dans le bouton "Cancel";
* pour les boutons "enregistrer et voir" et "Enregistrer et fermer" => ?
### Assets pour les listes de suggestion
Créer une liste de suggestions à ajouter (tout l'item est cliquable)
```html
<ul class="list-suggest add-items">
<li>
<span>item</span>
</li>
</ul>
```
Créer une liste de suggestions à enlever (avec une croix rouge cliquable, l'ancre a est vide)
```html
<ul class="list-suggest remove-items">
<li>
<span>
item
<a></a>
</span>
</li>
</ul>
```
Créer un titre enlevable (avec une croix rouge cliquable, l'ancre a est vide)
```html
<div class="item-title">
title
<a></a>
</div>
```
Les classes `cols` ou `inline` peuvent être ajoutées à côté de `list-suggest` pour modifier la disposition de la liste.

View File

@ -629,12 +629,12 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
chill-bundles
Copyright (C) 2021 Chill Project
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,

View File

@ -24,6 +24,7 @@
"phpoffice/phpspreadsheet": "^1.16",
"ramsey/uuid-doctrine": "^1.7",
"sensio/framework-extra-bundle": "^5.5",
"spomky-labs/base64url": "^2.0",
"symfony/asset": "4.*",
"symfony/browser-kit": "^5.2",
"symfony/css-selector": "^5.2",
@ -52,8 +53,10 @@
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3",
"drupol/php-conventions": "^5",
"fakerphp/faker": "^1.13",
"nelmio/alice": "^3.8",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^7.0",
"symfony/debug-bundle": "^5.1",
"symfony/dotenv": "^5.1",
@ -88,7 +91,8 @@
},
"autoload-dev": {
"psr-4": {
"App\\": "tests/app/src/"
"App\\": "tests/app/src/",
"Chill\\DocGeneratorBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests"
}
},
"minimum-stability": "dev",

View File

@ -1,123 +1,126 @@
<?php
/**
* 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\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;
class BirthdateFilter implements FilterInterface, ExportElementValidatedInterface
class BirthdateFilter implements ExportElementValidatedInterface, FilterInterface
{
// add specific role for this filter
// add specific role for this filter
public function addRole()
{
// we do not need any new role for this filter, so we return null
return null;
}
// here, we alter the query created by Export
public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
// we create the clause here
$clause = $qb->expr()->between(
'person.birthdate',
':date_from',
':date_to'
);
// we have to take care **not to** remove previous clauses...
if ($where instanceof Expr\Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
// we add parameters from $data. $data contains the parameters from the form
$qb->setParameter('date_from', $data['date_from']);
$qb->setParameter('date_to', $data['date_to']);
}
// we give information on which type of export this filter applies
public function applyOn()
{
return 'person';
}
public function getTitle()
{
return 'Filter by person\'s birthdate';
}
// we build a form to collect some parameters from the users
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',
));
}
// the form created above must be validated. The process of validation
// is executed here. This function is added by the interface
// `ExportElementValidatedInterface`, and can be ignore if there is
// no need for a validation
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();
}
}
// here, we alter the query created by Export
public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
// we create the clause here
$clause = $qb->expr()->between('person.birthdate', ':date_from',
':date_to');
// we have to take care **not to** remove previous clauses...
if ($where instanceof Expr\Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
// we add parameters from $data. $data contains the parameters from the form
$qb->setParameter('date_from', $data['date_from']);
$qb->setParameter('date_to', $data['date_to']);
]);
}
// here, we create a simple string which will describe the action of
// the filter in the Response
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()
{
return 'Filter by person\'s birthdate';
}
// the form created above must be validated. The process of validation
// is executed here. This function is added by the interface
// `ExportElementValidatedInterface`, and can be ignore if there is
// no need for a validation
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();
}
}
}

View File

@ -1,118 +1,117 @@
<?php
/**
* 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\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 Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
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;
// this export does not add any form
}
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";
// the Closure which will be executed by the formatter.
return function ($value) {
switch ($value) {
case '_header':
// we have to process specifically the '_header' string,
// which will be used by the formatter to show a column title
return $this->getTitle();
default:
// for all value, we do not process them and return them
// immediatly
return $value;
}
};
}
public function requiredRole()
{
return new Role(PersonVoter::STATS);
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = array())
{
// we gather all center the user choose.
$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)
{
// this array match the result keys in the query. We have only
// one column.
return array('export_result');
return ['export_result'];
}
public function getLabels($key, array $values, $data)
public function getResult($qb, $data)
{
// the Closure which will be executed by the formatter.
return function($value) {
switch($value) {
case '_header':
// we have to process specifically the '_header' string,
// which will be used by the formatter to show a column title
return $this->getTitle();
default:
// for all value, we do not process them and return them
// immediatly
return $value;
};
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getAllowedFormattersTypes()
public function getTitle()
{
return array(FormatterInterface::TYPE_TABULAR);
return 'Count peoples';
}
public function buildForm(FormBuilderInterface $builder) {
// this export does not add any form
public function getType()
{
return Declarations::PERSON_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
// we gather all center the user choose.
$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()
{
// explain the export manager which formatters and filters are allowed
return array(Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN);
return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN];
}
}

View File

@ -23,15 +23,196 @@ Every time an entity is created, viewed or updated, the software check if the us
The user must be granted access to the action on this particular entity, with this scope and center.
TL;DR
=====
Resolve scope and center
------------------------
In a service, resolve the center and scope of an entity
.. code-block:: php
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
class MyService {
private ScopeResolverDispatcher $scopeResolverDispatcher;
private CenterResolverDispatcher $centerResolverDispatcher;
public function myFunction($entity) {
/** @var null|Center[]|Center $center */
$center = $this->centerResolverDispatcher->resolveCenter($entity);
// $center may be null, an array of center, or an instance of Center
if ($this->scopeResolverDispatcher->isConcerned($entity) {
/** @var null|Scope[]|Scope */
$scope = $this-scopeResolverDispatcher->resolveScope($entity);
// $scope may be null, an array of Scope, or an instance of Scope
}
}
}
In twig template, resolve the center:
.. code-block:: twig
{# resolve a center #}
{% if person|chill_resolve_center is not null%}
{% if person|chill_resolve_center is iterable %}
{% set centers = person|chill_resolve_center %}
{% else %}
{% set centers = [ person|chill_resolve_center ] %}
{% endif %}
<span class="open_sansbold">
{{ 'Center'|trans|upper}} :
</span>
{% for c in centers %}
{{ c.name|upper }}
{% if not loop.last %}, {% endif %}
{% endfor %}
{%- endif -%}
In twig template, resolve the scope:
.. code-block:: twig
{% if entity|chill_is_scope_concerned %}
{% if entity|chill_resolve_scope is iterable %}
{% set scopes = entity|chill_resolve_scope %}
{% else %}
{% set scopes = [ entity|chill_resolve_scope ] %}
{% endif %}
<span>Scopes&nbsp;:</span>
{% for s in scopes %}
{{ c.name|localize_translatable_string }}
{% if not loop.last %}, {% endif %}
{% endfor %}
{%- endif -%}
Build a ``Voter``
-----------------
.. code-block:: php
<?php
namespace Chill\DocStoreBundle\Security\Authorization;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Security;
class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
// roles should be stored into constants:
const CREATE = 'CHILL_PERSON_DOCUMENT_CREATE';
const SEE = 'CHILL_PERSON_DOCUMENT_SEE';
const SEE_DETAILS = 'CHILL_PERSON_DOCUMENT_SEE_DETAILS';
const UPDATE = 'CHILL_PERSON_DOCUMENT_UPDATE';
const DELETE = 'CHILL_PERSON_DOCUMENT_DELETE';
protected Security $security;
protected VoterHelperInterface $voterHelper;
public function __construct(
Security $security,
VoterHelperFactoryInterface $voterHelperFactory
) {
$this->security = $security;
// we build here a voter helper. This will ease the operations below.
// when the authorization model is changed, it will be easy to make a different implementation
// of the helper, instead of writing all Voters
$this->voterHelper = $voterHelperFactory
// create a builder with some context
->generate(self::class)
// add the support of given roles for given class:
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->addCheckFor(PersonDocument::class, $this->getRoles())
->build();
}
protected function supports($attribute, $subject)
{
return $this->voterHelper->supports($attribute, $subject);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
// basic check
if (!$token->getUser() instanceof User) {
return false;
}
// we first check the acl for associated elements.
// here, we must be able to see the person associated to the document:
if ($subject instanceof PersonDocument
&& !$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) {
// not possible to see the associated person ? Then, not possible to see the document!
return false;
}
// the voter helper will implements the logic:
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
}
// all the method below are used to register roles into the admin part
public function getRoles()
{
return [
self::CREATE,
self::SEE,
self::SEE_DETAILS,
self::UPDATE,
self::DELETE
];
}
public function getRolesWithoutScope()
{
return array();
}
public function getRolesWithHierarchy()
{
return ['PersonDocument' => $this->getRoles() ];
}
}
From an user point of view
--------------------------
==========================
The software is design to allow fine tuned access rights for complicated installation and team structure. The administrators may also decide that every user has the right to see all resources, where team have a more simple structure.
Here is an overview of the model.
Chill can be multi-center
^^^^^^^^^^^^^^^^^^^^^^^^^
-------------------------
Chill is designed to be installed once for social center who work with multiple teams separated, or for social services's federation who would like to share the same installation of the software for all their members.
@ -42,7 +223,7 @@ Otherwise, it is not required to create multiple center: Chill can also work for
Obviously, users working in the different centers are not allowed to see the entities (_persons_, _reports_, _activities_) of other centers. But users may be attached to multiple centers: consequently they will be able to see the entities of the multiple centers they are attached to.
Inside center, scope divide team
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
--------------------------------
Users are attached to one or more center and, inside to those center, there may exists differents scopes. The aim of those _scopes_ is to divide the whole team of social worker amongst different departement, for instance: the social team, the psychologist team, the nurse team, the administrative team, ... Each team is granted of different rights amongst scope. For instance, the social team may not see the _activities_ of the psychologist team. The administrative team may see the date & time's activities, but is not allowed to see the detail of those entities (the personal notes, ...).
@ -52,8 +233,38 @@ As entities have only one scopes, if some entities must be shared across two dif
Example: if some activities must be seen and updated between nurses and psychologists, the administrator will create a scope "nurse and psy" and add the ability for both team "nurse" and "psychologist" to "create", "see", and "update" the activities belonging to scope "nurse and psy".
Where does the ``scope`` and ``center`` comes from ?
====================================================
Most often, scope and center comes from user's input:
* when person is created, Chill asks the associated center to the user. Then, every entity associated to the user (Activity, ...) is associated to this center;
* when an entity is created, Chill asks the associated scope.
The UI check the model before adding those input into form. If the user hae access to only one center or scope, this scope or center is filled automatically, and the UI does not ask the user. Most of the times, the user does not see "Pick a scope" and "Pick a center" inputs.
Scope and Center are associated to entities through ``ManyToOne`` properties, which are then mapped to ``FOREIGN KEY`` in tables, ...
But sometimes, this implementation does not fits the needs:
* persons are associated to center *geographically*: the address of each person contains lat/lon coordinates, and the center is resolved from this coordinated;
* some would like to associated persons to multiple center, or one center;
* entities are associated to scope through the job reached by "creator" (an user);
* some would like not to use scope at all;
* …
For this reasons, associated center and scopes must be resolved programmatically. The default implementation rely on the model association, as described above. But it becomes possible to change the behaviour on different implementations.
Is my entity "concerned" by scopes ?
------------------------------------
Some entities are concerned by scope, some not.
This is also programmatically resolved.
The concepts translated into code
-----------------------------------
===================================
.. figure:: /_static/access_control_model.png
@ -81,7 +292,7 @@ At each step of his lifetime (creation, view of the entity and eventually of his
All those action are executed through symfony voters and helpers.
How to check authorization ?
----------------------------
============================
Just use the symfony way-of-doing, but do not forget to associate the entity you want to check access. For instance, in controller :
@ -100,34 +311,23 @@ Just use the symfony way-of-doing, but do not forget to associate the entity you
And in template :
.. code-block:: html+jinja
.. code-block:: twig
{{ if is_granted('CHILL_ENTITY_SEE', entity) %}print something{% endif %}
Retrieving reachable scopes and centers
----------------------------------------
Retrieving reachable scopes and centers for a user
--------------------------------------------------
The class :class:`Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelper` helps you to get centers and scope reachable by a user.
The class :class:`Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelperInterface` helps you to get centers and scope reachable by a user.
Those methods are intentionnaly build to give information about user rights:
- getReachableCenters: to get reachable centers for a user
- getReachableScopes : to get reachable scopes for a user
.. note::
The service is reachable through the Depedency injection with the key `chill.main.security.authorization.helper`. Example :
.. code-block:: php
$helper = $container->get('chill.main.security.authorization.helper');
.. todo::
Waiting for a link between our api and this doc, we invite you to read the method signatures `here <https://github.com/Chill-project/Main/blob/add_acl/Security/Authorization/AuthorizationHelper.php>`_
Adding your own roles
=====================
---------------------
Extending Chill will requires you to define your own roles and rules for your entities. You will have to define your own voter to do so.
@ -152,7 +352,7 @@ To create your own roles, you should:
Declare your role
------------------
^^^^^^^^^^^^^^^^^^
To declare new role, implement the class :class:`Chill\\MainBundle\\Security\\ProvideRoleInterface`.
@ -212,69 +412,8 @@ Example of an implementation of :class:`Chill\\MainBundle\\Security\\ProvideRole
}
Implement your voter
--------------------
Inside this class, you might use the :class:`Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelper` to check permission (do not re-invent the wheel). This is a real-world example:
.. code-block:: php
namespace Chill\ReportBundle\Security\Authorization;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
class ReportVoter extends AbstractChillVoter
{
const CREATE = 'CHILL_REPORT_CREATE';
const SEE = 'CHILL_REPORT_SEE';
const UPDATE = 'CHILL_REPORT_UPDATE';
/**
*
* @var AuthorizationHelper
*/
protected $helper;
public function __construct(AuthorizationHelper $helper)
{
$this->helper = $helper;
}
protected function getSupportedAttributes()
{
return array(self::CREATE, self::SEE, self::UPDATE);
}
protected function getSupportedClasses()
{
return array('Chill\ReportBundle\Entity\Report');
}
protected function isGranted($attribute, $report, $user = null)
{
if (! $user instanceof \Chill\MainBundle\Entity\User){
return false;
}
return $this->helper->userHasAccess($user, $report, $attribute);
}
}
Then, you will have to declare the service and tag it as a voter :
.. code-block:: yaml
services:
chill.report.security.authorization.report_voter:
class: Chill\ReportBundle\Security\Authorization\ReportVoter
arguments:
- "@chill.main.security.authorization.helper"
tags:
- { name: security.voter }
Adding role hierarchy
---------------------
^^^^^^^^^^^^^^^^^^^^^
You should prepend Symfony's security component directly from your code.
@ -312,3 +451,484 @@ You should prepend Symfony's security component directly from your code.
}
Implement your voter
^^^^^^^^^^^^^^^^^^^^
Most of the time, Voter will check that:
1. The given role is reachable (= ``$attribute``)
2. for the given center,
3. and, if any, for the given role
4. if the entity is associated to another entity, this entity should be, at least, viewable by the user.
Thats what we call the "autorization logic". But this logic may be replace by a new one, and developers should take care of it.
Then voter implementation should take care of:
* check the access to associated entities. For instance, if an ``Activity`` is associated to a ``Person``, the voter should first check that the user can show the associated ``Person``;
* as far as possible, delegates the check for associated center, scopes, and check for authorization using the authorization logic. VoterHelper will ease the most common operation of this logic.
This is an example of implementation:
.. code-block:: php
<?php
namespace Chill\DocStoreBundle\Security\Authorization;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
protected Security $security;
protected VoterHelperInterface $voterHelper;
public function __construct(
Security $security,
VoterHelperFactoryInterface $voterHelperFactory
) {
$this->security = $security;
// we build here a voter helper. This will ease the operations below.
// when the authorization model is changed, it will be easy to make a different implementation
// of the helper, instead of writing all Voters
$this->voterHelper = $voterHelperFactory
// create a builder with some context
->generate(self::class)
// add the support of given roles for given class:
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->addCheckFor(PersonDocument::class, $this->getRoles())
->build();
}
protected function supports($attribute, $subject)
{
return $this->voterHelper->supports($attribute, $subject);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
// basic check
if (!$token->getUser() instanceof User) {
return false;
}
// we first check the acl for associated elements.
// here, we must be able to see the person associated to the document:
if ($subject instanceof PersonDocument
&& !$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) {
// not possible to see the associated person ? Then, not possible to see the document!
return false;
}
// the voter helper will implements the logic of checking:
// 1. that the center is reachable
// 2. for this given entity
// 3. for this given scope
// 4. and for the given role
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
}
public function getRoles()
{
// ...
}
public function getRolesWithoutScope()
{
// ...
}
public function getRolesWithHierarchy()
{
// ...
}
}
Then, you will have to declare the service and tag it as a voter :
.. code-block:: yaml
services:
chill.report.security.authorization.report_voter:
class: Chill\ReportBundle\Security\Authorization\ReportVoter
arguments:
- "@chill.main.security.authorization.helper"
tags:
- { name: security.voter }
How to resolve scope and center programmatically ?
==================================================
In a service, resolve the center and scope of an entity
.. code-block:: php
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
class MyService {
private ScopeResolverDispatcher $scopeResolverDispatcher;
private CenterResolverDispatcher $centerResolverDispatcher;
public function myFunction($entity) {
/** @var null|Center[]|Center $center */
$center = $this->centerResolverDispatcher->resolveCenter($entity);
// $center may be null, an array of center, or an instance of Center
if ($this->scopeResolverDispatcher->isConcerned($entity) {
/** @var null|Scope[]|Scope */
$scope = $this-scopeResolverDispatcher->resolveScope($entity);
// $scope may be null, an array of Scope, or an instance of Scope
}
}
}
In twig template, resolve the center:
.. code-block:: twig
{# resolve a center #}
{% if person|chill_resolve_center is not null%}
{% if person|chill_resolve_center is iterable %}
{% set centers = person|chill_resolve_center %}
{% else %}
{% set centers = [ person|chill_resolve_center ] %}
{% endif %}
<span class="open_sansbold">
{{ 'Center'|trans|upper}} :
</span>
{% for c in centers %}
{{ c.name|upper }}
{% if not loop.last %}, {% endif %}
{% endfor %}
{%- endif -%}
In twig template, resolve the scope:
.. code-block:: twig
{% if entity|chill_is_scope_concerned %}
{% if entity|chill_resolve_scope is iterable %}
{% set scopes = entity|chill_resolve_scope %}
{% else %}
{% set scopes = [ entity|chill_resolve_scope ] %}
{% endif %}
<span>Scopes&nbsp;:</span>
{% for s in scopes %}
{{ c.name|localize_translatable_string }}
{% if not loop.last %}, {% endif %}
{% endfor %}
{%- endif -%}
What is the default implementation of Scope and Center resolver ?
-----------------------------------------------------------------
By default, the implementation rely on association into entities.
* implements ``Chill\MainBundle\Entity\HasCenterInterface`` on entities which have one or any center;
* implements ``Chill\MainBundle\Entity\HasCentersInterface`` on entities which have one, multiple or any centers;
* implements ``Chill\MainBundle\Entity\HasScopeInterface`` on entities which have one or any scope;
* implements ``Chill\MainBundle\Entity\HasScopesInterface`` on entities which have one or any scopes;
Then, the default implementation will resolve the center and scope based on the implementation in your model.
How to change the default behaviour ?
-------------------------------------
Implements those interface into services:
* ``Chill\MainBundle\Security\Resolver\CenterResolverInterface``;
* ``Chill\MainBundle\Security\Resolver\ScopeResolverInterface``;
Authorization into lists and index pages
========================================
Due to the fact that authorization model may be overriden, "list" and "index" pages should not rely on center and scope from controller. This must be delegated to dedicated service, which will be aware of the authorization model. We call them ``ACLAwareRepository``. This service must implements an interface, in order to allow to change the implementation.
The controller **must not** performs any DQL or SQL query.
Example in a controller:
.. code-block:: php
namespace Chill\TaskBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface;
final class SingleTaskController extends AbstractController
{
private SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository;
/**
*
* @Route(
* "/{_locale}/task/single-task/list",
* name="chill_task_singletask_list"
* )
*/
public function listAction(
Request $request
) {
$this->denyAccessUnlessGranted(TaskVoter::SHOW, null);
$nb = $this->singleTaskAclAwareRepository->countByAllViewable(
'', // search pattern
[] // search flags
);
$paginator = $this->paginatorFactory->create($nb);
if (0 < $nb) {
$tasks = $this->singleTaskAclAwareRepository->findByAllViewable(
'', // search pattern
[] // search flags
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage(),
// ordering:
[
'startDate' => 'DESC',
'endDate' => 'DESC',
]
);
} else {
$tasks = [];
}
return $this->render('@ChillTask/SingleTask/List/index.html.twig', [
'tasks' => $tasks,
'paginator' => $paginator,
'filter_order' => $filterOrder
]);
}
}
Writing ``ACLAwareRepository``
------------------------------
The ACLAwareRepository should rely on interfaces
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As described above, the ACLAwareRepository will perform the query for listing entities, and take care of authorization.
Those "ACLAwareRepositories" must be described into ``interfaces``.
The service must rely on this interface, and not on the default implementation.
Example: at first, we design an interface for listing ``SingleTask`` entities:
.. code-block:: php
<?php
namespace Chill\TaskBundle\Repository;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
interface SingleTaskAclAwareRepositoryInterface
{
/**
* @return SingleTask[]|array
*/
public function findByCurrentUsersTasks(?string $pattern = null, ?array $flags = [], ?int $start = 0, ?int $limit = 50, ?array $orderBy = []): array;
public function countByCurrentUsersTasks(?string $pattern = null, ?array $flags = []): int;
public function countByAllViewable(
?string $pattern = null,
?array $flags = []
): int;
/**
* @return SingleTask[]|array
*/
public function findByAllViewable(
?string $pattern = null,
?array $flags = [],
?int $start = 0,
?int $limit = 50,
?array $orderBy = []
): array;
}
Implements this interface and register the interface as an alias for the implementation.
.. code-block:: yaml
services:
Chill\TaskBundle\Repository\SingleTaskAclAwareRepository:
autowire: true
autoconfigure: true
Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface: '@Chill\TaskBundle\Repository\SingleTaskAclAwareRepository'
Write the basic implementation for re-use: separate authorization logic and search logic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The logic of such repository may be separated into two logic:
* the authorization logic (show only entities that the user is allowed to see);
* the search logic (filter entities on some criterias).
This logic should be separated into your implementation.
Considering this simple interface:
.. code-block:: php
interface MyEntityACLAwareRepositoryInterface {
public function countByAuthorized(array $criterias): int;
public function findByAuthorized(array $criteria, int $start, int $limit, array $orderBy): array;
}
The base implementation should separate the logic to allow an easy reuse. Here, the method ``buildQuery`` build a basic query without authorization logic, which can be re-used. The authorization logic is dedicated to a private method. For ease of user, the logic of adding ordering criterias and pagination parameters (``$start`` and ``$limit``) are also delegated to a public method.
.. code-block:: php
namespace Chill\MyBundle\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
final class MyEntityACLAwareRepository implements MyEntityACLAwareRepositoryInterface {
private EntityManagerInterface $em;
// constructor omitted
public function countByAuthorized(array $criterias): int
{
$qb = $this->buildQuery($criterias);
return $this->addAuthorizations($qb)->select("COUNT(e)")->getQuery()->getResult()->getSingleScalarResult();
}
public function findByAuthorized(array $criteria, int $start, int $limit, array $orderBy): array
{
$qb = $this->buildQuery($criterias);
return $this->getResult($this->addAuthorizations($qb), $start, $limit, $orderBy);
}
public function getResult(QueryBuilder $qb, int $start, int $limit, array $orderBy): array
{
$qb
->setFirstResult($start)
->setMaxResults($limit)
;
// add order by logic
return $qb->getQuery()->getResult();
}
public function buildQuery(array $criterias): QueryBuilder
{
$qb = $this->em->createQueryBuilder();
// implement you logic with search criteria here
return $qb;
}
private function addAuthorizations(QueryBuilder $qb): QueryBuilder
{
// add authorization logic here
return $qb;
}
}
Once this logic is executed, it becomes easy to make a new implementation of the repository:
.. code-block:: php
namespace Chill\MyOtherBundle\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Chill\MyBundle\Repository\MyEntityACLAwareRepository
final class AnotherEntityACLAwareRepository implements MyEntityACLAwareRepositoryInterface {
private EntityManagerInterface $em;
private \Chill\MyBundle\Repository\MyEntityACLAwareRepository $initial;
public function __construct(
EntityManagerInterface $em,
\Chill\MyBundle\Repository\MyEntityACLAwareRepository $initial
) {
$this->em = $em;
$this->initial = $initial;
}
public function countByAuthorized(array $criterias): int
{
$qb = $this->initial->buildQuery($criterias);
return $this->addAuthorizations($qb)->select("COUNT(e)")->getQuery()->getResult()->getSingleScalarResult();
}
public function findByAuthorized(array $criteria, int $start, int $limit, array $orderBy): array
{
$qb = $this->initial->buildQuery($criterias);
return $this->initial->getResult($this->addAuthorizations($qb), $start, $limit, $orderBy);
}
private function addAuthorizations(QueryBuilder $qb): QueryBuilder
{
// add a different authorization logic here
return $qb;
}
}
Then, register this service and decorates the old one:
.. code-block:: yaml
services:
Chill\MyOtherBundle\Repository\AnotherEntityACLAwareRepository:
autowire: true
autoconfigure: true
decorates: Chill\MyBundle\Repository\MyEntityACLAwareRepositoryInterface:

View File

@ -1,42 +1,49 @@
<?php
/**
* 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\MyBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class ItemController extends Controller {
public function yourAction()
class example extends Controller
{
public function yourAction()
{
$em = $this->getDoctrine()->getManager();
// first, get the number of total item are available
$total = $em
->createQuery("SELECT COUNT (item.id) FROM ChillMyBundle:Item item")
->getSingleScalarResult();
->createQuery('SELECT COUNT (item.id) FROM ChillMyBundle:Item item')
->getSingleScalarResult();
// get the PaginatorFactory
$paginatorFactory = $this->get('chill_main.paginator_factory');
// create a pagination instance. This instance is only valid for
// create a pagination instance. This instance is only valid for
// the current route and parameters
$paginator = $paginatorFactory->create($total);
// launch your query on item. Limit the query to the results
// for the current page using the paginator
$items = $em->createQuery("SELECT item FROM ChillMyBundle:Item item WHERE <your clause>")
$items = $em->createQuery('SELECT item FROM ChillMyBundle:Item item WHERE <your clause>')
// use the paginator to get the first item number
->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
// use the paginator to get the number of items to display
->setMaxResults($paginator->getItemsPerPage());
return $this->render('ChillMyBundle:Item:list.html.twig', array(
return $this->render(
'ChillMyBundle:Item:list.html.twig',
[
'items' => $items,
'paginator' => $paginator
);
'paginator' => $paginator,
]
);
}
}

View File

@ -1,43 +1,53 @@
<?php
/**
* 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\HealthBundle\Controller;
use Chill\HealthBundle\Security\Authorization\ConsultationVoter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Role\Role;
class ConsultationController extends Controller
{
/**
*
* @param int $id personId
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws type
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function listAction($id)
{
/* @var $person \Chill\PersonBundle\Entity\Person */
/** @var \Chill\PersonBundle\Entity\Person $person */
$person = $this->get('chill.person.repository.person')
->find($id);
if ($person === null) {
throw $this->createNotFoundException("The person is not found");
if (null === $person) {
throw $this->createNotFoundException('The person is not found');
}
$this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
/* @var $authorizationHelper \Chill\MainBundle\Security\Authorization\AuthorizationHelper */
/** @var \Chill\MainBundle\Security\Authorization\AuthorizationHelper $authorizationHelper */
$authorizationHelper = $this->get('chill.main.security.'
. 'authorization.helper');
$circles = $authorizationHelper->getReachableCircles(
$this->getUser(),
new Role(ConsultationVoter::SEE),
$this->getUser(),
new Role(ConsultationVoter::SEE),
$person->getCenter()
);
// create a query which take circles into account
);
// create a query which take circles into account
$consultations = $this->getDoctrine()->getManager()
->createQuery('SELECT c FROM ChillHealthBundle:Consultation c '
. 'WHERE c.patient = :person AND c.circle IN(:circles) '
@ -45,11 +55,10 @@ class ConsultationController extends Controller
->setParameter('person', $person)
->setParameter('circles', $circles)
->getResult();
return $this->render('ChillHealthBundle:Consultation:list.html.twig', array(
'person' => $person,
'consultations' => $consultations
));
return $this->render('ChillHealthBundle:Consultation:list.html.twig', [
'person' => $person,
'consultations' => $consultations,
]);
}
}

View File

@ -1,64 +1,63 @@
<?php
# Chill\MainBundle\DependencyInjection\Configuration.php
namespace Chill\MainBundle\DependencyInjection;
/**
* 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;
use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Configure the main bundle
* Configure the main bundle.
*/
class Configuration implements ConfigurationInterface
class ChillMainConfiguration implements ConfigurationInterface
{
use AddWidgetConfigurationTrait;
/**
*
* @var ContainerBuilder
*/
* @var ContainerBuilder
*/
private $containerBuilder;
public function __construct(array $widgetFactories = array(),
ContainerBuilder $containerBuilder)
{
public function __construct(
array $widgetFactories,
ContainerBuilder $containerBuilder
) {
// we register here widget factories (see below)
$this->setWidgetFactories($widgetFactories);
$this->setWidgetFactories($widgetFactories);
// we will need the container builder later...
$this->containerBuilder = $containerBuilder;
$this->containerBuilder = $containerBuilder;
}
/**
* {@inheritDoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('chill_main');
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('chill_main');
$rootNode
->children()
$rootNode
->children()
// ...
->arrayNode('widgets')
->canBeDisabled()
->children()
->arrayNode('widgets')
->canBeDisabled()
->children()
// we declare here all configuration for homepage place
->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder))
->end() // end of widgets/children
->end() // end of widgets
->end() // end of root/children
->end() // end of root
;
->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder))
->end() // end of widgets/children
->end() // end of widgets
->end() // end of root/children
->end() // end of root
;
return $treeBuilder;
return $treeBuilder;
}
}

View File

@ -1,16 +1,19 @@
<?php
#Chill\MainBundle\DependencyInjection\ChillMainExtension.php
/**
* 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;
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 Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* This class load config for chillMainExtension.
@ -18,42 +21,42 @@ use Chill\MainBundle\DependencyInjection\Configuration;
class ChillMainExtension extends Extension implements Widget\HasWidgetFactoriesExtensionInterface
{
/**
* widget factory
*
* widget factory.
*
* @var WidgetFactoryInterface[]
*/
protected $widgetFactories = array();
protected $widgetFactories = [];
public function addWidgetFactory(WidgetFactoryInterface $factory)
{
$this->widgetFactories[] = $factory;
}
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new Configuration($this->widgetFactories, $container);
}
/**
*
* @return WidgetFactoryInterface[]
*/
public function getWidgetFactories()
{
return $this->widgetFactories;
}
public function load(array $configs, ContainerBuilder $container)
{
// configuration for main bundle
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
// add the key 'widget' without the key 'enable'
$container->setParameter('chill_main.widgets',
array('homepage' => $config['widgets']['homepage']));
// ...
// add the key 'widget' without the key 'enable'
$container->setParameter(
'chill_main.widgets',
['homepage' => $config['widgets']['homepage']]
);
// ...
}
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new Configuration($this->widgetFactories, $container);
}
}

View File

@ -1,69 +1,73 @@
<?php
# Chill/PersonBundle/Widget/PersonListWidgetFactory
/**
* 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\Widget;
use Chill\MainBundle\DependencyInjection\Widget\Factory\AbstractWidgetFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* add configuration for the person_list widget.
*/
class PersonListWidgetFactory extends AbstractWidgetFactory
class ChillPersonAddAPersonListWidgetFactory extends AbstractWidgetFactory
{
/*
* append the option to the configuration
* see http://symfony.com/doc/current/components/config/definition.html
*
*/
/*
* append the option to the configuration
* see http://symfony.com/doc/current/components/config/definition.html
*
*/
public function configureOptions($place, NodeBuilder $node)
{
$node->booleanNode('only_active')
->defaultTrue()
->end();
->defaultTrue()
->end();
$node->integerNode('number_of_items')
->defaultValue(50)
->end();
$node->scalarNode('filtering_class')
->defaultNull()
->end();
->defaultNull()
->end();
}
/*
* return an array with the allowed places where the widget can be rendered
*
* @return string[]
*/
/**
* return an array with the allowed places where the widget can be rendered.
*
* @return string[]
*/
public function getAllowedPlaces()
{
return array('homepage');
return ['homepage'];
}
/*
* return the widget alias
*
* @return string
*/
public function getWidgetAlias()
{
return 'person_list';
}
/*
* return the service id for the service which will render the widget.
*
* this service must implements `Chill\MainBundle\Templating\Widget\WidgetInterface`
*
* the service must exists in the container, and it is not required that the service
*
* the service must exists in the container, and it is not required that the service
* has the `chill_main` tag.
*/
public function getServiceId(ContainerBuilder $containerBuilder, $place, $order, array $config)
{
return 'chill_person.widget.person_list';
}
}
/**
* return the widget alias.
*
* @return string
*/
public function getWidgetAlias()
{
return 'person_list';
}
}

View File

@ -1,66 +1,73 @@
<?php
# Chill/PersonBundle/Widget/PersonListWidget.php
/**
* 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\Widget;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\Widget\WidgetInterface;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTime;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr;
use Doctrine\DBAL\Types\Type;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Symfony\Component\Security\Core\Role\Role;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\User\UserInterface;
use Twig_Environment;
/**
* add a widget with person list.
*
* add a widget with person list.
*
* The configuration is defined by `PersonListWidgetFactory`
*/
class PersonListWidget implements WidgetInterface
class ChillPersonAddAPersonWidget implements WidgetInterface
{
/**
* Repository for persons
*
* @var EntityRepository
* the authorization helper.
*
* @var AuthorizationHelper;
*/
protected $personRepository;
protected $authorizationHelper;
/**
* The entity manager
* The entity manager.
*
* @var EntityManager
*/
protected $entityManager;
/**
* the authorization helper
*
* @var AuthorizationHelper;
*/
protected $authorizationHelper;
/**
* Repository for persons.
*
* @var EntityRepository
*/
protected $personRepository;
/**
* @var TokenStorage
*/
protected $tokenStorage;
/**
*
* @var UserInterface
*/
protected $user;
public function __construct(
EntityRepository $personRepostory,
EntityManager $em,
AuthorizationHelper $authorizationHelper,
TokenStorage $tokenStorage
) {
EntityRepository $personRepostory,
EntityManager $em,
AuthorizationHelper $authorizationHelper,
TokenStorage $tokenStorage
) {
$this->personRepository = $personRepostory;
$this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage;
@ -68,27 +75,24 @@ class PersonListWidget implements WidgetInterface
}
/**
*
* @param type $place
* @param array $context
* @param array $config
*
* @return string
*/
public function render(\Twig_Environment $env, $place, array $context, array $config)
{
public function render(Twig_Environment $env, $place, array $context, array $config)
{
$qb = $this->personRepository
->createQueryBuilder('person');
->createQueryBuilder('person');
// show only the person from the authorized centers
$and = $qb->expr()->andX();
$centers = $this->authorizationHelper
->getReachableCenters($this->getUser(), new Role(PersonVoter::SEE));
->getReachableCenters($this->getUser(), new Role(PersonVoter::SEE));
$and->add($qb->expr()->in('person.center', ':centers'));
$qb->setParameter('centers', $centers);
// add the "only active" where clause
if ($config['only_active'] === true) {
if (true === $config['only_active']) {
$qb->join('person.accompanyingPeriods', 'ap');
$or = new Expr\Orx();
// add the case where closingDate IS NULL
@ -98,32 +102,30 @@ class PersonListWidget implements WidgetInterface
$or->add($andWhenClosingDateIsNull);
// add the case when now is between opening date and closing date
$or->add(
(new Expr())->between(':now', 'ap.openingDate', 'ap.closingDate')
);
(new Expr())->between(':now', 'ap.openingDate', 'ap.closingDate')
);
$and->add($or);
$qb->setParameter('now', new \DateTime(), Type::DATE);
$qb->setParameter('now', new DateTime(), Type::DATE);
}
// adding the where clause to the query
$qb->where($and);
$qb->setFirstResult(0)->setMaxResults($config['number_of_items']);
$persons = $qb->getQuery()->getResult();
return $env->render(
'ChillPersonBundle:Widget:homepage_person_list.html.twig',
array('persons' => $persons)
);
['persons' => $persons]
);
}
/**
*
* @return UserInterface
*/
private function getUser()
{
// return a user
}
}

View File

@ -1,51 +1,48 @@
<?php
# Chill/PersonBundle/DependencyInjection/ChillPersonExtension.php
/**
* 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;
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 Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* This is the class that loads and manages your bundle configuration
* This is the class that loads and manages your bundle configuration.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class ChillPersonExtension extends Extension implements PrependExtensionInterface
{
/**
* {@inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
// ...
// ...
}
/**
*
* Add a widget "add a person" on the homepage, automatically
*
* Add a widget "add a person" on the homepage, automatically.
*
* @param \Chill\PersonBundle\DependencyInjection\containerBuilder $container
*/
public function prepend(ContainerBuilder $container)
public function prepend(ContainerBuilder $container)
{
$container->prependExtensionConfig('chill_main', array(
'widgets' => array(
'homepage' => array(
array(
$container->prependExtensionConfig('chill_main', [
'widgets' => [
'homepage' => [
[
'widget_alias' => 'add_person',
'order' => 2
)
)
)
));
'order' => 2,
],
],
],
]);
}
}

14
grumphp.yml Normal file
View File

@ -0,0 +1,14 @@
imports:
- {
resource: tests/app/vendor/drupol/php-conventions/config/php73/grumphp.yml,
}
parameters:
tasks.phpcsfixer.config: .php_cs.dist.php
tasks.license.name: AGPL-3.0
tasks.license.holder: Champs-Libres
tasks.license.date_from: 2001
tasks.phpcsfixer.allow_risky: true
tasks.phpcsfixer.diff: true
tasks.phpstan.level: 1

101
phpstan-baseline.neon Normal file
View File

@ -0,0 +1,101 @@
parameters:
ignoreErrors:
-
message: "#^Variable property access on \\$this\\(Chill\\\\ActivityBundle\\\\Entity\\\\ActivityType\\)\\.$#"
count: 3
path: src/Bundle/ChillActivityBundle/Entity/ActivityType.php
-
message: "#^Foreach overwrites \\$key with its key variable\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php
-
message: "#^Variable \\$participation might not be defined\\.$#"
count: 3
path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php
-
message: "#^Foreach overwrites \\$value with its value variable\\.$#"
count: 1
path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php
-
message: "#^Variable method call on object\\.$#"
count: 2
path: src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php
-
message: "#^Cannot unset offset '_token' on array\\{formatter\\: mixed, export\\: mixed, centers\\: mixed, alias\\: string\\}\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Controller/ExportController.php
-
message: "#^Foreach overwrites \\$line with its value variable\\.$#"
count: 2
path: src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php
-
message: "#^Foreach overwrites \\$key with its key variable\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php
-
message: "#^Foreach overwrites \\$key with its value variable\\.$#"
count: 3
path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php
-
message: "#^Foreach overwrites \\$value with its value variable\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php
-
message: "#^Variable \\$message on left side of \\?\\? always exists and is not nullable\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Templating/ChillTwigHelper.php
-
message: "#^Variable \\$sqls on left side of \\?\\? always exists and is not nullable\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php
-
message: "#^Class Chill\\\\PersonBundle\\\\Entity\\\\Person constructor invoked with 1 parameter, 0 required\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php
-
message: "#^Variable \\$street1Value might not be defined\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php
-
message: "#^Variable method call on mixed\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php
-
message: "#^Foreach overwrites \\$value with its value variable\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php
-
message: "#^Foreach overwrites \\$action with its value variable\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Foreach overwrites \\$action with its value variable\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php
-
message: "#^Foreach overwrites \\$action with its value variable\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php
-
message: "#^Foreach overwrites \\$value with its value variable\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Form/ChoiceLoader/ThirdPartyChoiceLoader.php

126
phpstan-critical.neon Normal file
View File

@ -0,0 +1,126 @@
parameters:
ignoreErrors:
-
message: "#^Implicit array creation is not allowed \\- variable \\$centers might not exist\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
-
message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$currentHouseholdParticipationAt\\.$#"
count: 3
path: src/Bundle/ChillPersonBundle/Entity/Person.php
-
message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$relation\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Entity/Household/PersonHouseholdAddress.php
-
message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\AccompanyingPeriod\\:\\:\\$work\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
-
message: "#^Undefined variable\\: \\$person$#"
count: 1
path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php
-
message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Household\\\\MembersEditorFactory\\:\\:\\$validator\\.$#"
count: 2
path: src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php
-
message: "#^Parameter \\$action of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:buildQueryBySocialActionWithDescendants\\(\\) has invalid type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\SocialAction\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Parameter \\$action of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:countBySocialActionWithDescendants\\(\\) has invalid type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\SocialAction\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Undefined variable\\: \\$action$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Undefined variable\\: \\$limit$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Undefined variable\\: \\$offset$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Undefined variable\\: \\$orderBy$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Variable variables are not allowed\\.$#"
count: 4
path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php
-
message: "#^Function Chill\\\\PersonBundle\\\\Serializer\\\\Normalizer\\\\·\\\\is_array not found\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php
-
message: "#^Undefined variable\\: \\$choiceSlug$#"
count: 1
path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php
-
message: "#^Undefined variable\\: \\$choiceSlug$#"
count: 1
path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php
-
message: "#^Undefined variable\\: \\$type$#"
count: 1
path: src/Bundle/ChillTaskBundle/Controller/TaskController.php
-
message: "#^Call to an undefined method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\AbstractCRUDController\\:\\:getRoleFor\\(\\)\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php
-
message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Controller\\\\UserController\\:\\:createEditForm\\(\\)\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Controller/UserController.php
-
message: "#^Undefined variable\\: \\$current$#"
count: 1
path: src/Bundle/ChillMainBundle/Pagination/PageGenerator.php
-
message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AbstractChillVoter\\:\\:getSupportedAttributes\\(\\)\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php
-
message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AbstractChillVoter\\:\\:getSupportedClasses\\(\\)\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php
-
message: "#^Call to an undefined method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AbstractChillVoter\\:\\:isGranted\\(\\)\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Security/Authorization/AbstractChillVoter.php
-
message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Controller\\\\PersonController\\:\\:\\$security\\.$#"
count: 3
path: src/Bundle/ChillPersonBundle/Controller/PersonController.php
-
message: "#^Call to an undefined method Chill\\\\ThirdPartyBundle\\\\Form\\\\Type\\\\PickThirdPartyTypeCategoryType\\:\\:transform\\(\\)\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php

1592
phpstan-deprecations.neon Normal file

File diff suppressed because it is too large Load Diff

497
phpstan-types.neon Normal file
View File

@ -0,0 +1,497 @@
parameters:
ignoreErrors:
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillActivityBundle/Entity/ActivityReason.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php
-
message: "#^Method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\StatActivityDuration\\:\\:getDescription\\(\\) should return string but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php
-
message: "#^Method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\StatActivityDuration\\:\\:getTitle\\(\\) should return string but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in &&, mixed given on the right side\\.$#"
count: 3
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 3
path: src/Bundle/ChillBudgetBundle/Form/ChargeType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2
path: src/Bundle/ChillBudgetBundle/Form/ResourceType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Command/CreateFieldsOnGroupCommand.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/AbstractCustomField.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 4
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php
-
message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldChoice\\:\\:buildForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldChoice.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php
-
message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldDate\\:\\:buildForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldDate.php
-
message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldLongChoice\\:\\:buildForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldLongChoice.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php
-
message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldNumber\\:\\:buildForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldNumber.php
-
message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldText\\:\\:buildForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldText.php
-
message: "#^Method Chill\\\\CustomFieldsBundle\\\\CustomFields\\\\CustomFieldTitle\\:\\:buildForm\\(\\) should return Symfony\\\\Component\\\\Form\\\\FormTypeInterface but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/CustomFields/CustomFieldTitle.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldsGroup.php
-
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldType.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php
-
message: "#^Method Chill\\\\EventBundle\\\\Entity\\\\Participation\\:\\:offsetGet\\(\\) should return mixed but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillEventBundle/Entity/Participation.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php
-
message: "#^Parameter \\$resolver of method Chill\\\\EventBundle\\\\Form\\\\EventTypeType\\:\\:setDefaultOptions\\(\\) has invalid type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolverInterface\\.$#"
count: 1
path: src/Bundle/ChillEventBundle/Form/EventTypeType.php
-
message: "#^Parameter \\$resolver of method Chill\\\\EventBundle\\\\Form\\\\RoleType\\:\\:setDefaultOptions\\(\\) has invalid type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolverInterface\\.$#"
count: 1
path: src/Bundle/ChillEventBundle/Form/RoleType.php
-
message: "#^Parameter \\$resolver of method Chill\\\\EventBundle\\\\Form\\\\StatusType\\:\\:setDefaultOptions\\(\\) has invalid type Symfony\\\\Component\\\\OptionsResolver\\\\OptionsResolverInterface\\.$#"
count: 1
path: src/Bundle/ChillEventBundle/Form/StatusType.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
path: src/Bundle/ChillEventBundle/Search/EventSearch.php
-
message: "#^Method Chill\\\\EventBundle\\\\Search\\\\EventSearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillEventBundle/Search/EventSearch.php
-
message: "#^Casting to string something that's already string\\.$#"
count: 5
path: src/Bundle/ChillFamilyMembersBundle/Entity/AbstractFamilyMember.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2
path: src/Bundle/ChillFamilyMembersBundle/Form/FamilyMemberType.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php
-
message: "#^Parameter \\$scope of method Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\CRUDController\\:\\:getReachableCenters\\(\\) has invalid type Chill\\\\MainBundle\\\\CRUD\\\\Controller\\\\Scope\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/CRUD/Resolver/Resolver.php
-
message: "#^Method Chill\\\\MainBundle\\\\CRUD\\\\Resolver\\\\Resolver\\:\\:getConfigValue\\(\\) should return string but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/CRUD/Resolver/Resolver.php
-
message: "#^Method Chill\\\\MainBundle\\\\Command\\\\ChillImportUsersCommand\\:\\:execute\\(\\) should return int but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Command/ChillImportUsersCommand.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Command/ChillUserSendRenewPasswordCodeCommand.php
-
message: "#^Method Chill\\\\MainBundle\\\\Command\\\\ChillUserSendRenewPasswordCodeCommand\\:\\:execute\\(\\) should return int but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Command/ChillUserSendRenewPasswordCodeCommand.php
-
message: "#^Method Chill\\\\MainBundle\\\\Command\\\\LoadAndUpdateLanguagesCommand\\:\\:execute\\(\\) should return int but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php
-
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
count: 2
path: src/Bundle/ChillMainBundle/Command/LoadAndUpdateLanguagesCommand.php
-
message: "#^Method Chill\\\\MainBundle\\\\Command\\\\LoadCountriesCommand\\:\\:execute\\(\\) should return int but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Command/LoadCountriesCommand.php
-
message: "#^Method Chill\\\\MainBundle\\\\Command\\\\LoadPostalCodesCommand\\:\\:execute\\(\\) should return int but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Command/LoadPostalCodesCommand.php
-
message: "#^Method Chill\\\\MainBundle\\\\Command\\\\SetPasswordCommand\\:\\:execute\\(\\) should return int but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Command/SetPasswordCommand.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Controller/PasswordController.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Controller/PostalCodeController.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Entity/Address.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Entity/Embeddable/CommentEmbeddable.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Entity/User.php
-
message: "#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Form/DataMapper/AddressDataMapper.php
-
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
count: 2
path: src/Bundle/ChillMainBundle/Form/Type/AddressType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2
path: src/Bundle/ChillMainBundle/Form/Type/AddressType.php
-
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Form/Type/ChillTextareaType.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 3
path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/DateIntervalTransformer.php
-
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
path: src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Security/PasswordRecover/PasswordRecoverEvent.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Security/PasswordRecover/TokenManager.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 3
path: src/Bundle/ChillMainBundle/Templating/Entity/AddressRender.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php
-
message: "#^Method Chill\\\\MainBundle\\\\Timeline\\\\TimelineBuilder\\:\\:getTemplateData\\(\\) should return array but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Actions/Remove/PersonMove.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/CRUD/Controller/OneToOneEntityPersonCRUDController.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php
-
message: "#^Method Chill\\\\PersonBundle\\\\Command\\\\ChillPersonMoveCommand\\:\\:execute\\(\\) should return int but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Command/ChillPersonMoveCommand.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 6
path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
path: src/Bundle/ChillPersonBundle/Entity/PersonPhone.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Export/Aggregator/CountryOfBirthAggregator.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Export/Aggregator/NationalityAggregator.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php
-
message: "#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php
-
message: "#^Method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:buildQueryBySocialActionWithDescendants\\(\\) has invalid return type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\QueryBuilder\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 3
path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php
-
message: "#^Method Chill\\\\PersonBundle\\\\Search\\\\PersonSearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
path: src/Bundle/ChillPersonBundle/Templating/Entity/ClosingMotiveRender.php
-
message: "#^Method Chill\\\\ReportBundle\\\\DataFixtures\\\\ORM\\\\LoadReports\\:\\:getRandomChoice\\(\\) should return array\\<string\\>\\|string but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillReportBundle/DataFixtures/ORM/LoadReports.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php
-
message: "#^Method Chill\\\\ReportBundle\\\\Security\\\\Authorization\\\\ReportVoter\\:\\:supports\\(\\) should return bool but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillReportBundle/Security/Authorization/ReportVoter.php
-
message: "#^Casting to string something that's already string\\.$#"
count: 3
path: src/Bundle/ChillTaskBundle/Entity/AbstractTask.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2
path: src/Bundle/ChillTaskBundle/Form/SingleTaskListType.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 5
path: src/Bundle/ChillTaskBundle/Repository/SingleTaskRepository.php
-
message: "#^Method Chill\\\\TaskBundle\\\\Timeline\\\\SingleTaskTaskLifeCycleEventTimelineProvider\\:\\:getTransitionByName\\(\\) should return Symfony\\\\Component\\\\Workflow\\\\Transition but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php
-
message: "#^Method Chill\\\\TaskBundle\\\\Timeline\\\\TaskLifeCycleEventTimelineProvider\\:\\:getTransitionByName\\(\\) should return Symfony\\\\Component\\\\Workflow\\\\Transition but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Form/ChoiceLoader/ThirdPartyChoiceLoader.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php
-
message: "#^Method Chill\\\\ThirdPartyBundle\\\\Search\\\\ThirdPartySearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartySearch.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php

26
phpstan.neon.dist Normal file
View File

@ -0,0 +1,26 @@
parameters:
level: 1
paths:
- src/
excludePaths:
- docs/
- src/Bundle/*/Tests/*
- src/Bundle/*/tests/*
- src/Bundle/*/Test/*
- src/Bundle/*/config/*
- src/Bundle/*/migrations/*
- src/Bundle/*/translations/*
- src/Bundle/*/Resources/*
- src/Bundle/*/src/Tests/*
- src/Bundle/*/src/Test/*
- src/Bundle/*/src/config/*
- src/Bundle/*/src/migrations/*
- src/Bundle/*/src/translations/*
- src/Bundle/*/src/Resources/*
includes:
- phpstan-baseline.neon
- phpstan-critical.neon
- phpstan-deprecations.neon
- phpstan-types.neon

View File

@ -37,6 +37,9 @@
<testsuite name="CalendarBundle">
<directory suffix="Test.php">src/Bundle/ChillCalendarBundle/Tests/</directory>
</testsuite>
<testsuite name="DocGeneratorBundle">
<directory suffix="Test.php">src/Bundle/ChillDocGeneratorBundle/tests/</directory>
</testsuite>
</testsuites>
<listeners>

5
resource/header.txt Normal file
View File

@ -0,0 +1,5 @@
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.

View File

@ -1,5 +1,14 @@
<?php
/**
* 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\ActivityBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;

View File

@ -1,85 +1,252 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Controller;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepository;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Form\ActivityType;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Repository\ActivityTypeCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Role\Role;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Form\ActivityType;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Symfony\Component\Serializer\SerializerInterface;
use function array_key_exists;
/**
* Class ActivityController
*
* @package Chill\ActivityBundle\Controller
*/
class ActivityController extends AbstractController
final class ActivityController extends AbstractController
{
protected EventDispatcherInterface $eventDispatcher;
private AccompanyingPeriodRepository $accompanyingPeriodRepository;
protected AuthorizationHelper $authorizationHelper;
private ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
protected LoggerInterface $logger;
private ActivityRepository $activityRepository;
protected SerializerInterface $serializer;
private ActivityTypeCategoryRepository $activityTypeCategoryRepository;
protected ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
private ActivityTypeRepository $activityTypeRepository;
private EntityManagerInterface $entityManager;
private EventDispatcherInterface $eventDispatcher;
private LocationRepository $locationRepository;
private LoggerInterface $logger;
private PersonRepository $personRepository;
private SerializerInterface $serializer;
private ThirdPartyRepository $thirdPartyRepository;
public function __construct(
ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
ActivityTypeRepository $activityTypeRepository,
ActivityTypeCategoryRepository $activityTypeCategoryRepository,
PersonRepository $personRepository,
ThirdPartyRepository $thirdPartyRepository,
LocationRepository $locationRepository,
ActivityRepository $activityRepository,
AccompanyingPeriodRepository $accompanyingPeriodRepository,
EntityManagerInterface $entityManager,
EventDispatcherInterface $eventDispatcher,
AuthorizationHelper $authorizationHelper,
LoggerInterface $logger,
SerializerInterface $serializer
) {
$this->activityACLAwareRepository = $activityACLAwareRepository;
$this->activityTypeRepository = $activityTypeRepository;
$this->activityTypeCategoryRepository = $activityTypeCategoryRepository;
$this->personRepository = $personRepository;
$this->thirdPartyRepository = $thirdPartyRepository;
$this->locationRepository = $locationRepository;
$this->activityRepository = $activityRepository;
$this->accompanyingPeriodRepository = $accompanyingPeriodRepository;
$this->entityManager = $entityManager;
$this->eventDispatcher = $eventDispatcher;
$this->authorizationHelper = $authorizationHelper;
$this->logger = $logger;
$this->serializer = $serializer;
}
/**
* Deletes a Activity entity.
*
* @param mixed $id
*/
public function deleteAction(Request $request, $id)
{
$view = null;
[$person, $accompanyingPeriod] = $this->getEntity($request);
$activity = $this->activityRepository->find($id);
if (!$activity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
}
if ($activity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:confirm_deleteAccompanyingCourse.html.twig';
$accompanyingPeriod = $activity->getAccompanyingPeriod();
} else {
$view = 'ChillActivityBundle:Activity:confirm_deletePerson.html.twig';
}
// TODO
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_DELETE', $activity);
$form = $this->createDeleteForm($activity->getId(), $person, $accompanyingPeriod);
if ($request->getMethod() === Request::METHOD_DELETE) {
$form->handleRequest($request);
if ($form->isValid()) {
$this->logger->notice('An activity has been removed', [
'by_user' => $this->getUser()->getUsername(),
'activity_id' => $activity->getId(),
'person_id' => $activity->getPerson() ? $activity->getPerson()->getId() : null,
'comment' => $activity->getComment()->getComment(),
'scope_id' => $activity->getScope() ? $activity->getScope()->getId() : null,
'reasons_ids' => $activity->getReasons()
->map(
static fn (ActivityReason $ar): int => $ar->getId()
)
->toArray(),
'type_id' => $activity->getActivityType()->getId(),
'duration' => $activity->getDurationTime() ? $activity->getDurationTime()->format('U') : null,
'date' => $activity->getDate()->format('Y-m-d'),
'attendee' => $activity->getAttendee(),
]);
$this->entityManager->remove($activity);
$this->entityManager->flush();
$this->addFlash('success', $this->get('translator')
->trans('The activity has been successfully removed.'));
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
return $this->redirectToRoute('chill_activity_activity_list', $params);
}
}
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, [
'activity' => $activity,
'delete_form' => $form->createView(),
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
]);
}
/**
* Displays a form to edit an existing Activity entity.
*/
public function editAction(int $id, Request $request): Response
{
$view = null;
[$person, $accompanyingPeriod] = $this->getEntity($request);
$entity = $this->activityRepository->find($id);
if (null === $entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
}
if ($entity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:editAccompanyingCourse.html.twig';
$accompanyingPeriod = $entity->getAccompanyingPeriod();
} else {
$view = 'ChillActivityBundle:Activity:editPerson.html.twig';
}
// TODO
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),
'role' => new Role('CHILL_ACTIVITY_UPDATE'),
'activityType' => $entity->getActivityType(),
'accompanyingPeriod' => $accompanyingPeriod,
])->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($entity);
$this->entityManager->flush();
$this->addFlash('success', $this->get('translator')->trans('Success : activity updated!'));
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
$params['id'] = $entity->getId();
return $this->redirectToRoute('chill_activity_activity_show', $params);
}
$deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);
/*
* TODO
$event = new PrivacyEvent($person, array(
'element_class' => Activity::class,
'element_id' => $entity->getId(),
'action' => 'edit'
));
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
*/
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
$activity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']);
return $this->render($view, [
'entity' => $entity,
'edit_form' => $form->createView(),
'delete_form' => $deleteForm->createView(),
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'activity_json' => $activity_array,
]);
}
/**
* Lists all Activity entities.
*/
public function listAction(Request $request): Response
{
$em = $this->getDoctrine()->getManager();
$view = null;
$activities = [];
// TODO: add pagination
[$person, $accompanyingPeriod] = $this->getEntity($request);
@ -89,10 +256,10 @@ class ActivityController extends AbstractController
$activities = $this->activityACLAwareRepository
->findByPerson($person, ActivityVoter::SEE, 0, null);
$event = new PrivacyEvent($person, array(
$event = new PrivacyEvent($person, [
'element_class' => Activity::class,
'action' => 'list'
));
'action' => 'list',
]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
$view = 'ChillActivityBundle:Activity:listPerson.html.twig';
@ -105,62 +272,19 @@ class ActivityController extends AbstractController
$view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig';
}
return $this->render($view, array(
'activities' => $activities,
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
));
}
public function selectTypeAction(Request $request): Response
{
$em = $this->getDoctrine()->getManager();
$view = null;
[$person, $accompanyingPeriod] = $this->getEntity($request);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:selectTypeAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:selectTypePerson.html.twig';
}
$data = [];
$activityTypeCategories = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityTypeCategory::class)
->findBy(['active' => true], ['ordering' => 'ASC']);
foreach ($activityTypeCategories as $activityTypeCategory) {
$activityTypes = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityType::class)
->findBy(['active' => true, 'category' => $activityTypeCategory], ['ordering' => 'ASC']);
$data[] = [
'activityTypeCategory' => $activityTypeCategory,
'activityTypes' => $activityTypes,
];
}
if ($request->query->has('activityData')) {
$activityData = $request->query->get('activityData');
} else {
$activityData = [];
}
if ($view === null) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, [
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'data' => $data,
'activityData' => $activityData
]);
return $this->render(
$view,
[
'activities' => $activities,
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
]
);
}
public function newAction(Request $request): Response
{
$em = $this->getDoctrine()->getManager();
$view = null;
[$person, $accompanyingPeriod] = $this->getEntity($request);
@ -171,26 +295,26 @@ class ActivityController extends AbstractController
}
$activityType_id = $request->get('activityType_id', 0);
$activityType = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityType::class)
->find($activityType_id);
$activityType = $this->activityTypeRepository->find($activityType_id);
if (isset($activityType) && !$activityType->isActive()) {
throw new \InvalidArgumentException('Activity type must be active');
throw new InvalidArgumentException('Activity type must be active');
}
$activityData = null;
if ($request->query->has('activityData')) {
$activityData = $request->query->get('activityData');
}
if (!$activityType instanceof \Chill\ActivityBundle\Entity\ActivityType ||
!$activityType->isActive()) {
if (!$activityType instanceof \Chill\ActivityBundle\Entity\ActivityType
|| !$activityType->isActive()) {
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
if (null !== $activityData) {
$params['activityData'] = $activityData;
}
return $this->redirectToRoute('chill_activity_activity_select_type', $params);
}
@ -205,8 +329,8 @@ class ActivityController extends AbstractController
$entity->setAccompanyingPeriod($accompanyingPeriod);
}
$entity->setType($activityType);
$entity->setDate(new \DateTime('now'));
$entity->setActivityType($activityType);
$entity->setDate(new DateTime('now'));
if ($request->query->has('activityData')) {
$activityData = $request->query->get('activityData');
@ -215,61 +339,61 @@ class ActivityController extends AbstractController
$durationTimeInMinutes = $activityData['durationTime'];
$hours = floor($durationTimeInMinutes / 60);
$minutes = $durationTimeInMinutes % 60;
$duration = \DateTime::createFromFormat("H:i", $hours.':'.$minutes);
$duration = DateTime::createFromFormat('H:i', $hours . ':' . $minutes);
if ($duration) {
$entity->setDurationTime($duration);
}
}
if (array_key_exists('date', $activityData)) {
$date = \DateTime::createFromFormat('Y-m-d', $activityData['date']);
$date = DateTime::createFromFormat('Y-m-d', $activityData['date']);
if ($date) {
$entity->setDate($date);
}
}
if (array_key_exists('personsId', $activityData)) {
foreach($activityData['personsId'] as $personId){
$concernedPerson = $em->getRepository(\Chill\PersonBundle\Entity\Person::class)->find($personId);
foreach ($activityData['personsId'] as $personId) {
$concernedPerson = $this->personRepository->find($personId);
$entity->addPerson($concernedPerson);
}
}
if (array_key_exists('professionalsId', $activityData)) {
foreach($activityData['professionalsId'] as $professionalsId){
$professional = $em->getRepository(\Chill\ThirdPartyBundle\Entity\ThirdParty::class)->find($professionalsId);
foreach ($activityData['professionalsId'] as $professionalsId) {
$professional = $this->thirdPartyRepository->find($professionalsId);
$entity->addThirdParty($professional);
}
}
if (array_key_exists('location', $activityData)) {
$location = $em->getRepository(\Chill\MainBundle\Entity\Location::class)->find($activityData['location']);
$location = $this->locationRepository->find($activityData['location']);
$entity->setLocation($location);
}
if (array_key_exists('comment', $activityData)) {
$comment = new CommentEmbeddable();
$comment->setComment($activityData['comment']);
$comment->setUserId($this->getUser()->getid());
$comment->setDate(new \DateTime('now'));
$comment->setDate(new DateTime('now'));
$entity->setComment($comment);
}
}
// TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_CREATE', $entity);
$this->denyAccessUnlessGranted(ActivityVoter::CREATE, $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),
'role' => new Role('CHILL_ACTIVITY_CREATE'),
'activityType' => $entity->getType(),
'role' => new Role('CHILL_ACTIVITY_CREATE'),
'activityType' => $entity->getActivityType(),
'accompanyingPeriod' => $accompanyingPeriod,
])->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($entity);
$em->flush();
$this->entityManager->persist($entity);
$this->entityManager->flush();
$this->addFlash('success', $this->get('translator')->trans('Success : activity created!'));
@ -280,24 +404,71 @@ class ActivityController extends AbstractController
return $this->redirectToRoute('chill_activity_activity_show', $params);
}
if ($view === null) {
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
$activity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']);
$defaultLocation = $this->getUser()->getCurrentLocation();
return $this->render($view, [
'person' => $person,
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'entity' => $entity,
'form' => $form->createView(),
'activity_json' => $activity_array
'activity_json' => $activity_array,
'default_location' => $defaultLocation,
]);
}
public function showAction(Request $request, $id): Response
public function selectTypeAction(Request $request): Response
{
$em = $this->getDoctrine()->getManager();
$view = null;
[$person, $accompanyingPeriod] = $this->getEntity($request);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:selectTypeAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:selectTypePerson.html.twig';
}
$data = [];
$activityTypeCategories = $this
->activityTypeCategoryRepository
->findBy(['active' => true], ['ordering' => 'ASC']);
foreach ($activityTypeCategories as $activityTypeCategory) {
$activityTypes = $this
->activityTypeRepository
->findBy(
['active' => true, 'category' => $activityTypeCategory],
['ordering' => 'ASC']
);
$data[] = [
'activityTypeCategory' => $activityTypeCategory,
'activityTypes' => $activityTypes,
];
}
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, [
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'data' => $data,
'activityData' => $request->query->get('activityData', []),
]);
}
public function showAction(Request $request, int $id): Response
{
$view = null;
[$person, $accompanyingPeriod] = $this->getEntity($request);
@ -307,21 +478,22 @@ class ActivityController extends AbstractController
$view = 'ChillActivityBundle:Activity:showPerson.html.twig';
}
$entity = $em->getRepository('ChillActivityBundle:Activity')->find($id);
$entity = $this->activityRepository->find($id);
if (!$entity) {
if (null === $entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
}
if (null !== $accompanyingPeriod) {
$entity->personsAssociated = $entity->getPersonsAssociated();
$entity->personsNotAssociated = $entity->getPersonsNotAssociated();
// @TODO: Properties created dynamically.
$entity->personsAssociated = $entity->getPersonsAssociated();
$entity->personsNotAssociated = $entity->getPersonsNotAssociated();
}
// TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_SEE', $entity);
$deleteForm = $this->createDeleteForm($id, $person, $accompanyingPeriod);
$deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);
// TODO
/*
@ -331,166 +503,39 @@ class ActivityController extends AbstractController
'action' => 'show'
));
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
*/
*/
if ($view === null) {
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, array(
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'entity' => $entity,
'delete_form' => $deleteForm->createView(),
));
}
/**
* Displays a form to edit an existing Activity entity.
*
*/
public function editAction($id, Request $request): Response
{
$em = $this->getDoctrine()->getManager();
[$person, $accompanyingPeriod] = $this->getEntity($request);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:editAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:editPerson.html.twig';
}
$entity = $em->getRepository('ChillActivityBundle:Activity')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
}
// TODO
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),
'role' => new Role('CHILL_ACTIVITY_UPDATE'),
'activityType' => $entity->getType(),
'accompanyingPeriod' => $accompanyingPeriod,
])->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($entity);
$em->flush();
$this->addFlash('success', $this->get('translator')->trans('Success : activity updated!'));
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
$params['id'] = $id;
return $this->redirectToRoute('chill_activity_activity_show', $params);
}
$deleteForm = $this->createDeleteForm($id, $person, $accompanyingPeriod);
/*
* TODO
$event = new PrivacyEvent($person, array(
'element_class' => Activity::class,
'element_id' => $entity->getId(),
'action' => 'edit'
));
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
*/
if ($view === null) {
throw $this->createNotFoundException('Template not found');
}
$activity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']);
return $this->render($view, array(
'entity' => $entity,
'edit_form' => $form->createView(),
'delete_form' => $deleteForm->createView(),
return $this->render($view, [
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'activity_json' => $activity_array
));
'entity' => $entity,
'delete_form' => $deleteForm->createView(),
]);
}
/**
* Deletes a Activity entity.
*
*/
public function deleteAction(Request $request, $id)
private function buildParamsToUrl(?Person $person, ?AccompanyingPeriod $accompanyingPeriod): array
{
$em = $this->getDoctrine()->getManager();
$params = [];
[$person, $accompanyingPeriod] = $this->getEntity($request);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:confirm_deleteAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:confirm_deletePerson.html.twig';
if (null !== $person) {
$params['person_id'] = $person->getId();
}
/* @var $activity Activity */
$activity = $em->getRepository('ChillActivityBundle:Activity')->find($id);
if (!$activity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
if (null !== $accompanyingPeriod) {
$params['accompanying_period_id'] = $accompanyingPeriod->getId();
}
// TODO
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_DELETE', $activity);
$form = $this->createDeleteForm($id, $person, $accompanyingPeriod);
if ($request->getMethod() === Request::METHOD_DELETE) {
$form->handleRequest($request);
if ($form->isValid()) {
$this->logger->notice("An activity has been removed", array(
'by_user' => $this->getUser()->getUsername(),
'activity_id' => $activity->getId(),
'person_id' => $activity->getPerson() ? $activity->getPerson()->getId() : null,
'comment' => $activity->getComment()->getComment(),
'scope_id' => $activity->getScope() ? $activity->getScope()->getId() : null,
'reasons_ids' => $activity->getReasons()
->map(function ($ar) { return $ar->getId(); })
->toArray(),
'type_id' => $activity->getType()->getId(),
'duration' => $activity->getDurationTime() ? $activity->getDurationTime()->format('U') : null,
'date' => $activity->getDate()->format('Y-m-d'),
'attendee' => $activity->getAttendee()
));
$em->remove($activity);
$em->flush();
$this->addFlash('success', $this->get('translator')
->trans("The activity has been successfully removed."));
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
return $this->redirectToRoute('chill_activity_activity_list', $params);
}
}
if ($view === null) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, array(
'activity' => $activity,
'delete_form' => $form->createView(),
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
));
return $params;
}
/**
* Creates a form to delete a Activity entity by id.
*/
private function createDeleteForm(int $id, ?Person $person, ?AccompanyingPeriod $accompanyingPeriod): Form
private function createDeleteForm(int $id, ?Person $person, ?AccompanyingPeriod $accompanyingPeriod): FormInterface
{
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
$params['id'] = $id;
@ -498,58 +543,40 @@ class ActivityController extends AbstractController
return $this->createFormBuilder()
->setAction($this->generateUrl('chill_activity_activity_delete', $params))
->setMethod('DELETE')
->add('submit', SubmitType::class, array('label' => 'Delete'))
->getForm()
;
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}
private function getEntity(Request $request): array
{
$em = $this->getDoctrine()->getManager();
$person = $accompanyingPeriod = null;
$person = $accompanyingPeriod = null;
if ($request->query->has('person_id')) {
$person_id = $request->get('person_id');
$person = $em->getRepository(Person::class)->find($person_id);
$person = $this->personRepository->find($person_id);
if ($person === null) {
if (null === $person) {
throw $this->createNotFoundException('Person not found');
}
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
} elseif ($request->query->has('accompanying_period_id')) {
$accompanying_period_id = $request->get('accompanying_period_id');
$accompanyingPeriod = $em->getRepository(AccompanyingPeriod::class)->find($accompanying_period_id);
$accompanyingPeriod = $this->accompanyingPeriodRepository->find($accompanying_period_id);
if ($accompanyingPeriod === null) {
if (null === $accompanyingPeriod) {
throw $this->createNotFoundException('Accompanying Period not found');
}
// TODO Add permission
// $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
} else {
throw $this->createNotFoundException("Person or Accompanying Period not found");
throw $this->createNotFoundException('Person or Accompanying Period not found');
}
return [
$person, $accompanyingPeriod
$person,
$accompanyingPeriod,
];
}
private function buildParamsToUrl(
?Person $person,
?AccompanyingPeriod $accompanyingPeriod
): array {
$params = [];
if ($person) {
$params['person_id'] = $person->getId();
}
if ($accompanyingPeriod) {
$params['accompanying_period_id'] = $accompanyingPeriod->getId();
}
return $params;
}
}

View File

@ -1,38 +1,30 @@
<?php
/**
* 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\ActivityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\ActivityBundle\Entity\ActivityReasonCategory;
use Chill\ActivityBundle\Form\ActivityReasonCategoryType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
/**
* ActivityReasonCategory controller.
*
*/
class ActivityReasonCategoryController extends AbstractController
{
/**
* Lists all ActivityReasonCategory entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('ChillActivityBundle:ActivityReasonCategory')->findAll();
return $this->render('ChillActivityBundle:ActivityReasonCategory:index.html.twig', array(
'entities' => $entities,
));
}
/**
* Creates a new ActivityReasonCategory entity.
*
*/
public function createAction(Request $request)
{
@ -45,71 +37,19 @@ class ActivityReasonCategoryController extends AbstractController
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activityreasoncategory_show', array('id' => $entity->getId())));
return $this->redirect($this->generateUrl('chill_activity_activityreasoncategory_show', ['id' => $entity->getId()]));
}
return $this->render('ChillActivityBundle:ActivityReasonCategory:new.html.twig', array(
return $this->render('ChillActivityBundle:ActivityReasonCategory:new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
));
}
/**
* Creates a form to create a ActivityReasonCategory entity.
*
* @param ActivityReasonCategory $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(ActivityReasonCategory $entity)
{
$form = $this->createForm(ActivityReasonCategoryType::class, $entity, array(
'action' => $this->generateUrl('chill_activity_activityreasoncategory_create'),
'method' => 'POST',
));
$form->add('submit', SubmitType::class, array('label' => 'Create'));
return $form;
}
/**
* Displays a form to create a new ActivityReasonCategory entity.
*
*/
public function newAction()
{
$entity = new ActivityReasonCategory();
$form = $this->createCreateForm($entity);
return $this->render('ChillActivityBundle:ActivityReasonCategory:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
/**
* Finds and displays a ActivityReasonCategory entity.
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ChillActivityBundle:ActivityReasonCategory')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.');
}
return $this->render('ChillActivityBundle:ActivityReasonCategory:show.html.twig', array(
'entity' => $entity,
));
'form' => $form->createView(),
]);
}
/**
* Displays a form to edit an existing ActivityReasonCategory entity.
*
* @param mixed $id
*/
public function editAction($id)
{
@ -123,33 +63,64 @@ class ActivityReasonCategoryController extends AbstractController
$editForm = $this->createEditForm($entity);
return $this->render('ChillActivityBundle:ActivityReasonCategory:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
));
return $this->render('ChillActivityBundle:ActivityReasonCategory:edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
}
/**
* Creates a form to edit a ActivityReasonCategory entity.
*
* @param ActivityReasonCategory $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(ActivityReasonCategory $entity)
* Lists all ActivityReasonCategory entities.
*/
public function indexAction()
{
$form = $this->createForm(ActivityReasonCategoryType::class, $entity, array(
'action' => $this->generateUrl('chill_activity_activityreasoncategory_update', array('id' => $entity->getId())),
'method' => 'PUT',
));
$em = $this->getDoctrine()->getManager();
$form->add('submit', SubmitType::class, array('label' => 'Update'));
$entities = $em->getRepository('ChillActivityBundle:ActivityReasonCategory')->findAll();
return $form;
return $this->render('ChillActivityBundle:ActivityReasonCategory:index.html.twig', [
'entities' => $entities,
]);
}
/**
* Displays a form to create a new ActivityReasonCategory entity.
*/
public function newAction()
{
$entity = new ActivityReasonCategory();
$form = $this->createCreateForm($entity);
return $this->render('ChillActivityBundle:ActivityReasonCategory:new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
]);
}
/**
* Finds and displays a ActivityReasonCategory entity.
*
* @param mixed $id
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ChillActivityBundle:ActivityReasonCategory')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.');
}
return $this->render('ChillActivityBundle:ActivityReasonCategory:show.html.twig', [
'entity' => $entity,
]);
}
/**
* Edits an existing ActivityReasonCategory entity.
*
* @param mixed $id
*/
public function updateAction(Request $request, $id)
{
@ -167,12 +138,50 @@ class ActivityReasonCategoryController extends AbstractController
if ($editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activityreasoncategory_edit', array('id' => $id)));
return $this->redirect($this->generateUrl('chill_activity_activityreasoncategory_edit', ['id' => $id]));
}
return $this->render('ChillActivityBundle:ActivityReasonCategory:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
));
return $this->render('ChillActivityBundle:ActivityReasonCategory:edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
}
/**
* Creates a form to create a ActivityReasonCategory entity.
*
* @param ActivityReasonCategory $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(ActivityReasonCategory $entity)
{
$form = $this->createForm(ActivityReasonCategoryType::class, $entity, [
'action' => $this->generateUrl('chill_activity_activityreasoncategory_create'),
'method' => 'POST',
]);
$form->add('submit', SubmitType::class, ['label' => 'Create']);
return $form;
}
/**
* Creates a form to edit a ActivityReasonCategory entity.
*
* @param ActivityReasonCategory $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(ActivityReasonCategory $entity)
{
$form = $this->createForm(ActivityReasonCategoryType::class, $entity, [
'action' => $this->generateUrl('chill_activity_activityreasoncategory_update', ['id' => $entity->getId()]),
'method' => 'PUT',
]);
$form->add('submit', SubmitType::class, ['label' => 'Update']);
return $form;
}
}

View File

@ -1,38 +1,30 @@
<?php
/**
* 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\ActivityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Form\ActivityReasonType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
/**
* ActivityReason controller.
*
*/
class ActivityReasonController extends AbstractController
{
/**
* Lists all ActivityReason entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('ChillActivityBundle:ActivityReason')->findAll();
return $this->render('ChillActivityBundle:ActivityReason:index.html.twig', array(
'entities' => $entities,
));
}
/**
* Creates a new ActivityReason entity.
*
*/
public function createAction(Request $request)
{
@ -45,71 +37,19 @@ class ActivityReasonController extends AbstractController
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activityreason', array('id' => $entity->getId())));
return $this->redirect($this->generateUrl('chill_activity_activityreason', ['id' => $entity->getId()]));
}
return $this->render('ChillActivityBundle:ActivityReason:new.html.twig', array(
return $this->render('ChillActivityBundle:ActivityReason:new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
));
}
/**
* Creates a form to create a ActivityReason entity.
*
* @param ActivityReason $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(ActivityReason $entity)
{
$form = $this->createForm(ActivityReasonType::class, $entity, array(
'action' => $this->generateUrl('chill_activity_activityreason_create'),
'method' => 'POST',
));
$form->add('submit', SubmitType::class, array('label' => 'Create'));
return $form;
}
/**
* Displays a form to create a new ActivityReason entity.
*
*/
public function newAction()
{
$entity = new ActivityReason();
$form = $this->createCreateForm($entity);
return $this->render('ChillActivityBundle:ActivityReason:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
/**
* Finds and displays a ActivityReason entity.
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ChillActivityBundle:ActivityReason')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReason entity.');
}
return $this->render('ChillActivityBundle:ActivityReason:show.html.twig', array(
'entity' => $entity,
));
'form' => $form->createView(),
]);
}
/**
* Displays a form to edit an existing ActivityReason entity.
*
* @param mixed $id
*/
public function editAction($id)
{
@ -123,33 +63,64 @@ class ActivityReasonController extends AbstractController
$editForm = $this->createEditForm($entity);
return $this->render('ChillActivityBundle:ActivityReason:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView()
));
return $this->render('ChillActivityBundle:ActivityReason:edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
}
/**
* Creates a form to edit a ActivityReason entity.
*
* @param ActivityReason $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(ActivityReason $entity)
* Lists all ActivityReason entities.
*/
public function indexAction()
{
$form = $this->createForm(ActivityReasonType::class, $entity, array(
'action' => $this->generateUrl('chill_activity_activityreason_update', array('id' => $entity->getId())),
'method' => 'PUT',
));
$em = $this->getDoctrine()->getManager();
$form->add('submit', SubmitType::class, array('label' => 'Update'));
$entities = $em->getRepository('ChillActivityBundle:ActivityReason')->findAll();
return $form;
return $this->render('ChillActivityBundle:ActivityReason:index.html.twig', [
'entities' => $entities,
]);
}
/**
* Displays a form to create a new ActivityReason entity.
*/
public function newAction()
{
$entity = new ActivityReason();
$form = $this->createCreateForm($entity);
return $this->render('ChillActivityBundle:ActivityReason:new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
]);
}
/**
* Finds and displays a ActivityReason entity.
*
* @param mixed $id
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ChillActivityBundle:ActivityReason')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReason entity.');
}
return $this->render('ChillActivityBundle:ActivityReason:show.html.twig', [
'entity' => $entity,
]);
}
/**
* Edits an existing ActivityReason entity.
*
* @param mixed $id
*/
public function updateAction(Request $request, $id)
{
@ -167,12 +138,50 @@ class ActivityReasonController extends AbstractController
if ($editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activityreason', array('id' => $id)));
return $this->redirect($this->generateUrl('chill_activity_activityreason', ['id' => $id]));
}
return $this->render('ChillActivityBundle:ActivityReason:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView()
));
return $this->render('ChillActivityBundle:ActivityReason:edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
}
/**
* Creates a form to create a ActivityReason entity.
*
* @param ActivityReason $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(ActivityReason $entity)
{
$form = $this->createForm(ActivityReasonType::class, $entity, [
'action' => $this->generateUrl('chill_activity_activityreason_create'),
'method' => 'POST',
]);
$form->add('submit', SubmitType::class, ['label' => 'Create']);
return $form;
}
/**
* Creates a form to edit a ActivityReason entity.
*
* @param ActivityReason $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(ActivityReason $entity)
{
$form = $this->createForm(ActivityReasonType::class, $entity, [
'action' => $this->generateUrl('chill_activity_activityreason_update', ['id' => $entity->getId()]),
'method' => 'PUT',
]);
$form->add('submit', SubmitType::class, ['label' => 'Update']);
return $form;
}
}

View File

@ -1,5 +1,14 @@
<?php
/**
* 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\ActivityBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
@ -9,10 +18,8 @@ use Symfony\Component\HttpFoundation\Request;
class AdminActivityPresenceController extends CRUDController
{
/**
* @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)

View File

@ -1,5 +1,14 @@
<?php
/**
* 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\ActivityBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
@ -9,10 +18,8 @@ use Symfony\Component\HttpFoundation\Request;
class AdminActivityTypeCategoryController extends CRUDController
{
/**
* @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)

View File

@ -1,5 +1,14 @@
<?php
/**
* 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\ActivityBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
@ -9,15 +18,14 @@ use Symfony\Component\HttpFoundation\Request;
class AdminActivityTypeController extends CRUDController
{
/**
* @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)
{
/** @var \Doctrine\ORM\QueryBuilder $query */
return $query->orderBy('e.ordering', 'ASC');
return $query->orderBy('e.ordering', 'ASC')
->addOrderBy('e.id', 'ASC');
}
}

View File

@ -1,33 +1,20 @@
<?php
/*
/**
* Chill is a software for social workers
* Copyright (C) 2015 Champs Libres <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* Controller for activity configuration
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
* Controller for activity configuration.
*/
class AdminController extends AbstractController
{
@ -35,7 +22,7 @@ class AdminController extends AbstractController
{
return $this->render('ChillActivityBundle:Admin:layout_activity.html.twig');
}
public function redirectToAdminIndexAction()
{
return $this->redirectToRoute('chill_main_admin_central');

View File

@ -1,55 +1,41 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\DataFixtures\ORM\LoadScopes;
use Chill\MainBundle\DataFixtures\ORM\LoadUsers;
use Chill\PersonBundle\Entity\Person;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectManager;
use Faker\Factory as FakerFactory;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\DataFixtures\ORM\LoadUsers;
use Chill\ActivityBundle\DataFixtures\ORM\LoadActivityReason;
use Chill\ActivityBundle\DataFixtures\ORM\LoadActivityType;
use Chill\MainBundle\DataFixtures\ORM\LoadScopes;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
/**
* Load reports into DB
*
* @author Champs-Libres Coop
*/
class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
{
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
private EntityManagerInterface $em;
/**
* @var \Faker\Generator
*/
private $faker;
public function __construct()
public function __construct(EntityManagerInterface $em)
{
$this->faker = FakerFactory::create('fr_FR');
$this->em = $em;
}
public function getOrder()
@ -57,57 +43,27 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, C
return 16400;
}
/**
* Return a random scope
*
* @return \Chill\MainBundle\Entity\Scope
*/
private function getRandomScope()
public function load(ObjectManager $manager)
{
$scopeRef = LoadScopes::$references[array_rand(LoadScopes::$references)];
return $this->getReference($scopeRef);
}
$persons = $this->em
->getRepository(Person::class)
->findAll();
/**
* Return a random activityType
*
* @return \Chill\ActivityBundle\Entity\ActivityType
*/
private function getRandomActivityType()
{
$typeRef = LoadActivityType::$references[array_rand(LoadActivityType::$references)];
return $this->getReference($typeRef);
}
foreach ($persons as $person) {
$activityNbr = mt_rand(0, 3);
/**
* Return a random activityReason
*
* @return \Chill\ActivityBundle\Entity\ActivityReason
*/
private function getRandomActivityReason(array $excludingIds)
{
$reasonRef = LoadActivityReason::$references[array_rand(LoadActivityReason::$references)];
for ($i = 0; $i < $activityNbr; ++$i) {
$activity = $this->newRandomActivity($person);
if (in_array($this->getReference($reasonRef)->getId(), $excludingIds)) {
// we have a reason which should be excluded. Find another...
return $this->getRandomActivityReason($excludingIds);
if (null !== $activity) {
$manager->persist($activity);
}
}
}
return $this->getReference($reasonRef);
$manager->flush();
}
/**
* Return a random user
*
* @return \Chill\MainBundle\Entity\User
*/
private function getRandomUser()
{
$userRef = array_rand(LoadUsers::$refs);
return $this->getReference($userRef);
}
public function newRandomActivity($person)
public function newRandomActivity($person): ?Activity
{
$activity = (new Activity())
->setUser($this->getRandomUser())
@ -115,38 +71,68 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, C
->setDate($this->faker->dateTimeThisYear())
->setDurationTime($this->faker->dateTime(36000))
->setType($this->getRandomActivityType())
->setScope($this->getRandomScope())
;
->setScope($this->getRandomScope());
// ->setAttendee($this->faker->boolean())
$usedId = array();
for ($i = 0; $i < rand(0, 4); $i++) {
$reason = $this->getRandomActivityReason($usedId);
$usedId[] = $reason->getId();
$activity->addReason($reason);
for ($i = 0; mt_rand(0, 4) > $i; ++$i) {
$reason = $this->getRandomActivityReason();
if (null !== $reason) {
$activity->addReason($reason);
} else {
return null;
}
}
return $activity;
}
public function load(ObjectManager $manager)
/**
* Return a random activityReason.
*
* @return \Chill\ActivityBundle\Entity\ActivityReason
*/
private function getRandomActivityReason()
{
$persons = $this->container->get('doctrine.orm.entity_manager')
->getRepository('ChillPersonBundle:Person')
->findAll();
$reasonRef = LoadActivityReason::$references[array_rand(LoadActivityReason::$references)];
foreach($persons as $person) {
$activityNbr = rand(0,3);
$ref = 'activity_'.$person->getFullnameCanonical();
return $this->getReference($reasonRef);
}
for($i = 0; $i < $activityNbr; $i ++) {
$activity = $this->newRandomActivity($person);
$manager->persist($activity);
}
/**
* Return a random activityType.
*
* @return \Chill\ActivityBundle\Entity\ActivityType
*/
private function getRandomActivityType()
{
$typeRef = LoadActivityType::$references[array_rand(LoadActivityType::$references)];
$this->setReference($ref, $activity);
}
$manager->flush();
return $this->getReference($typeRef);
}
/**
* Return a random scope.
*
* @return \Chill\MainBundle\Entity\Scope
*/
private function getRandomScope()
{
$scopeRef = LoadScopes::$references[array_rand(LoadScopes::$references)];
return $this->getReference($scopeRef);
}
/**
* Return a random user.
*
* @return \Chill\MainBundle\Entity\User
*/
private function getRandomUser()
{
$userRef = array_rand(LoadUsers::$refs);
return $this->getReference($userRef);
}
}

View File

@ -1,15 +1,23 @@
<?php
/**
* 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\ActivityBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\DataFixtures\ORM\LoadAbstractNotificationsTrait;
use Chill\ActivityBundle\DataFixtures\ORM\LoadActivity;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
/**
* Load notififications into database
* Load notififications into database.
*/
class LoadActivityNotifications extends AbstractFixture implements DependentFixtureInterface
{
@ -25,9 +33,9 @@ class LoadActivityNotifications extends AbstractFixture implements DependentFixt
'center a_social',
'center a_administrative',
'center a_direction',
'multi_center'
'multi_center',
],
]
],
];
public function getDependencies()

View File

@ -1,81 +1,68 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\ActivityBundle\Entity\ActivityReason;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use Chill\ActivityBundle\Entity\ActivityReason;
/**
* Description of LoadActivityReason
*
* @author Champs-Libres Coop
* Description of LoadActivityReason.
*/
class LoadActivityReason extends AbstractFixture implements OrderedFixtureInterface
{
public static $references = [];
public function getOrder()
{
return 16300;
}
public static $references = array();
public function load(ObjectManager $manager)
{
$reasons = [
[
'name' => ['fr' => 'Recherche logement', 'en' => 'Housing research', 'nl' => 'Woning zoektoch'],
'category' => 'cat_Housing'],
'category' => 'cat_Housing', ],
[
'name' => ['fr' => 'Problème avec propriétaire', 'en' => 'Landlord problems', 'nl' => 'Huisbaas problemen'],
'category' => 'cat_Housing'],
'category' => 'cat_Housing', ],
[
'name' => ['fr' => 'Retard de payement', 'en' => 'Payement problems', 'nl' => 'Betalings vertragingen'],
'category' => 'cat_Housing'],
'category' => 'cat_Housing', ],
[
'name' => ['fr' => 'Explication législation', 'en' => 'Legislation explanation', 'nl' => 'Legislative uitleg'],
'category' => 'cat_Unemployment procedure'],
'category' => 'cat_Unemployment procedure', ],
[
'name' => ['fr' => 'Coaching entretien d\'activation', 'en' => 'Interview coaching', 'nl' => 'Interview coaching'],
'category' => 'cat_Unemployment procedure'],
'category' => 'cat_Unemployment procedure', ],
[
'name' => ['fr' => 'Récupération des allocations', 'en' => 'Allowance recovery', 'nl' => 'Terugwinning van de uitkeringen'],
'category' => 'cat_Unemployment procedure']
'category' => 'cat_Unemployment procedure', ],
];
foreach ($reasons as $r) {
print "Creating activity reason : " . $r['name']['en'] . "\n";
echo 'Creating activity reason : ' . $r['name']['en'] . "\n";
$activityReason = (new ActivityReason())
->setName(($r['name']))
->setActive(true)
->setCategory($this->getReference($r['category']));
$manager->persist($activityReason);
$reference = 'activity_reason_'.$r['name']['en'];
$reference = 'activity_reason_' . $r['name']['en'];
$this->addReference($reference, $activityReason);
static::$references[] = $reference;
}
$manager->flush();
}
}

View File

@ -1,35 +1,23 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\ActivityBundle\Entity\ActivityReasonCategory;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use Chill\ActivityBundle\Entity\ActivityReasonCategory;
/**
* Description of LoadActivityReasonCategory
*
* @author Champs-Libres Coop
* Description of LoadActivityReasonCategory.
*/
class LoadActivityReasonCategory extends AbstractFixture implements OrderedFixtureInterface
{
@ -37,27 +25,26 @@ class LoadActivityReasonCategory extends AbstractFixture implements OrderedFixtu
{
return 16200;
}
public function load(ObjectManager $manager)
{
$categs = [
['name' =>
['fr' => 'Logement', 'en' => 'Housing', 'nl' => 'Woning']],
['name' =>
['fr' => 'Démarches chômage', 'en' => 'Unemployment procedure', 'nl' => 'Werkloosheid werkwijze']],
['name' => ['fr' => 'Logement', 'en' => 'Housing', 'nl' => 'Woning']],
['name' => ['fr' => 'Démarches chômage', 'en' => 'Unemployment procedure', 'nl' => 'Werkloosheid werkwijze']],
];
foreach ($categs as $c) {
print "Creating activity reason category : " . $c['name']['en'] . "\n";
echo 'Creating activity reason category : ' . $c['name']['en'] . "\n";
$activityReasonCategory = (new ActivityReasonCategory())
->setName(($c['name']))
->setActive(true);
$manager->persist($activityReasonCategory);
$this->addReference(
'cat_'.$c['name']['en'],
$activityReasonCategory);
'cat_' . $c['name']['en'],
$activityReasonCategory
);
}
$manager->flush();
}
}

View File

@ -1,86 +1,67 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\ActivityBundle\Entity\ActivityType;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use Chill\ActivityBundle\Entity\ActivityType;
/**
* Description of LoadActivityType
*
* @author Champs-Libres Coop
* Description of LoadActivityType.
*/
class LoadActivityType extends Fixture implements OrderedFixtureInterface
{
public static $references = [];
public function getOrder()
{
return 16100;
}
public static $references = array();
public function load(ObjectManager $manager)
{
$types = [
# Exange
// Exange
[
'name' =>
['fr' => 'Entretien physique avec l\'usager'],
'category' => 'exchange' ],
'name' => ['fr' => 'Entretien physique avec l\'usager'],
'category' => 'exchange', ],
[
'name' =>
['fr' => 'Appel téléphonique', 'en' => 'Telephone call', 'nl' => 'Telefoon appel'],
'category' => 'exchange' ],
'name' => ['fr' => 'Appel téléphonique', 'en' => 'Telephone call', 'nl' => 'Telefoon appel'],
'category' => 'exchange', ],
[
'name' =>
['fr' => 'Courriel', 'en' => 'Email', 'nl' => 'Email'],
'category' => 'exchange' ],
# Meeting
'name' => ['fr' => 'Courriel', 'en' => 'Email', 'nl' => 'Email'],
'category' => 'exchange', ],
// Meeting
[
'name' =>
['fr' => 'Point technique encadrant'],
'category' => 'meeting' ],
'name' => ['fr' => 'Point technique encadrant'],
'category' => 'meeting', ],
[
'name' =>
['fr' => 'Réunion avec des partenaires'],
'category' => 'meeting' ],
'name' => ['fr' => 'Réunion avec des partenaires'],
'category' => 'meeting', ],
[
'name' =>
['fr' => 'Commission pluridisciplinaire et pluri-institutionnelle'],
'category' => 'meeting' ],
'name' => ['fr' => 'Commission pluridisciplinaire et pluri-institutionnelle'],
'category' => 'meeting', ],
];
foreach ($types as $t) {
print "Creating activity type : " . $t['name']['fr'] . " (cat:". $t['category'] . " \n";
echo 'Creating activity type : ' . $t['name']['fr'] . ' (cat:' . $t['category'] . " \n";
$activityType = (new ActivityType())
->setName(($t['name']))
->setCategory($this->getReference('activity_type_cat_'.$t['category']))
->setCategory($this->getReference('activity_type_cat_' . $t['category']))
->setSocialIssuesVisible(1)
->setSocialActionsVisible(1);
$manager->persist($activityType);
$reference = 'activity_type_'.$t['name']['fr'];
$reference = 'activity_type_' . $t['name']['fr'];
$this->addReference($reference, $activityType);
static::$references[] = $reference;
}

View File

@ -1,40 +1,27 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2021, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\ActivityBundle\Entity\ActivityTypeCategory;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use Chill\ActivityBundle\Entity\ActivityTypeCategory;
/**
* Fixtures for ActivityTypeCategory
*
* @author Champs-Libres Coop
* Fixtures for ActivityTypeCategory.
*/
class LoadActivityTypeCategory extends Fixture implements OrderedFixtureInterface
{
public static $references = array();
public static $references = [];
public function getOrder()
{
@ -55,13 +42,13 @@ class LoadActivityTypeCategory extends Fixture implements OrderedFixtureInterfac
];
foreach ($categories as $cat) {
print "Creating activity type category : " . $cat['ref'] . "\n";
echo 'Creating activity type category : ' . $cat['ref'] . "\n";
$newCat = (new ActivityTypeCategory())
->setName(($cat['name']));
$manager->persist($newCat);
$reference = 'activity_type_cat_'.$cat['ref'];
$reference = 'activity_type_cat_' . $cat['ref'];
$this->addReference($reference, $newCat);
static::$references[] = $reference;

View File

@ -1,37 +1,29 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup;
use Chill\MainBundle\DataFixtures\ORM\LoadScopes;
use Chill\MainBundle\Entity\RoleScope;
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\MainBundle\DataFixtures\ORM\LoadScopes;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use function in_array;
/**
* Add a role CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE for all groups except administrative,
* and a role CHILL_ACTIVITY_SEE for administrative
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* and a role CHILL_ACTIVITY_SEE for administrative.
*/
class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterface
{
@ -40,12 +32,12 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac
return 16000;
}
public function load(ObjectManager $manager)
{
foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) {
$permissionsGroup = $this->getReference($permissionsGroupRef);
foreach (LoadScopes::$references as $scopeRef){
foreach (LoadScopes::$references as $scopeRef) {
$scope = $this->getReference($scopeRef);
//create permission group
switch ($permissionsGroup->getName()) {
@ -53,47 +45,52 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac
if ($scope->getName()['en'] === 'administrative') {
break 2; // we do not want any power on administrative
}
break;
case 'administrative':
case 'direction':
if (in_array($scope->getName()['en'], array('administrative', 'social'))) {
if (in_array($scope->getName()['en'], ['administrative', 'social'], true)) {
break 2; // we do not want any power on social or administrative
}
}
break;
}
printf("Adding CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE & CHILL_ACTIVITY_DELETE, and stats and list permissions to %s "
. "permission group, scope '%s' \n",
$permissionsGroup->getName(), $scope->getName()['en']);
printf(
'Adding CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE & CHILL_ACTIVITY_DELETE, and stats and list permissions to %s '
. "permission group, scope '%s' \n",
$permissionsGroup->getName(),
$scope->getName()['en']
);
$roleScopeUpdate = (new RoleScope())
->setRole('CHILL_ACTIVITY_UPDATE')
->setScope($scope);
->setRole('CHILL_ACTIVITY_UPDATE')
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeUpdate);
$roleScopeCreate = (new RoleScope())
->setRole('CHILL_ACTIVITY_CREATE')
->setScope($scope);
->setRole(ActivityVoter::CREATE_ACCOMPANYING_COURSE)
->setScope($scope);
$roleScopeCreate = (new RoleScope())
->setRole(ActivityVoter::CREATE_PERSON)
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeCreate);
$roleScopeDelete = (new RoleScope())
->setRole('CHILL_ACTIVITY_DELETE')
->setScope($scope);
->setRole('CHILL_ACTIVITY_DELETE')
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeDelete);
$roleScopeList = (new RoleScope())
->setRole(ActivityStatsVoter::LISTS)
;
->setRole(ActivityStatsVoter::LISTS);
$permissionsGroup->addRoleScope($roleScopeList);
$roleScopeStat = (new RoleScope())
->setRole(ActivityStatsVoter::STATS)
;
->setRole(ActivityStatsVoter::STATS);
$permissionsGroup->addRoleScope($roleScopeStat);
$manager->persist($roleScopeUpdate);
$manager->persist($roleScopeCreate);
$manager->persist($roleScopeDelete);
}
}
$manager->flush();
}
}

View File

@ -1,45 +1,30 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\DependencyInjection;
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\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* This is the class that loads and manages your bundle configuration
* This is the class that loads and manages your bundle configuration.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class ChillActivityExtension extends Extension implements PrependExtensionInterface
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
@ -47,7 +32,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
$container->setParameter('chill_activity.form.time_duration', $config['form']['time_duration']);
$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/export.yaml');
$loader->load('services/repositories.yaml');
@ -56,6 +41,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
$loader->load('services/form.yaml');
$loader->load('services/templating.yaml');
$loader->load('services/accompanyingPeriodConsistency.yaml');
$loader->load('services/doctrine.entitylistener.yaml');
}
public function prepend(ContainerBuilder $container)
@ -65,31 +51,38 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
$this->prependCruds($container);
}
/* (non-PHPdoc)
public function prependAuthorization(ContainerBuilder $container)
{
$container->prependExtensionConfig('security', [
'role_hierarchy' => [
ActivityVoter::UPDATE => [ActivityVoter::SEE_DETAILS],
ActivityVoter::CREATE_PERSON => [ActivityVoter::SEE_DETAILS],
ActivityVoter::CREATE_ACCOMPANYING_COURSE => [ActivityVoter::SEE_DETAILS],
ActivityVoter::DELETE => [ActivityVoter::SEE_DETAILS],
ActivityVoter::SEE_DETAILS => [ActivityVoter::SEE],
ActivityVoter::FULL => [
ActivityVoter::CREATE_PERSON,
ActivityVoter::CREATE_ACCOMPANYING_COURSE,
ActivityVoter::DELETE,
ActivityVoter::UPDATE,
],
],
]);
}
/** (non-PHPdoc).
* @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
*/
public function prependRoutes(ContainerBuilder $container)
{
//add routes for custom bundle
$container->prependExtensionConfig('chill_main', array(
'routing' => array(
'resources' => array(
'@ChillActivityBundle/config/routes.yaml'
)
)
));
}
public function prependAuthorization(ContainerBuilder $container)
{
$container->prependExtensionConfig('security', array(
'role_hierarchy' => array(
ActivityVoter::UPDATE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::CREATE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::DELETE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::SEE_DETAILS => array(ActivityVoter::SEE)
)
));
$container->prependExtensionConfig('chill_main', [
'routing' => [
'resources' => [
'@ChillActivityBundle/config/routes.yaml',
],
],
]);
}
protected function prependCruds(ContainerBuilder $container)
@ -105,17 +98,17 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
'actions' => [
'index' => [
'template' => '@ChillActivity/ActivityType/index.html.twig',
'role' => 'ROLE_ADMIN'
'role' => 'ROLE_ADMIN',
],
'new' => [
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityType/new.html.twig',
],
'edit' => [
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityType/edit.html.twig',
]
]
],
],
],
[
'class' => \Chill\ActivityBundle\Entity\ActivityTypeCategory::class,
@ -126,17 +119,17 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
'actions' => [
'index' => [
'template' => '@ChillActivity/ActivityTypeCategory/index.html.twig',
'role' => 'ROLE_ADMIN'
'role' => 'ROLE_ADMIN',
],
'new' => [
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityTypeCategory/new.html.twig',
],
'edit' => [
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityTypeCategory/edit.html.twig',
]
]
],
],
],
[
'class' => \Chill\ActivityBundle\Entity\ActivityPresence::class,
@ -147,19 +140,19 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
'actions' => [
'index' => [
'template' => '@ChillActivity/ActivityPresence/index.html.twig',
'role' => 'ROLE_ADMIN'
'role' => 'ROLE_ADMIN',
],
'new' => [
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityPresence/new.html.twig',
],
'edit' => [
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityPresence/edit.html.twig',
]
]
],
],
],
]
],
]);
}
}

View File

@ -1,71 +1,76 @@
<?php
/**
* 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\ActivityBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use function is_int;
/**
* This is the class that validates and merges configuration from your app/config files
* This is the class that validates and merges configuration from your app/config files.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('chill_activity');
$rootNode = $treeBuilder->getRootNode('chill_activity');
$rootNode
->children()
->arrayNode('form')
->canBeEnabled()
->children()
->arrayNode('time_duration')
->isRequired()
->requiresAtLeastOneElement()
->defaultValue(
array(
[ 'label' => '5 minutes', 'seconds' => 300],
[ 'label' => '10 minutes', 'seconds' => 600],
[ 'label' => '15 minutes', 'seconds' => 900],
[ 'label' => '20 minutes', 'seconds' => 1200],
[ 'label' => '25 minutes', 'seconds' => 1500],
[ 'label' => '30 minutes', 'seconds' => 1800],
[ 'label' => '45 minutes', 'seconds' => 2700],
[ 'label' => '1 hour', 'seconds' => 3600],
[ 'label' => '1 hour 15', 'seconds' => 4500],
[ 'label' => '1 hour 30', 'seconds' => 5400],
[ 'label' => '1 hour 45', 'seconds' => 6300],
[ 'label' => '2 hours', 'seconds' => 7200],
)
)
->info('The intervals of time to show in activity form')
->prototype('array')
->children()
->scalarNode('seconds')
->info("The number of seconds of this duration. Must be an integer.")
->cannotBeEmpty()
->validate()
->ifTrue(function($data) {
return !is_int($data);
})->thenInvalid("The value %s is not a valid integer")
->end()
->end()
->scalarNode('label')
->cannotBeEmpty()
->info("The label to show into fields")
->end()
->end()
->end()
->children()
->arrayNode('form')
->canBeEnabled()
->children()
->arrayNode('time_duration')
->isRequired()
->requiresAtLeastOneElement()
->defaultValue(
[
['label' => '5 minutes', 'seconds' => 300],
['label' => '10 minutes', 'seconds' => 600],
['label' => '15 minutes', 'seconds' => 900],
['label' => '20 minutes', 'seconds' => 1200],
['label' => '25 minutes', 'seconds' => 1500],
['label' => '30 minutes', 'seconds' => 1800],
['label' => '45 minutes', 'seconds' => 2700],
['label' => '1 hour', 'seconds' => 3600],
['label' => '1 hour 15', 'seconds' => 4500],
['label' => '1 hour 30', 'seconds' => 5400],
['label' => '1 hour 45', 'seconds' => 6300],
['label' => '2 hours', 'seconds' => 7200],
]
)
->info('The intervals of time to show in activity form')
->prototype('array')
->children()
->scalarNode('seconds')
->info('The number of seconds of this duration. Must be an integer.')
->cannotBeEmpty()
->validate()
->ifTrue(static function ($data) {
return !is_int($data);
})->thenInvalid('The value %s is not a valid integer')
->end()
->end()
->scalarNode('label')
->cannotBeEmpty()
->info('The label to show into fields')
->end()
->end()
->end()
// ->validate()
//
//
// ->ifTrue(function ($data) {
// // test this is an array
// if (!is_array($data)) {
@ -84,11 +89,10 @@ class Configuration implements ConfigurationInterface
// })
// ->thenInvalid("The data are invalid. The keys must be a string and the value integers")
// ->end()
->end()
->end()
->end()
->end();
->end()
->end()
->end()
->end();
return $treeBuilder;
}

View File

@ -1,71 +1,107 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Entity;
use Chill\ActivityBundle\Validator\Constraints as ActivityValidator;
use Chill\DocStoreBundle\Entity\Document;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency;
use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\ORM\Mapping as ORM;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Center;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface;
use Doctrine\Common\Collections\Collection;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency;
use Symfony\Component\Serializer\Annotation\Groups;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
/**
* Class Activity
* Class Activity.
*
* @package Chill\ActivityBundle\Entity
* @ORM\Entity(repositoryClass="Chill\ActivityBundle\Repository\ActivityRepository")
* @ORM\Table(name="activity")
* @ORM\HasLifecycleCallbacks()
* @ORM\HasLifecycleCallbacks
* @DiscriminatorMap(typeProperty="type", mapping={
* "activity"=Activity::class
* })
* "activity": Activity::class
* })
* @ActivityValidator\ActivityValidity
*
* TODO see if necessary
* UserCircleConsistency(
* "CHILL_ACTIVITY_SEE_DETAILS",
* getUserFunction="getUser",
* path="scope")
*/
/*
* TODO : revoir
* @UserCircleConsistency(
* "CHILL_ACTIVITY_SEE_DETAILS",
* getUserFunction="getUser",
* path="scope")
*/
class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPeriodLinkedWithSocialIssuesEntityInterface
class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCenterInterface, HasScopeInterface
{
const SENTRECEIVED_SENT = 'sent';
const SENTRECEIVED_RECEIVED = 'received';
public const SENTRECEIVED_RECEIVED = 'received';
public const SENTRECEIVED_SENT = 'sent';
/**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod")
* @Groups({"read"})
*/
private ?AccompanyingPeriod $accompanyingPeriod = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityType")
* @Groups({"read"})
* @SerializedName("activityType")
* @ORM\JoinColumn(name="type_id")
*/
private ActivityType $activityType;
/**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityPresence")
*/
private ?ActivityPresence $attendee = null;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_")
*/
private CommentEmbeddable $comment;
/**
* @ORM\Column(type="datetime")
*/
private DateTime $date;
/**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"})
*/
private Collection $documents;
/**
* @ORM\Column(type="time", nullable=true)
*/
private ?DateTime $durationTime = null;
/**
* @ORM\Column(type="boolean", options={"default": false})
*/
private bool $emergency = false;
/**
* @ORM\Id
@ -76,29 +112,21 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
* @groups({"read"})
*/
private User $user;
private ?Location $location = null;
/**
* @ORM\Column(type="datetime")
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person")
*/
private \DateTime $date;
private ?Person $person = null;
/**
* @ORM\Column(type="time", nullable=true)
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person")
* @Groups({"read"})
*/
private ?\DateTime $durationTime = null;
/**
* @ORM\Column(type="time", nullable=true)
*/
private ?\DateTime $travelTime = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityPresence")
*/
private ?ActivityPresence $attendee = null;
private ?Collection $persons = null;
/**
* @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason")
@ -106,11 +134,14 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
private Collection $reasons;
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue")
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue")
* @Groups({"read"})
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
*/
private Collection $socialIssues;
private ?Scope $scope = null;
/**
* @ORM\Column(type="string", options={"default": ""})
*/
private string $sentReceived = '';
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialAction")
@ -120,36 +151,11 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
private Collection $socialActions;
/**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityType")
*/
private ActivityType $type;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
*/
private ?Scope $scope = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person")
*/
private ?Person $person = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod")
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue")
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue")
* @Groups({"read"})
*/
private ?AccompanyingPeriod $accompanyingPeriod = null;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_")
*/
private CommentEmbeddable $comment;
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person")
* @Groups({"read"})
*/
private ?Collection $persons = null;
private Collection $socialIssues;
/**
* @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
@ -158,9 +164,14 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
private ?Collection $thirdParties = null;
/**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject")
* @ORM\Column(type="time", nullable=true)
*/
private Collection $documents;
private ?DateTime $travelTime = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
*/
private User $user;
/**
* @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User")
@ -168,23 +179,6 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
*/
private ?Collection $users = null;
/**
* @ORM\Column(type="boolean", options={"default"=false})
*/
private bool $emergency = false;
/**
* @ORM\Column(type="string", options={"default"=""})
*/
private string $sentReceived = '';
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
* @groups({"read"})
*/
private ?Location $location = null;
public function __construct()
{
$this->reasons = new ArrayCollection();
@ -197,71 +191,27 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
$this->socialActions = new ArrayCollection();
}
public function getId(): ?int
public function addDocument(Document $document): self
{
return $this->id;
}
public function setUser(User $user): self
{
$this->user = $user;
$this->documents[] = $document;
return $this;
}
public function getUser(): User
/**
* Add a person to the person list.
*/
public function addPerson(?Person $person): self
{
return $this->user;
}
public function setDate(\DateTime $date): self
{
$this->date = $date;
if (null !== $person) {
if (!$this->persons->contains($person)) {
$this->persons[] = $person;
}
}
return $this;
}
public function getDate(): \DateTime
{
return $this->date;
}
public function setDurationTime(?\DateTime $durationTime): self
{
$this->durationTime = $durationTime;
return $this;
}
public function getDurationTime(): ?\DateTime
{
return $this->durationTime;
}
public function setTravelTime(\DateTime $travelTime): self
{
$this->travelTime = $travelTime;
return $this;
}
public function getTravelTime(): ?\DateTime
{
return $this->travelTime;
}
public function setAttendee(ActivityPresence $attendee): self
{
$this->attendee = $attendee;
return $this;
}
public function getAttendee(): ?ActivityPresence
{
return $this->attendee;
}
public function addReason(ActivityReason $reason): self
{
$this->reasons->add($reason);
@ -269,49 +219,6 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
return $this;
}
public function removeReason(ActivityReason $reason): void
{
$this->reasons->removeElement($reason);
}
public function getReasons(): Collection
{
return $this->reasons;
}
public function setReasons(?ArrayCollection $reasons): self
{
$this->reasons = $reasons;
return $this;
}
public function getSocialIssues(): Collection
{
return $this->socialIssues;
}
public function addSocialIssue(SocialIssue $socialIssue): self
{
if (!$this->socialIssues->contains($socialIssue)) {
$this->socialIssues[] = $socialIssue;
}
return $this;
}
public function removeSocialIssue(SocialIssue $socialIssue): self
{
$this->socialIssues->removeElement($socialIssue);
return $this;
}
public function getSocialActions(): Collection
{
return $this->socialActions;
}
public function addSocialAction(SocialAction $socialAction): self
{
if (!$this->socialActions->contains($socialAction)) {
@ -321,67 +228,55 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
return $this;
}
public function removeSocialAction(SocialAction $socialAction): self
public function addSocialIssue(SocialIssue $socialIssue): self
{
$this->socialActions->removeElement($socialAction);
if (!$this->socialIssues->contains($socialIssue)) {
$this->socialIssues[] = $socialIssue;
}
return $this;
}
public function setType(ActivityType $type): self
public function addThirdParty(?ThirdParty $thirdParty): self
{
$this->type = $type;
if (null !== $thirdParty) {
if (!$this->thirdParties->contains($thirdParty)) {
$this->thirdParties[] = $thirdParty;
}
}
return $this;
}
public function getType(): ActivityType
public function addUser(?User $user): self
{
return $this->type;
}
public function setScope(Scope $scope): self
{
$this->scope = $scope;
if (null !== $user) {
if (!$this->users->contains($user)) {
$this->users[] = $user;
}
}
return $this;
}
public function getScope(): ?Scope
{
return $this->scope;
}
public function setPerson(?Person $person): self
{
$this->person = $person;
return $this;
}
public function getPerson(): ?Person
{
return $this->person;
}
public function getAccompanyingPeriod(): ?AccompanyingPeriod
{
return $this->accompanyingPeriod;
}
public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self
public function getActivityType(): ActivityType
{
$this->accompanyingPeriod = $accompanyingPeriod;
return $this->activityType;
}
return $this;
public function getAttendee(): ?ActivityPresence
{
return $this->attendee;
}
/**
* get the center
* center is extracted from person
* center is extracted from person.
*/
public function getCenter(): ?Center
{
@ -397,28 +292,39 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
return $this->comment;
}
public function setComment(CommentEmbeddable $comment): self
public function getDate(): DateTime
{
$this->comment = $comment;
return $this;
return $this->date;
}
/**
* Add a person to the person list
*/
public function addPerson(?Person $person): self
public function getDocuments(): Collection
{
if (null !== $person) {
$this->persons[] = $person;
}
return $this;
return $this->documents;
}
public function removePerson(Person $person): void
public function getDurationTime(): ?DateTime
{
$this->persons->removeElement($person);
return $this->durationTime;
}
public function getEmergency(): bool
{
return $this->emergency;
}
public function getId(): ?int
{
return $this->id;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function getPerson(): ?Person
{
return $this->person;
}
public function getPersons(): Collection
@ -430,13 +336,16 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
{
if (null !== $this->accompanyingPeriod) {
$personsAssociated = [];
foreach ($this->accompanyingPeriod->getParticipations() as $participation) {
if ($this->persons->contains($participation->getPerson())) {
$personsAssociated[] = $participation->getPerson();
}
}
return $personsAssociated;
}
return [];
}
@ -444,28 +353,104 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
{
if (null !== $this->accompanyingPeriod) {
$personsNotAssociated = [];
// TODO better semantic with: return $this->persons->filter(...);
foreach ($this->persons as $person) {
if (!in_array($person, $this->getPersonsAssociated())) {
$personsNotAssociated[] = $person;
if ($this->accompanyingPeriod->getOpenParticipationContainsPerson($person) === null) {
$personsNotAssociated[] = $person;
}
}
return $personsNotAssociated;
}
return [];
}
public function setPersons(?Collection $persons): self
public function getReasons(): Collection
{
$this->persons = $persons;
return $this->reasons;
}
public function getScope(): ?Scope
{
return $this->scope;
}
public function getSentReceived(): string
{
return $this->sentReceived;
}
public function getSocialActions(): Collection
{
return $this->socialActions;
}
public function getSocialIssues(): Collection
{
return $this->socialIssues;
}
public function getThirdParties(): Collection
{
return $this->thirdParties;
}
public function getTravelTime(): ?DateTime
{
return $this->travelTime;
}
/**
* @deprecated
*/
public function getType(): ActivityType
{
return $this->activityType;
}
public function getUser(): User
{
return $this->user;
}
public function getUsers(): Collection
{
return $this->users;
}
public function isEmergency(): bool
{
return $this->getEmergency();
}
public function removeDocument(Document $document): void
{
$this->documents->removeElement($document);
}
public function removePerson(Person $person): void
{
$this->persons->removeElement($person);
}
public function removeReason(ActivityReason $reason): void
{
$this->reasons->removeElement($reason);
}
public function removeSocialAction(SocialAction $socialAction): self
{
$this->socialActions->removeElement($socialAction);
return $this;
}
public function addThirdParty(?ThirdParty $thirdParty): self
public function removeSocialIssue(SocialIssue $socialIssue): self
{
if (null !== $thirdParty) {
$this->thirdParties[] = $thirdParty;
}
$this->socialIssues->removeElement($socialIssue);
return $this;
}
@ -474,33 +459,44 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
$this->thirdParties->removeElement($thirdParty);
}
public function getThirdParties(): Collection
public function removeUser(User $user): void
{
return $this->thirdParties;
$this->users->removeElement($user);
}
public function setThirdParties(?Collection $thirdParties): self
public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self
{
$this->thirdParties = $thirdParties;
$this->accompanyingPeriod = $accompanyingPeriod;
return $this;
}
public function addDocument(Document $document): self
public function setActivityType(ActivityType $activityType): self
{
$this->documents[] = $document;
$this->activityType = $activityType;
return $this;
}
public function removeDocument(Document $document): void
public function setAttendee(ActivityPresence $attendee): self
{
$this->documents->removeElement($document);
$this->attendee = $attendee;
return $this;
}
public function getDocuments(): Collection
public function setComment(CommentEmbeddable $comment): self
{
return $this->documents;
$this->comment = $comment;
return $this;
}
public function setDate(DateTime $date): self
{
$this->date = $date;
return $this;
}
public function setDocuments(Collection $documents): self
@ -510,41 +506,13 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
return $this;
}
public function addUser(?User $user): self
public function setDurationTime(?DateTime $durationTime): self
{
if (null !== $user) {
$this->users[] = $user;
}
return $this;
}
public function removeUser(User $user): void
{
$this->users->removeElement($user);
}
public function getUsers(): Collection
{
return $this->users;
}
public function setUsers(?Collection $users): self
{
$this->users = $users;
$this->durationTime = $durationTime;
return $this;
}
public function isEmergency(): bool
{
return $this->getEmergency();
}
public function getEmergency(): bool
{
return $this->emergency;
}
public function setEmergency(bool $emergency): self
{
$this->emergency = $emergency;
@ -552,9 +520,39 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
return $this;
}
public function getSentReceived(): string
public function setLocation(?Location $location): Activity
{
return $this->sentReceived;
$this->location = $location;
return $this;
}
public function setPerson(?Person $person): self
{
$this->person = $person;
return $this;
}
public function setPersons(?Collection $persons): self
{
$this->persons = $persons;
return $this;
}
public function setReasons(?ArrayCollection $reasons): self
{
$this->reasons = $reasons;
return $this;
}
public function setScope(Scope $scope): self
{
$this->scope = $scope;
return $this;
}
public function setSentReceived(?string $sentReceived): self
@ -564,21 +562,41 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
return $this;
}
/**
* @return Location|null
*/
public function getLocation(): ?Location
public function setThirdParties(?Collection $thirdParties): self
{
return $this->location;
$this->thirdParties = $thirdParties;
return $this;
}
public function setTravelTime(DateTime $travelTime): self
{
$this->travelTime = $travelTime;
return $this;
}
/**
* @param Location|null $location
* @return Activity
* @deprecated
*/
public function setLocation(?Location $location): Activity
public function setType(ActivityType $activityType): self
{
$this->location = $location;
$this->activityType = $activityType;
return $this;
}
public function setUser(UserInterface $user): self
{
$this->user = $user;
return $this;
}
public function setUsers(?Collection $users): self
{
$this->users = $users;
return $this;
}
}

View File

@ -1,37 +1,32 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ActivityPresence
* Class ActivityPresence.
*
* @package Chill\ActivityBundle\Entity
* @ORM\Entity()
* @ORM\Entity
* @ORM\Table(name="activitytpresence")
* @ORM\HasLifecycleCallbacks()
* @ORM\HasLifecycleCallbacks
*/
class ActivityPresence
{
/**
* @ORM\Column(type="boolean")
*/
private bool $active = true;
/**
* @ORM\Id
* @ORM\Column(name="id", type="integer")
@ -44,28 +39,6 @@ class ActivityPresence
*/
private array $name = [];
/**
* @ORM\Column(type="boolean")
*/
private bool $active = true;
public function getId(): int
{
return $this->id;
}
public function setName(array $name): self
{
$this->name = $name;
return $this;
}
public function getName(): array
{
return $this->name;
}
/**
* Get active
* return true if the category type is active.
@ -75,9 +48,19 @@ class ActivityPresence
return $this->active;
}
public function getId(): int
{
return $this->id;
}
public function getName(): array
{
return $this->name;
}
/**
* Is active
* return true if the category type is active
* return true if the category type is active.
*/
public function isActive(): bool
{
@ -86,7 +69,7 @@ class ActivityPresence
/**
* Set active
* set to true if the category type is active
* set to true if the category type is active.
*/
public function setActive(bool $active): self
{
@ -94,4 +77,11 @@ class ActivityPresence
return $this;
}
public function setName(array $name): self
{
$this->name = $name;
return $this;
}
}

View File

@ -1,40 +1,43 @@
<?php
/*
*
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
/**
* 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\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Chill\ActivityBundle\Entity\ActivityReasonCategory;
/**
* Class ActivityReason
* Class ActivityReason.
*
* @package Chill\ActivityBundle\Entity
* @ORM\Entity()
* @ORM\Entity
* @ORM\Table(name="activityreason")
* @ORM\HasLifecycleCallbacks()
* @ORM\HasLifecycleCallbacks
*/
class ActivityReason
{
/**
* @var integer
* @var bool
* @ORM\Column(type="boolean")
*/
private $active = true;
/**
* @var ActivityReasonCategory
* @ORM\ManyToOne(
* targetEntity="Chill\ActivityBundle\Entity\ActivityReasonCategory",
* inversedBy="reasons")
*/
private $category;
/**
* @var int
*
* @ORM\Id
* @ORM\Column(name="id", type="integer")
@ -44,91 +47,22 @@ class ActivityReason
/**
* @var array
* @ORM\Column(type="json_array")
* @ORM\Column(type="json")
*/
private $name;
/**
* @var ActivityReasonCategory
* @ORM\ManyToOne(
* targetEntity="Chill\ActivityBundle\Entity\ActivityReasonCategory",
* inversedBy="reasons")
*/
private $category;
/**
* @var boolean
* @ORM\Column(type="boolean")
*/
private $active = true;
/**
* Get id
* Get active.
*
* @return integer
* @return bool
*/
public function getId()
public function getActive()
{
return $this->id;
return $this->active;
}
/**
* Set name
*
* @param array $name
* @return ActivityReason
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return array | string
*/
public function getName($locale = null)
{
if ($locale) {
if (isset($this->name[$locale])) {
return $this->name[$locale];
} else {
foreach ($this->name as $name) {
if (!empty($name)) {
return $name;
}
}
}
return '';
} else {
return $this->name;
}
}
/**
* Set category of the reason. If you set to the reason an inactive
* category, the reason will become inactive
*
* @param ActivityReasonCategory $category
* @return ActivityReason
*/
public function setCategory(ActivityReasonCategory $category)
{
if($this->category !== $category && ! $category->getActive()) {
$this->setActive(False);
}
$this->category = $category;
return $this;
}
/**
* Get category
* Get category.
*
* @return ActivityReasonCategory
*/
@ -138,9 +72,46 @@ class ActivityReason
}
/**
* Set active
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Get name.
*
* @param mixed|null $locale
*
* @return array | string
*/
public function getName($locale = null)
{
if ($locale) {
if (isset($this->name[$locale])) {
return $this->name[$locale];
}
foreach ($this->name as $name) {
if (!empty($name)) {
return $name;
}
}
return '';
}
return $this->name;
}
/**
* Set active.
*
* @param bool $active
*
* @param boolean $active
* @return ActivityReason
*/
public function setActive($active)
@ -151,13 +122,33 @@ class ActivityReason
}
/**
* Get active
* Set category of the reason. If you set to the reason an inactive
* category, the reason will become inactive.
*
* @return boolean
* @return ActivityReason
*/
public function getActive()
public function setCategory(ActivityReasonCategory $category)
{
return $this->active;
if ($this->category !== $category && !$category->getActive()) {
$this->setActive(false);
}
$this->category = $category;
return $this;
}
/**
* Set name.
*
* @param array $name
*
* @return ActivityReason
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
}

View File

@ -1,39 +1,36 @@
<?php
/*
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
/**
* 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\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ActivityReasonCategory
* Class ActivityReasonCategory.
*
* @package Chill\ActivityBundle\Entity
* @ORM\Entity()
* @ORM\Entity
* @ORM\Table(name="activityreasoncategory")
* @ORM\HasLifecycleCallbacks()
* @ORM\HasLifecycleCallbacks
*/
class ActivityReasonCategory
{
/**
* @var integer
* @var bool
* @ORM\Column(type="boolean")
*/
private $active = true;
/**
* @var int
*
* @ORM\Id
* @ORM\Column(name="id", type="integer")
@ -43,25 +40,20 @@ class ActivityReasonCategory
/**
* @var string
* @ORM\Column(type="json_array")
* @ORM\Column(type="json")
*/
private $name;
/**
* @var boolean
* @ORM\Column(type="boolean")
*/
private $active = true;
/**
* Array of ActivityReason
* Array of ActivityReason.
*
* @var ArrayCollection
* @ORM\OneToMany(
* targetEntity="Chill\ActivityBundle\Entity\ActivityReason",
* mappedBy="category")
* mappedBy="category")
*/
private $reasons;
/**
* ActivityReasonCategory constructor.
*/
@ -69,19 +61,29 @@ class ActivityReasonCategory
{
$this->reasons = new ArrayCollection();
}
/**
* @return string
*/
public function __toString()
{
return 'ActivityReasonCategory('.$this->getName('x').')';
return 'ActivityReasonCategory(' . $this->getName('x') . ')';
}
/**
* Get id
* Get active.
*
* @return integer
* @return bool
*/
public function getActive()
{
return $this->active;
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
@ -89,20 +91,9 @@ class ActivityReasonCategory
}
/**
* Set name
* Get name.
*
* @param array $name
* @return ActivityReasonCategory
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
* @param mixed|null $locale
*
* @return array
*/
@ -111,48 +102,53 @@ class ActivityReasonCategory
if ($locale) {
if (isset($this->name[$locale])) {
return $this->name[$locale];
} else {
foreach ($this->name as $name) {
if (!empty($name)) {
return $name;
}
}
foreach ($this->name as $name) {
if (!empty($name)) {
return $name;
}
}
return '';
} else {
return $this->name;
}
return $this->name;
}
/**
* Declare a category as active (or not). When a category is set
* as unactive, all the reason have this entity as category is also
* set as unactive
* set as unactive.
*
* @param bool $active
*
* @param boolean $active
* @return ActivityReasonCategory
*/
public function setActive($active)
{
if($this->active !== $active && !$active) {
if ($this->active !== $active && !$active) {
foreach ($this->reasons as $reason) {
$reason->setActive($active);
}
}
$this->active = $active;
return $this;
}
/**
* Get active
* Set name.
*
* @return boolean
* @param array $name
*
* @return ActivityReasonCategory
*/
public function getActive()
public function setName($name)
{
return $this->active;
$this->name = $name;
return $this;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +1,47 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ActivityTypeCateogry
*
* @package Chill\ActivityBundle\Entity
* @ORM\Entity()
* @ORM\Entity
* @ORM\Table(name="activitytypecategory")
* @ORM\HasLifecycleCallbacks()
* @ORM\HasLifecycleCallbacks
*/
class ActivityTypeCategory
{
/**
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private ?int $id;
/**
* @ORM\Column(type="json_array")
*/
private array $name = [];
/**
* @ORM\Column(type="boolean")
*/
private bool $active = true;
/**
* @ORM\Column(type="float", options={"default"="0.0"})
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private ?int $id = null;
/**
* @ORM\Column(type="json")
*/
private array $name = [];
/**
* @ORM\Column(type="float", options={"default": "0.0"})
*/
private float $ordering = 0.0;
/**
* Get id
*/
public function getId(): int
{
return $this->id;
}
/**
* Set name
*/
public function setName(array $name): self
{
$this->name = $name;
return $this;
}
/**
* Get name
*/
public function getName(): array
{
return $this->name;
}
/**
* Get active
* return true if the category type is active.
@ -89,9 +51,27 @@ class ActivityTypeCategory
return $this->active;
}
public function getId(): ?int
{
return $this->id;
}
/**
* Get name.
*/
public function getName(): array
{
return $this->name;
}
public function getOrdering(): float
{
return $this->ordering;
}
/**
* Is active
* return true if the category type is active
* return true if the category type is active.
*/
public function isActive(): bool
{
@ -100,7 +80,7 @@ class ActivityTypeCategory
/**
* Set active
* set to true if the category type is active
* set to true if the category type is active.
*/
public function setActive(bool $active): self
{
@ -109,9 +89,14 @@ class ActivityTypeCategory
return $this;
}
public function getOrdering(): float
/**
* Set name.
*/
public function setName(array $name): self
{
return $this->ordering;
$this->name = $name;
return $this;
}
public function setOrdering(float $ordering): self

View File

@ -0,0 +1,77 @@
<?php
/**
* 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\ActivityBundle\EntityListener;
use Chill\ActivityBundle\Entity\Activity;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use function in_array;
class ActivityEntityListener
{
private EntityManagerInterface $em;
private AccompanyingPeriodWorkRepository $workRepository;
public function __construct(EntityManagerInterface $em, AccompanyingPeriodWorkRepository $workRepository)
{
$this->em = $em;
$this->workRepository = $workRepository;
}
public function persistActionToCourse(Activity $activity)
{
if ($activity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
$period = $activity->getAccompanyingPeriod();
$accompanyingCourseWorks = $this->workRepository->findByAccompanyingPeriod($period);
$periodActions = [];
$now = new DateTimeImmutable();
foreach ($accompanyingCourseWorks as $key => $work) {
// take only the actions which are still opened
if ($work->getEndDate() === null || $work->getEndDate() > ($activity->getDate() ?? $now)) {
$periodActions[$key] = spl_object_hash($work->getSocialAction());
}
}
$associatedPersons = $activity->getPersonsAssociated();
$associatedThirdparties = $activity->getThirdParties();
foreach ($activity->getSocialActions() as $action) {
if (in_array(spl_object_hash($action), $periodActions, true)) {
continue;
}
$newAction = new AccompanyingPeriodWork();
$newAction->setSocialAction($action);
$period->addWork($newAction);
$date = DateTimeImmutable::createFromMutable($activity->getDate());
$newAction->setStartDate($date);
foreach ($associatedPersons as $person) {
$newAction->addPerson($person);
}
foreach ($associatedThirdparties as $thirdparty) {
$newAction->setHandlingThierparty($thirdparty);
}
$this->em->persist($newAction);
$this->em->flush();
}
}
}
}

View File

@ -1,107 +1,92 @@
<?php
/*
* Copyright (C) 2016 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Export\AggregatorInterface;
use Symfony\Component\Security\Core\Role\Role;
use Chill\ActivityBundle\Repository\ActivityReasonCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Doctrine\ORM\EntityRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Join;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use RuntimeException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function array_key_exists;
use function count;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ActivityReasonAggregator implements AggregatorInterface,
ExportElementValidatedInterface
class ActivityReasonAggregator implements AggregatorInterface, ExportElementValidatedInterface
{
/**
*
* @var EntityRepository
*/
protected $categoryRepository;
protected ActivityReasonCategoryRepository $activityReasonCategoryRepository;
/**
*
* @var EntityRepository
*/
protected $reasonRepository;
protected ActivityReasonRepository $activityReasonRepository;
/**
*
* @var TranslatableStringHelper
*/
protected $stringHelper;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
EntityRepository $categoryRepository,
EntityRepository $reasonRepository,
TranslatableStringHelper $stringHelper
ActivityReasonCategoryRepository $activityReasonCategoryRepository,
ActivityReasonRepository $activityReasonRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->categoryRepository = $categoryRepository;
$this->reasonRepository = $reasonRepository;
$this->stringHelper = $stringHelper;
$this->activityReasonCategoryRepository = $activityReasonCategoryRepository;
$this->activityReasonRepository = $activityReasonRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function alterQuery(QueryBuilder $qb, $data)
{
// add select element
if ($data['level'] === 'reasons') {
if ('reasons' === $data['level']) {
$elem = 'reasons.id';
$alias = 'activity_reasons_id';
} elseif ($data['level'] === 'categories') {
} elseif ('categories' === $data['level']) {
$elem = 'category.id';
$alias = 'activity_categories_id';
} else {
throw new \RuntimeException('the data provided are not recognized');
throw new RuntimeException('The data provided are not recognized.');
}
$qb->addSelect($elem.' as '.$alias);
$qb->addSelect($elem . ' as ' . $alias);
// make a jointure only if needed
$join = $qb->getDQLPart('join');
if (
(array_key_exists('activity', $join)
&&
!$this->checkJoinAlreadyDefined($join['activity'], 'reasons')
(
array_key_exists('activity', $join)
&& !$this->checkJoinAlreadyDefined($join['activity'], 'reasons')
)
OR
(! array_key_exists('activity', $join))
|| (!array_key_exists('activity', $join))
) {
$qb->add(
'join',
array('activity' =>
new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons')
),
true);
'join',
[
'activity' => new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons'),
],
true
);
}
// join category if necessary
if ($alias === 'activity_categories_id') {
if ('activity_categories_id' === $alias) {
// add join only if needed
if (!$this->checkJoinAlreadyDefined($qb->getDQLPart('join')['activity'], 'category')) {
$qb->join('reasons.category', 'category');
@ -118,12 +103,104 @@ class ActivityReasonAggregator implements AggregatorInterface,
}
}
public function applyOn()
{
return 'activity';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add(
'level',
ChoiceType::class,
[
'choices' => [
'By reason' => 'reasons',
'By category of reason' => 'categories',
],
'multiple' => false,
'expanded' => true,
'label' => "Reason's level",
]
);
}
public function getLabels($key, array $values, $data)
{
// for performance reason, we load data from db only once
switch ($data['level']) {
case 'reasons':
$this->activityReasonRepository->findBy(['id' => $values]);
break;
case 'categories':
$this->activityReasonCategoryRepository->findBy(['id' => $values]);
break;
default:
throw new RuntimeException(sprintf("The level data '%s' is invalid.", $data['level']));
}
return function ($value) use ($data) {
if ('_header' === $value) {
return 'reasons' === $data['level'] ? 'Group by reasons' : 'Group by categories of reason';
}
switch ($data['level']) {
case 'reasons':
$r = $this->activityReasonRepository->find($value);
return sprintf(
'%s > %s',
$this->translatableStringHelper->localize($r->getCategory()->getName()),
$this->translatableStringHelper->localize($r->getName())
);
case 'categories':
$c = $this->activityReasonCategoryRepository->find($value);
return $this->translatableStringHelper->localize($c->getName());
}
};
}
public function getQueryKeys($data)
{
// add select element
if ('reasons' === $data['level']) {
return ['activity_reasons_id'];
}
if ('categories' === $data['level']) {
return ['activity_categories_id'];
}
throw new RuntimeException('The data provided are not recognised.');
}
public function getTitle()
{
return 'Aggregate by activity reason';
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['level']) {
$context
->buildViolation("The reasons's level should not be empty.")
->addViolation();
}
}
/**
* Check if a join between Activity and another alias
* Check if a join between Activity and another alias.
*
* @param Join[] $joins
* @param string $alias the alias to search for
* @return boolean
*
* @return bool
*/
private function checkJoinAlreadyDefined(array $joins, $alias)
{
@ -135,99 +212,4 @@ class ActivityReasonAggregator implements AggregatorInterface,
return false;
}
public function applyOn()
{
return 'activity';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('level', ChoiceType::class, array(
'choices' => array(
'By reason' => 'reasons',
'By category of reason' => 'categories'
),
'multiple' => false,
'expanded' => true,
'label' => 'Reason\'s level'
));
}
public function validateForm($data, ExecutionContextInterface $context)
{
if ($data['level'] === null) {
$context->buildViolation("The reasons's level should not be empty")
->addViolation();
}
}
public function getTitle()
{
return "Aggregate by activity reason";
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function getLabels($key, array $values, $data)
{
// for performance reason, we load data from db only once
switch ($data['level']) {
case 'reasons':
$this->reasonRepository->findBy(array('id' => $values));
break;
case 'categories':
$this->categoryRepository->findBy(array('id' => $values));
break;
default:
throw new \RuntimeException(sprintf("the level data '%s' is invalid",
$data['level']));
}
return function($value) use ($data) {
if ($value === '_header') {
return $data['level'] === 'reasons' ?
'Group by reasons'
:
'Group by categories of reason'
;
}
switch ($data['level']) {
case 'reasons':
/* @var $r \Chill\ActivityBundle\Entity\ActivityReason */
$r = $this->reasonRepository->find($value);
return $this->stringHelper->localize($r->getCategory()->getName())
." > "
. $this->stringHelper->localize($r->getName());
;
break;
case 'categories':
$c = $this->categoryRepository->find($value);
return $this->stringHelper->localize($c->getName());
break;
// no need for a default : the default was already set above
}
};
}
public function getQueryKeys($data)
{
// add select element
if ($data['level'] === 'reasons') {
return array('activity_reasons_id');
} elseif ($data['level'] === 'categories') {
return array ('activity_categories_id');
} else {
throw new \RuntimeException('the data provided are not recognised');
}
}
}

View File

@ -1,88 +1,54 @@
<?php
/*
* Copyright (C) 2016 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Export\AggregatorInterface;
use Symfony\Component\Security\Core\Role\Role;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Doctrine\ORM\EntityRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Closure;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ActivityTypeAggregator implements AggregatorInterface
{
/**
*
* @var EntityRepository
*/
protected $typeRepository;
/**
*
* @var TranslatableStringHelper
*/
protected $stringHelper;
const KEY = 'activity_type_aggregator';
public const KEY = 'activity_type_aggregator';
protected ActivityTypeRepository $activityTypeRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
EntityRepository $typeRepository,
TranslatableStringHelper $stringHelper
ActivityTypeRepository $activityTypeRepository,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->typeRepository = $typeRepository;
$this->stringHelper = $stringHelper;
$this->activityTypeRepository = $activityTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function alterQuery(QueryBuilder $qb, $data)
{
// add select element
// add select element
$qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY));
// add the "group by" part
$groupBy = $qb->addGroupBy(self::KEY);
}
/**
* Check if a join between Activity and another alias
*
* @param Join[] $joins
* @param string $alias the alias to search for
* @return boolean
*/
private function checkJoinAlreadyDefined(array $joins, $alias)
{
foreach ($joins as $join) {
if ($join->getAlias() === $alias) {
return true;
}
}
return false;
$qb->addGroupBy(self::KEY);
}
public function applyOn()
@ -95,37 +61,48 @@ class ActivityTypeAggregator implements AggregatorInterface
// no form required for this aggregator
}
public function getTitle()
{
return "Aggregate by activity type";
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function getLabels($key, array $values, $data)
public function getLabels($key, array $values, $data): Closure
{
// for performance reason, we load data from db only once
$this->typeRepository->findBy(array('id' => $values));
return function($value) use ($data) {
if ($value === '_header') {
$this->activityTypeRepository->findBy(['id' => $values]);
return function ($value): string {
if ('_header' === $value) {
return 'Activity type';
}
/* @var $r \Chill\ActivityBundle\Entity\ActivityType */
$t = $this->typeRepository->find($value);
$t = $this->activityTypeRepository->find($value);
return $this->stringHelper->localize($t->getName());
return $this->translatableStringHelper->localize($t->getName());
};
}
public function getQueryKeys($data)
public function getQueryKeys($data): array
{
return array(self::KEY);
return [self::KEY];
}
public function getTitle()
{
return 'Aggregate by activity type';
}
/**
* Check if a join between Activity and another alias.
*
* @param Join[] $joins
* @param string $alias the alias to search for
*
* @return bool
*/
private function checkJoinAlreadyDefined(array $joins, $alias)
{
foreach ($joins as $join) {
if ($join->getAlias() === $alias) {
return true;
}
}
return false;
}
}

View File

@ -1,51 +1,36 @@
<?php
/*
* Copyright (C) 2019 Champs Libres Cooperative <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Export\AggregatorInterface;
use Symfony\Component\Security\Core\Role\Role;
use Doctrine\ORM\Query\Expr\Join;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Doctrine\ORM\EntityManagerInterface;
use Chill\MainBundle\Entity\User;
/**
*
* Chill is a software for social workers
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class ActivityUserAggregator implements AggregatorInterface
{
/**
*
* @var EntityManagerInterface
*/
protected $em;
const KEY = 'activity_user_id';
function __construct(EntityManagerInterface $em)
{
$this->em = $em;
public const KEY = 'activity_user_id';
private UserRepository $userRepository;
public function __construct(
UserRepository $userRepository
) {
$this->userRepository = $userRepository;
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
@ -53,9 +38,9 @@ class ActivityUserAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
// add select element
// add select element
$qb->addSelect(sprintf('IDENTITY(activity.user) AS %s', self::KEY));
// add the "group by" part
$qb->addGroupBy(self::KEY);
}
@ -70,30 +55,27 @@ class ActivityUserAggregator implements AggregatorInterface
// nothing to add
}
public function getLabels($key, $values, $data): \Closure
public function getLabels($key, $values, $data): Closure
{
// preload users at once
$this->em->getRepository(User::class)
->findBy(['id' => $values]);
return function($value) {
switch ($value) {
case '_header':
return 'activity user';
default:
return $this->em->getRepository(User::class)->find($value)
->getUsername();
$this->userRepository->findBy(['id' => $values]);
return function ($value) {
if ('_header' === $value) {
return 'activity user';
}
return $this->userRepository->find($value)->getUsername();
};
}
public function getQueryKeys($data)
{
return [ self::KEY ];
return [self::KEY];
}
public function getTitle(): string
{
return "Aggregate by activity user";
return 'Aggregate by activity user';
}
}

View File

@ -1,121 +1,61 @@
<?php
/*
* Copyright (C) 2015 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
use Chill\MainBundle\Export\ExportInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Role\Role;
use Doctrine\ORM\Query;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Doctrine\ORM\EntityManagerInterface;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class CountActivity implements ExportInterface
{
/**
*
* @var EntityManagerInterface
*/
protected $entityManager;
protected ActivityRepository $activityRepository;
public function __construct(
EntityManagerInterface $em
)
{
$this->entityManager = $em;
}
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
{
ActivityRepository $activityRepository
) {
$this->activityRepository = $activityRepository;
}
public function getDescription()
public function buildForm(FormBuilderInterface $builder)
{
return "Count activities by various parameters.";
}
public function getTitle()
{
return "Count activities";
}
public function getType()
{
return 'activity';
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = array())
{
$qb = $this->entityManager->createQueryBuilder();
$centers = array_map(function($el) { return $el['center']; }, $acl);
$qb->select('COUNT(activity.id) as export_count_activity')
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'person')
;
$qb->where($qb->expr()->in('person.center', ':centers'))
->setParameter('centers', $centers)
;
return $qb;
}
public function supportsModifiers()
{
return array('person', 'activity');
}
public function requiredRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function getAllowedFormattersTypes()
{
return array(\Chill\MainBundle\Export\FormatterInterface::TYPE_TABULAR);
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription()
{
return 'Count activities by various parameters.';
}
public function getLabels($key, array $values, $data)
{
if ($key !== 'export_count_activity') {
throw new \LogicException("the key $key is not used by this export");
if ('export_count_activity' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return function($value) {
return $value === '_header' ?
'Number of activities'
:
$value
;
};
return static fn ($value) => '_header' === $value ? 'Number of activities' : $value;
}
public function getQueryKeys($data)
{
return array('export_count_activity');
return ['export_count_activity'];
}
public function getResult($qb, $data)
@ -123,4 +63,40 @@ class CountActivity implements ExportInterface
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle()
{
return 'Count activities';
}
public function getType()
{
return 'activity';
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this
->activityRepository
->createQueryBuilder('activity')
->select('COUNT(activity.id) as export_count_activity')
->join('activity.person', 'person');
$qb
->where($qb->expr()->in('person.center', ':centers'))
->setParameter('centers', $centers);
return $qb;
}
public function requiredRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function supportsModifiers()
{
return ['person', 'activity'];
}
}

View File

@ -1,69 +1,41 @@
<?php
/*
* Copyright (C) 2017 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
use Chill\MainBundle\Export\ListInterface;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Scope;
use Chill\ActivityBundle\Entity\ActivityType;
use Doctrine\ORM\Query\Expr;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Component\Security\Core\Role\Role;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use DateTime;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Doctrine\ORM\Query;
use Chill\MainBundle\Export\FormatterInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function array_key_exists;
use function count;
use function in_array;
/**
* Create a list for all activities
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ListActivity implements ListInterface
{
protected EntityManagerInterface $entityManager;
/**
*
* @var EntityManagerInterface
*/
protected $entityManager;
/**
*
* @var TranslatorInterface
*/
protected $translator;
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
protected $fields = array(
protected array $fields = [
'id',
'date',
'durationTime',
@ -74,115 +46,121 @@ class ListActivity implements ListInterface
'type_name',
'person_firstname',
'person_lastname',
'person_id'
);
'person_id',
];
protected TranslatableStringHelperInterface $translatableStringHelper;
protected TranslatorInterface $translator;
private ActivityRepository $activityRepository;
public function __construct(
EntityManagerInterface $em,
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper
)
{
EntityManagerInterface $em,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper,
ActivityRepository $activityRepository
) {
$this->entityManager = $em;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->activityRepository = $activityRepository;
}
/**
* {@inheritDoc}
*
* @param FormBuilderInterface $builder
*/
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('fields', ChoiceType::class, array(
$builder->add('fields', ChoiceType::class, [
'multiple' => true,
'expanded' => true,
'choices' => array_combine($this->fields, $this->fields),
'label' => 'Fields to include in export',
'constraints' => [new Callback(array(
'callback' => function($selected, ExecutionContextInterface $context) {
'label' => 'Fields to include in export',
'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();
}
}
))]
));
},
])],
]);
}
/**
* {@inheritDoc}
*
* @return type
*/
public function getAllowedFormattersTypes()
{
return array(FormatterInterface::TYPE_LIST);
return [FormatterInterface::TYPE_LIST];
}
public function getDescription()
{
return "List activities";
return 'List activities';
}
public function getLabels($key, array $values, $data)
{
switch ($key)
{
case 'date' :
return function($value) {
if ($value === '_header') return 'date';
switch ($key) {
case 'date':
return static function ($value) {
if ('_header' === $value) {
return 'date';
}
$date = \DateTime::createFromFormat('Y-m-d H:i:s', $value);
$date = DateTime::createFromFormat('Y-m-d H:i:s', $value);
return $date->format('d-m-Y');
};
case 'attendee':
return function($value) {
if ($value === '_header') return 'attendee';
return static function ($value) {
if ('_header' === $value) {
return 'attendee';
}
return $value ? 1 : 0;
};
case 'list_reasons' :
/* @var $activityReasonsRepository EntityRepository */
$activityRepository = $this->entityManager
->getRepository('ChillActivityBundle:Activity');
return function($value) use ($activityRepository) {
if ($value === '_header') return 'activity reasons';
case 'list_reasons':
$activityRepository = $this->activityRepository;
$activity = $activityRepository
->find($value);
return function ($value) use ($activityRepository): string {
if ('_header' === $value) {
return 'activity reasons';
}
return implode(", ", array_map(function(ActivityReason $r) {
$activity = $activityRepository->find($value);
return '"'.
return implode(', ', array_map(function (ActivityReason $r) {
return '"' .
$this->translatableStringHelper->localize($r->getCategory()->getName())
.' > '.
. ' > ' .
$this->translatableStringHelper->localize($r->getName())
.'"';
. '"';
}, $activity->getReasons()->toArray()));
};
case 'circle_name' :
return function($value) {
if ($value === '_header') return 'circle';
return $this->translatableStringHelper
->localize(json_decode($value, true));
};
case 'type_name' :
return function($value) {
if ($value === '_header') return 'activity type';
case 'circle_name':
return function ($value) {
if ('_header' === $value) {
return 'circle';
}
return $this->translatableStringHelper
->localize(json_decode($value, true));
return $this->translatableStringHelper->localize(json_decode($value, true));
};
case 'type_name':
return function ($value) {
if ('_header' === $value) {
return 'activity type';
}
return $this->translatableStringHelper->localize(json_decode($value, true));
};
default:
return function($value) use ($key) {
if ($value === '_header') return $key;
return static function ($value) use ($key) {
if ('_header' === $value) {
return $key;
}
return $value;
};
@ -209,68 +187,80 @@ class ListActivity implements ListInterface
return 'activity';
}
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 InvalidArgumentException('Any fields have been checked.');
}
$qb = $this->entityManager->createQueryBuilder();
$qb
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'person')
->join('person.center', 'center')
->andWhere('center IN (:authorized_centers)')
->setParameter('authorized_centers', $centers);
;
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'person')
->join('person.center', 'center')
->andWhere('center IN (:authorized_centers)')
->setParameter('authorized_centers', $centers);
foreach ($this->fields as $f) {
if (in_array($f, $data['fields'])) {
if (in_array($f, $data['fields'], true)) {
switch ($f) {
case 'id':
$qb->addSelect('activity.id AS id');
break;
case 'person_firstname':
$qb->addSelect('person.firstName AS person_firstname');
break;
case 'person_lastname':
$qb->addSelect('person.lastName AS person_lastname');
break;
case 'person_id':
$qb->addSelect('person.id AS person_id');
break;
case 'user_username':
$qb->join('activity.user', 'user');
$qb->addSelect('user.username AS user_username');
break;
case 'circle_name':
$qb->join('activity.scope', 'circle');
$qb->addSelect('circle.name AS circle_name');
break;
case 'type_name':
$qb->join('activity.type', 'type');
$qb->addSelect('type.name AS type_name');
break;
case 'list_reasons':
// this is a trick... The reasons is filled with the
// activity id which will be used to load reasons
$qb->addSelect('activity.id AS list_reasons');
break;
default:
$qb->addSelect(sprintf('activity.%s as %s', $f, $f));
break;
}
}
}
return $qb;
}
@ -281,7 +271,6 @@ class ListActivity implements ListInterface
public function supportsModifiers()
{
return array('activity', 'person');
return ['activity', 'person'];
}
}

View File

@ -1,154 +1,82 @@
<?php
/*
* Copyright (C) 2015 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
use Chill\MainBundle\Export\ExportInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Role\Role;
use Doctrine\ORM\Query;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Doctrine\ORM\EntityManagerInterface;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
/**
* This export allow to compute stats on activity duration.
*
*
* The desired stat must be given in constructor.
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class StatActivityDuration implements ExportInterface
{
/**
*
* @var EntityManagerInterface
*/
protected $entityManager;
const SUM = 'sum';
public const SUM = 'sum';
/**
* The action for this report.
*
* @var string
*/
protected $action;
protected string $action;
private ActivityRepository $activityRepository;
/**
* constructor
*
* @param EntityManagerInterface $em
* @param string $action the stat to perform
*/
public function __construct(
EntityManagerInterface $em,
$action = 'sum'
)
{
$this->entityManager = $em;
ActivityRepository $activityRepository,
string $action = 'sum'
) {
$this->action = $action;
}
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
{
$this->activityRepository = $activityRepository;
}
public function getDescription()
public function buildForm(FormBuilderInterface $builder)
{
if ($this->action === self::SUM) {
return "Sum activities duration by various parameters.";
}
}
public function getTitle()
{
if ($this->action === self::SUM) {
return "Sum activity duration";
}
}
public function getType()
{
return 'activity';
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = array())
{
$centers = array_map(function($el) { return $el['center']; }, $acl);
$qb = $this->entityManager->createQueryBuilder();
if ($this->action === self::SUM) {
$select = "SUM(activity.durationTime) AS export_stat_activity";
}
$qb->select($select)
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'person')
->join('person.center', 'center')
->where($qb->expr()->in('center', ':centers'))
->setParameter(':centers', $centers)
;
return $qb;
}
public function supportsModifiers()
{
return array('person', 'activity');
}
public function requiredRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function getAllowedFormattersTypes()
{
return array(\Chill\MainBundle\Export\FormatterInterface::TYPE_TABULAR);
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription()
{
if (self::SUM === $this->action) {
return 'Sum activities duration by various parameters.';
}
}
public function getLabels($key, array $values, $data)
{
if ($key !== 'export_stat_activity') {
throw new \LogicException("the key $key is not used by this export");
if ('export_stat_activity' !== $key) {
throw new LogicException(sprintf('The key %s is not used by this export', $key));
}
switch ($this->action) {
case self::SUM:
$header = "Sum of activities duration";
}
return function($value) use ($header) {
return $value === '_header' ?
$header
:
$value
;
};
$header = self::SUM === $this->action ? 'Sum of activities duration' : false;
return static fn (string $value) => '_header' === $value ? $header : $value;
}
public function getQueryKeys($data)
{
return array('export_stat_activity');
return ['export_stat_activity'];
}
public function getResult($qb, $data)
@ -156,4 +84,47 @@ class StatActivityDuration implements ExportInterface
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle()
{
if (self::SUM === $this->action) {
return 'Sum activity duration';
}
}
public function getType()
{
return 'activity';
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(
static fn (array $el): string => $el['center'],
$acl
);
$qb = $this->activityRepository->createQueryBuilder('activity');
$select = null;
if (self::SUM === $this->action) {
$select = 'SUM(activity.durationTime) AS export_stat_activity';
}
return $qb->select($select)
->join('activity.person', 'person')
->join('person.center', 'center')
->where($qb->expr()->in('center', ':centers'))
->setParameter(':centers', $centers);
}
public function requiredRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function supportsModifiers()
{
return ['person', 'activity'];
}
}

View File

@ -1,69 +1,57 @@
<?php
/*
* Copyright (C) 2017 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Export\FilterType;
use DateTime;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormError;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Doctrine\ORM\Query\Expr;
use Symfony\Component\Translation\TranslatorInterface;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ActivityDateFilter implements FilterInterface
{
/**
*
* @var TranslatorInterface
*/
protected $translator;
function __construct(TranslatorInterface $translator)
protected TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function addRole()
{
return null;
}
public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->between('activity.date', ':date_from',
':date_to');
$clause = $qb->expr()->between(
'activity.date',
':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']);
@ -74,57 +62,68 @@ class ActivityDateFilter implements FilterInterface
return 'activity';
}
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', DateType::class, array(
'label' => "Activities after this date",
'data' => new \DateTime(),
'attr' => array('class' => 'datepicker'),
'widget'=> 'single_text',
'format' => 'dd-MM-yyyy',
));
$builder->add('date_to', DateType::class, array(
'label' => "Activities before this date",
'data' => new \DateTime(),
'attr' => array('class' => 'datepicker'),
'widget'=> 'single_text',
'format' => 'dd-MM-yyyy',
));
$builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
/* @var $filterForm \Symfony\Component\Form\FormInterface */
$builder->add(
'date_from',
DateType::class,
[
'label' => 'Activities after this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
]
);
$builder->add(
'date_to',
DateType::class,
[
'label' => 'Activities before this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
]
);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var \Symfony\Component\Form\FormInterface $filterForm */
$filterForm = $event->getForm()->getParent();
$enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData();
if ($enabled === true) {
if (true === $enabled) {
// if the filter is enabled, add some validation
$form = $event->getForm();
$form = $event->getForm();
$date_from = $form->get('date_from')->getData();
$date_to = $form->get('date_to')->getData();
$date_to = $form->get('date_to')->getData();
// check that fields are not empty
if ($date_from === null) {
if (null === $date_from) {
$form->get('date_from')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')));
. 'should not be empty')
));
}
if ($date_to === null) {
if (null === $date_to) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')));
}
. 'should not be empty')
));
}
// check that date_from is before date_to
if (
($date_from !== null && $date_to !== null)
&&
$date_from >= $date_to
(null !== $date_from && null !== $date_to)
&& $date_from >= $date_to
) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This date should be after '
. 'the date given in "Implied in an activity after '
. 'this date" field')));
. 'this date" field')
));
}
}
});
@ -132,17 +131,17 @@ class ActivityDateFilter implements FilterInterface
public function describeAction($data, $format = 'string')
{
return array(
"Filtered by date of activity: only between %date_from% and %date_to%",
array(
"%date_from%" => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y')
));
return [
'Filtered by date of activity: only between %date_from% and %date_to%',
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
],
];
}
public function getTitle()
{
return "Filtered by date activity";
return 'Filtered by date activity';
}
}

View File

@ -1,88 +1,71 @@
<?php
/*
* Copyright (C) 2016 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\MainBundle\Export\FilterInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr;
use Symfony\Component\Security\Core\Role\Role;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function array_key_exists;
use function count;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ActivityReasonFilter implements FilterInterface,
ExportElementValidatedInterface
class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInterface
{
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/**
* The repository for activity reasons
*
* @var EntityRepository
*/
protected $reasonRepository;
protected ActivityReasonRepository $activityReasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
TranslatableStringHelper $helper,
EntityRepository $reasonRepository
TranslatableStringHelper $helper,
ActivityReasonRepository $activityReasonRepository
) {
$this->translatableStringHelper = $helper;
$this->reasonRepository = $reasonRepository;
$this->activityReasonRepository = $activityReasonRepository;
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$join = $qb->getDQLPart('join');
$join = $qb->getDQLPart('join');
$clause = $qb->expr()->in('reasons', ':selected_activity_reasons');
//dump($join);
// add a join to reasons only if needed
if (
(array_key_exists('activity', $join)
&&
!$this->checkJoinAlreadyDefined($join['activity'], 'reasons')
(
array_key_exists('activity', $join)
&& !$this->checkJoinAlreadyDefined($join['activity'], 'reasons')
)
OR
(! array_key_exists('activity', $join))
|| (!array_key_exists('activity', $join))
) {
$qb->add(
'join',
array('activity' => new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons')),
true
);
'join',
['activity' => new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons')],
true
);
}
if ($where instanceof Expr\Andx) {
@ -90,27 +73,10 @@ class ActivityReasonFilter implements FilterInterface,
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('selected_activity_reasons', $data['reasons']);
}
/**
* Check if a join between Activity and Reason is already defined
*
* @param Join[] $joins
* @return boolean
*/
private function checkJoinAlreadyDefined(array $joins, $alias)
{
foreach ($joins as $join) {
if ($join->getAlias() === $alias) {
return true;
}
}
return false;
}
public function applyOn()
{
@ -119,51 +85,59 @@ class ActivityReasonFilter implements FilterInterface,
public function buildForm(FormBuilderInterface $builder)
{
//create a local copy of translatableStringHelper
$helper = $this->translatableStringHelper;
$builder->add('reasons', EntityType::class, array(
'class' => 'ChillActivityBundle:ActivityReason',
'choice_label' => function (ActivityReason $reason) use ($helper) {
return $helper->localize($reason->getName());
},
'group_by' => function(ActivityReason $reason) use ($helper) {
return $helper->localize($reason->getCategory()->getName());
},
$builder->add('reasons', EntityType::class, [
'class' => ActivityReason::class,
'choice_label' => fn (ActivityReason $reason) => $this->translatableStringHelper->localize($reason->getName()),
'group_by' => fn (ActivityReason $reason) => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
'multiple' => true,
'expanded' => false
));
}
public function validateForm($data, ExecutionContextInterface $context)
{
if ($data['reasons'] === null || count($data['reasons']) === 0) {
$context->buildViolation("At least one reason must be choosen")
->addViolation();
}
'expanded' => false,
]);
}
public function getTitle()
{
return 'Filter by reason';
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function describeAction($data, $format = 'string')
{
// collect all the reasons'name used in this filter in one array
$reasonsNames = array_map(
function(ActivityReason $r) {
return "\"".$this->translatableStringHelper->localize($r->getName())."\"";
},
$this->reasonRepository->findBy(array('id' => $data['reasons']->toArray()))
);
return array("Filtered by reasons: only %list%",
["%list%" => implode(", ", $reasonsNames)]);
fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"',
$this->activityReasonRepository->findBy(['id' => $data['reasons']->toArray()])
);
return [
'Filtered by reasons: only %list%',
[
'%list%' => implode(', ', $reasonsNames),
],
];
}
public function getTitle()
{
return 'Filter by reason';
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['reasons'] || count($data['reasons']) === 0) {
$context
->buildViolation('At least one reason must be chosen')
->addViolation();
}
}
/**
* Check if a join between Activity and Reason is already defined.
*
* @param Join[] $joins
* @param mixed $alias
*/
private function checkJoinAlreadyDefined(array $joins, $alias): bool
{
foreach ($joins as $join) {
if ($join->getAlias() === $alias) {
return true;
}
}
return false;
}
}

View File

@ -1,67 +1,50 @@
<?php
/*
* Copyright (C) 2018 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\MainBundle\Export\FilterInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr;
use Symfony\Component\Security\Core\Role\Role;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
/**
*
*
*/
class ActivityTypeFilter implements FilterInterface,
ExportElementValidatedInterface
class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInterface
{
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/**
* The repository for activity reasons
*
* @var EntityRepository
*/
protected $typeRepository;
protected ActivityTypeRepository $activityTypeRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
TranslatableStringHelper $helper,
EntityRepository $typeRepository
TranslatableStringHelperInterface $translatableStringHelper,
ActivityTypeRepository $activityTypeRepository
) {
$this->translatableStringHelper = $helper;
$this->typeRepository = $typeRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->activityTypeRepository = $activityTypeRepository;
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
@ -72,27 +55,10 @@ class ActivityTypeFilter implements FilterInterface,
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('selected_activity_types', $data['types']);
}
/**
* Check if a join between Activity and Reason is already defined
*
* @param Join[] $joins
* @return boolean
*/
private function checkJoinAlreadyDefined(array $joins, $alias)
{
foreach ($joins as $join) {
if ($join->getAlias() === $alias) {
return true;
}
}
return false;
}
public function applyOn()
{
@ -101,48 +67,64 @@ class ActivityTypeFilter implements FilterInterface,
public function buildForm(FormBuilderInterface $builder)
{
//create a local copy of translatableStringHelper
$helper = $this->translatableStringHelper;
$builder->add('types', EntityType::class, array(
'class' => ActivityType::class,
'choice_label' => function (ActivityType $type) use ($helper) {
return $helper->localize($type->getName());
},
'multiple' => true,
'expanded' => false
));
}
public function validateForm($data, ExecutionContextInterface $context)
{
if ($data['types'] === null || count($data['types']) === 0) {
$context->buildViolation("At least one type must be choosen")
->addViolation();
}
$builder->add(
'types',
EntityType::class,
[
'class' => ActivityType::class,
'choice_label' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getName()),
'multiple' => true,
'expanded' => false,
]
);
}
public function getTitle()
{
return 'Filter by activity type';
}
public function addRole()
{
return new Role(ActivityStatsVoter::STATS);
}
public function describeAction($data, $format = 'string')
{
// collect all the reasons'name used in this filter in one array
$reasonsNames = array_map(
function(ActivityType $t) {
return "\"".$this->translatableStringHelper->localize($t->getName())."\"";
},
$this->typeRepository->findBy(array('id' => $data['types']->toArray()))
);
return array("Filtered by activity type: only %list%",
["%list%" => implode(", ", $reasonsNames)]);
fn (ActivityType $t): string => '"' . $this->translatableStringHelper->localize($t->getName()) . '"',
$this->activityTypeRepository->findBy(['id' => $data['types']->toArray()])
);
return [
'Filtered by activity type: only %list%',
[
'%list%' => implode(', ', $reasonsNames),
],
];
}
public function getTitle()
{
return 'Filter by activity type';
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['types'] || count($data['types']) === 0) {
$context
->buildViolation('At least one type must be chosen')
->addViolation();
}
}
/**
* Check if a join between Activity and Reason is already defined.
*
* @param Join[] $joins
* @param mixed $alias
*
* @return bool
*/
private function checkJoinAlreadyDefined(array $joins, $alias)
{
foreach ($joins as $join) {
if ($join->getAlias() === $alias) {
return true;
}
}
return false;
}
}

View File

@ -1,106 +1,85 @@
<?php
/*
* Copyright (C) 2017 Champs-Libres <info@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
use DateTime;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormError;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Doctrine\ORM\Query\Expr;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Chill\ActivityBundle\Entity\ActivityReason;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\EntityManager;
use Chill\PersonBundle\Export\Declarations;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use function count;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class PersonHavingActivityBetweenDateFilter implements FilterInterface,
ExportElementValidatedInterface
class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface
{
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/**
*
* @var EntityRepository
*/
protected $activityReasonRepository;
/**
*
* @var TranslatorInterface
*/
protected $translator;
protected ActivityReasonRepository $activityReasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
protected TranslatorInterface $translator;
public function __construct(
TranslatableStringHelper $translatableStringHelper,
EntityRepository $activityReasonRepository,
TranslatorInterface $translator
TranslatableStringHelper $translatableStringHelper,
ActivityReasonRepository $activityReasonRepository,
TranslatorInterface $translator
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->activityReasonRepository = $activityReasonRepository;
$this->translator = $translator;
$this->translator = $translator;
}
public function addRole()
{
return null;
}
public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
public function alterQuery(QueryBuilder $qb, $data)
{
// create a query for activity
$sqb = $qb->getEntityManager()->createQueryBuilder();
$sqb->select("person_person_having_activity.id")
->from("ChillActivityBundle:Activity", "activity_person_having_activity")
->join("activity_person_having_activity.person", "person_person_having_activity")
;
$sqb->select('person_person_having_activity.id')
->from('ChillActivityBundle:Activity', 'activity_person_having_activity')
->join('activity_person_having_activity.person', 'person_person_having_activity');
// add clause between date
$sqb->where("activity_person_having_activity.date BETWEEN "
. ":person_having_activity_between_date_from"
. " AND "
. ":person_having_activity_between_date_to");
$sqb->where('activity_person_having_activity.date BETWEEN '
. ':person_having_activity_between_date_from'
. ' AND '
. ':person_having_activity_between_date_to');
// add clause activity reason
$sqb->join('activity_person_having_activity.reasons',
'reasons_person_having_activity');
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
$sqb->andWhere(
$sqb->expr()->in(
'reasons_person_having_activity',
":person_having_activity_reasons")
);
$sqb->expr()->in(
'reasons_person_having_activity',
':person_having_activity_reasons'
)
);
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('person.id', $sqb->getDQL());
@ -109,12 +88,16 @@ class PersonHavingActivityBetweenDateFilter implements FilterInterface,
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('person_having_activity_between_date_from',
$data['date_from']);
$qb->setParameter('person_having_activity_between_date_to',
$data['date_to']);
$qb->setParameter(
'person_having_activity_between_date_from',
$data['date_from']
);
$qb->setParameter(
'person_having_activity_between_date_to',
$data['date_to']
);
$qb->setParameter('person_having_activity_reasons', $data['reasons']);
}
@ -123,106 +106,104 @@ class PersonHavingActivityBetweenDateFilter implements FilterInterface,
return Declarations::PERSON_IMPLIED_IN;
}
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', DateType::class, array(
'label' => "Implied in an activity after this date",
'data' => new \DateTime(),
'attr' => array('class' => 'datepicker'),
'widget'=> 'single_text',
$builder->add('date_from', DateType::class, [
'label' => 'Implied in an activity after this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
));
$builder->add('date_to', DateType::class, array(
'label' => "Implied in an activity before this date",
'data' => new \DateTime(),
'attr' => array('class' => 'datepicker'),
'widget'=> 'single_text',
]);
$builder->add('date_to', DateType::class, [
'label' => 'Implied in an activity before this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
));
$builder->add('reasons', EntityType::class, array(
'class' => 'ChillActivityBundle:ActivityReason',
'choice_label' => function (ActivityReason $reason) {
return $this->translatableStringHelper
->localize($reason->getName());
},
'group_by' => function(ActivityReason $reason) {
return $this->translatableStringHelper
->localize($reason->getCategory()->getName());
},
]);
$builder->add('reasons', EntityType::class, [
'class' => ActivityReason::class,
'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()),
'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
'data' => $this->activityReasonRepository->findAll(),
'multiple' => true,
'expanded' => false,
'label' => "Activity reasons for those activities"
));
$builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
/* @var $filterForm \Symfony\Component\Form\FormInterface */
'label' => 'Activity reasons for those activities',
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var FormInterface $filterForm */
$filterForm = $event->getForm()->getParent();
$enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData();
if ($enabled === true) {
if (true === $enabled) {
// if the filter is enabled, add some validation
$form = $event->getForm();
$form = $event->getForm();
$date_from = $form->get('date_from')->getData();
$date_to = $form->get('date_to')->getData();
$date_to = $form->get('date_to')->getData();
// check that fields are not empty
if ($date_from === null) {
if (null === $date_from) {
$form->get('date_from')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')));
. 'should not be empty')
));
}
if ($date_to === null) {
if (null === $date_to) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')));
}
. 'should not be empty')
));
}
// check that date_from is before date_to
if (
($date_from !== null && $date_to !== null)
&&
$date_from >= $date_to
(null !== $date_from && null !== $date_to)
&& $date_from >= $date_to
) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This date '
. 'should be after the date given in "Implied in an '
. 'activity after this date" field')));
. 'activity after this date" field')
));
}
}
});
}
public function validateForm($data, ExecutionContextInterface $context)
{
if ($data['reasons'] === null || count($data['reasons']) === 0) {
$context->buildViolation("At least one reason must be choosen")
->addViolation();
}
}
public function describeAction($data, $format = 'string')
{
return array(
"Filtered by person having an activity between %date_from% and "
. "%date_to% with reasons %reasons_name%",
array(
"%date_from%" => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
"%reasons_name%" => implode(", ", array_map(
function (ActivityReason $r) {
return '"'.$this->translatableStringHelper->
localize($r->getName()).'"';
},
$data['reasons']))
));
return [
'Filtered by person having an activity between %date_from% and '
. '%date_to% with reasons %reasons_name%',
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
'%reasons_name%' => implode(
', ',
array_map(
fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"',
$data['reasons']
)
),
],
];
}
public function getTitle()
{
return "Filtered by person having an activity in a period";
return 'Filtered by person having an activity in a period';
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['reasons'] || count($data['reasons']) === 0) {
$context->buildViolation('At least one reason must be chosen')
->addViolation();
}
}
}

View File

@ -1,13 +1,22 @@
<?php
/**
* 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\ActivityBundle\Form;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
class ActivityPresenceType extends AbstractType
{
@ -15,19 +24,19 @@ class ActivityPresenceType extends AbstractType
{
$builder
->add('name', TranslatableStringFormType::class)
->add('active', ChoiceType::class, array(
'choices' => array(
->add('active', ChoiceType::class, [
'choices' => [
'Yes' => true,
'No' => false
),
'expanded' => true
));
'No' => false,
],
'expanded' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(array(
'data_class' => ActivityPresence::class
));
$resolver->setDefaults([
'data_class' => ActivityPresence::class,
]);
}
}

View File

@ -1,25 +1,29 @@
<?php
/**
* 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\ActivityBundle\Form;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
class ActivityReasonCategoryType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TranslatableStringFormType::class)
->add('active', CheckboxType::class, array('required' => false))
;
->add('active', CheckboxType::class, ['required' => false]);
}
/**
@ -27,9 +31,9 @@ class ActivityReasonCategoryType extends AbstractType
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Chill\ActivityBundle\Entity\ActivityReasonCategory'
));
$resolver->setDefaults([
'data_class' => 'Chill\ActivityBundle\Entity\ActivityReasonCategory',
]);
}
/**

View File

@ -1,37 +1,38 @@
<?php
/**
* 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\ActivityBundle\Form;
use Chill\ActivityBundle\Form\Type\TranslatableActivityReasonCategory;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Chill\ActivityBundle\Form\Type\TranslatableActivityReasonCategory;
class ActivityReasonType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TranslatableStringFormType::class)
->add('active', CheckboxType::class, array('required' => false))
->add('category', TranslatableActivityReasonCategory::class)
;
->add('active', CheckboxType::class, ['required' => false])
->add('category', TranslatableActivityReasonCategory::class);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Chill\ActivityBundle\Entity\ActivityReason'
));
$resolver->setDefaults([
'data_class' => 'Chill\ActivityBundle\Entity\ActivityReason',
]);
}
/**

View File

@ -1,5 +1,14 @@
<?php
/**
* 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\ActivityBundle\Form;
use Chill\ActivityBundle\Entity\Activity;
@ -7,54 +16,57 @@ use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Form\Type\UserPickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateInterval;
use DateTime;
use DateTimeZone;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectManager;
use RuntimeException;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\Persistence\ObjectManager;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Chill\MainBundle\Entity\User;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Chill\MainBundle\Form\Type\UserPickerType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Form\Type\ChillDateType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\CallbackTransformer;
use Chill\PersonBundle\Form\DataTransformer\PersonToIdTransformer;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use function in_array;
class ActivityType extends AbstractType
{
protected User $user;
protected AuthorizationHelper $authorizationHelper;
protected ObjectManager $om;
protected TranslatableStringHelper $translatableStringHelper;
protected SocialActionRender $socialActionRender;
protected SocialIssueRender $socialIssueRender;
protected SocialActionRender $socialActionRender;
protected array $timeChoices;
public function __construct (
protected TranslatableStringHelper $translatableStringHelper;
protected User $user;
public function __construct(
TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper,
ObjectManager $om,
@ -64,7 +76,7 @@ class ActivityType extends AbstractType
SocialActionRender $socialActionRender
) {
if (!$tokenStorage->getToken()->getUser() instanceof User) {
throw new \RuntimeException("you should have a valid user");
throw new RuntimeException('you should have a valid user');
}
$this->user = $tokenStorage->getToken()->getUser();
@ -100,12 +112,13 @@ class ActivityType extends AbstractType
'center' => $options['center'],
'role' => $options['role'],
// TODO make required again once scope and rights are fixed
'required' => false
'required' => false,
]);
}
/** @var ? \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingPeriod */
$accompanyingPeriod = NULL;
$accompanyingPeriod = null;
if ($options['accompanyingPeriod']) {
$accompanyingPeriod = $options['accompanyingPeriod'];
}
@ -113,49 +126,53 @@ class ActivityType extends AbstractType
if ($activityType->isVisible('socialIssues') && $accompanyingPeriod) {
$builder->add('socialIssues', HiddenType::class);
$builder->get('socialIssues')
->addModelTransformer(new CallbackTransformer(
function (iterable $socialIssuesAsIterable): string {
$socialIssueIds = [];
foreach ($socialIssuesAsIterable as $value) {
$socialIssueIds[] = $value->getId();
}
return implode(',', $socialIssueIds);
},
function (?string $socialIssuesAsString): array {
if (null === $socialIssuesAsString) {
return [];
}
return array_map(
fn(string $id): ?SocialIssue => $this->om->getRepository(SocialIssue::class)->findOneBy(['id' => (int) $id]),
explode(',', $socialIssuesAsString)
);
}
))
;
->addModelTransformer(new CallbackTransformer(
static function (iterable $socialIssuesAsIterable): string {
$socialIssueIds = [];
foreach ($socialIssuesAsIterable as $value) {
$socialIssueIds[] = $value->getId();
}
return implode(',', $socialIssueIds);
},
function (?string $socialIssuesAsString): array {
if (null === $socialIssuesAsString) {
return [];
}
return array_map(
fn (string $id): ?SocialIssue => $this->om->getRepository(SocialIssue::class)->findOneBy(['id' => (int) $id]),
explode(',', $socialIssuesAsString)
);
}
));
}
if ($activityType->isVisible('socialActions') && $accompanyingPeriod) {
$builder->add('socialActions', HiddenType::class);
$builder->get('socialActions')
->addModelTransformer(new CallbackTransformer(
function (iterable $socialActionsAsIterable): string {
$socialActionIds = [];
foreach ($socialActionsAsIterable as $value) {
$socialActionIds[] = $value->getId();
}
return implode(',', $socialActionIds);
},
function (?string $socialActionsAsString): array {
if (null === $socialActionsAsString) {
return [];
}
return array_map(
fn(string $id): ?SocialAction => $this->om->getRepository(SocialAction::class)->findOneBy(['id' => (int) $id]),
explode(',', $socialActionsAsString)
);
}
))
;
->addModelTransformer(new CallbackTransformer(
static function (iterable $socialActionsAsIterable): string {
$socialActionIds = [];
foreach ($socialActionsAsIterable as $value) {
$socialActionIds[] = $value->getId();
}
return implode(',', $socialActionIds);
},
function (?string $socialActionsAsString): array {
if (null === $socialActionsAsString) {
return [];
}
return array_map(
fn (string $id): ?SocialAction => $this->om->getRepository(SocialAction::class)->findOneBy(['id' => (int) $id]),
explode(',', $socialActionsAsString)
);
}
));
}
if ($activityType->isVisible('date')) {
@ -188,7 +205,7 @@ class ActivityType extends AbstractType
'choice_label' => function (ActivityPresence $activityPresence) {
return $this->translatableStringHelper->localize($activityPresence->getName());
},
'query_builder' => function (EntityRepository $er) {
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('a')
->where('a.active = true');
},
@ -200,7 +217,7 @@ class ActivityType extends AbstractType
'label' => $activityType->getLabel('user'),
'required' => $activityType->isRequired('user'),
'center' => $options['center'],
'role' => $options['role']
'role' => $options['role'],
]);
}
@ -213,8 +230,8 @@ class ActivityType extends AbstractType
'choice_label' => function (ActivityReason $activityReason) {
return $this->translatableStringHelper->localize($activityReason->getName());
},
'attr' => array('class' => 'select2 '),
'query_builder' => function (EntityRepository $er) {
'attr' => ['class' => 'select2 '],
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('a')
->where('a.active = true');
},
@ -232,43 +249,53 @@ class ActivityType extends AbstractType
if ($activityType->isVisible('persons')) {
$builder->add('persons', HiddenType::class);
$builder->get('persons')
->addModelTransformer(new CallbackTransformer(
function (iterable $personsAsIterable): string {
$personIds = [];
foreach ($personsAsIterable as $value) {
$personIds[] = $value->getId();
}
return implode(',', $personIds);
},
function (?string $personsAsString): array {
return array_map(
fn(string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]),
explode(',', $personsAsString)
);
}
))
;
->addModelTransformer(new CallbackTransformer(
static function (iterable $personsAsIterable): string {
$personIds = [];
foreach ($personsAsIterable as $value) {
$personIds[] = $value->getId();
}
return implode(',', $personIds);
},
function (?string $personsAsString): array {
if (null === $personsAsString) {
return [];
}
return array_map(
fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]),
explode(',', $personsAsString)
);
}
));
}
if ($activityType->isVisible('thirdParties')) {
$builder->add('thirdParties', HiddenType::class);
$builder->get('thirdParties')
->addModelTransformer(new CallbackTransformer(
function (iterable $thirdpartyAsIterable): string {
$thirdpartyIds = [];
foreach ($thirdpartyAsIterable as $value) {
$thirdpartyIds[] = $value->getId();
}
return implode(',', $thirdpartyIds);
},
function (?string $thirdpartyAsString): array {
return array_map(
fn(string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]),
explode(',', $thirdpartyAsString)
);
}
))
;
->addModelTransformer(new CallbackTransformer(
static function (iterable $thirdpartyAsIterable): string {
$thirdpartyIds = [];
foreach ($thirdpartyAsIterable as $value) {
$thirdpartyIds[] = $value->getId();
}
return implode(',', $thirdpartyIds);
},
function (?string $thirdpartyAsString): array {
if (null === $thirdpartyAsString) {
return [];
}
return array_map(
fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]),
explode(',', $thirdpartyAsString)
);
}
));
}
if ($activityType->isVisible('documents')) {
@ -278,46 +305,51 @@ class ActivityType extends AbstractType
'required' => $activityType->isRequired('documents'),
'allow_add' => true,
'button_add_label' => 'activity.Insert a document',
'button_remove_label' => 'activity.Remove a document'
'button_remove_label' => 'activity.Remove a document',
]);
}
if ($activityType->isVisible('users')) {
$builder->add('users', HiddenType::class);
$builder->get('users')
->addModelTransformer(new CallbackTransformer(
function (iterable $usersAsIterable): string {
$userIds = [];
foreach ($usersAsIterable as $value) {
$userIds[] = $value->getId();
}
return implode(',', $userIds);
},
function (?string $usersAsString): array {
return array_map(
fn(string $id): ?User => $this->om->getRepository(User::class)->findOneBy(['id' => (int) $id]),
explode(',', $usersAsString)
);
}
))
;
->addModelTransformer(new CallbackTransformer(
static function (iterable $usersAsIterable): string {
$userIds = [];
foreach ($usersAsIterable as $value) {
$userIds[] = $value->getId();
}
return implode(',', $userIds);
},
function (?string $usersAsString): array {
if (null === $usersAsString) {
return [];
}
return array_map(
fn (string $id): ?User => $this->om->getRepository(User::class)->findOneBy(['id' => (int) $id]),
explode(',', $usersAsString)
);
}
));
}
if ($activityType->isVisible('location')) {
$builder->add('location', HiddenType::class)
->get('location')
->addModelTransformer(new CallbackTransformer(
function (?Location $location): string {
static function (?Location $location): string {
if (null === $location) {
return '';
}
return $location->getId();
return (string) $location->getId();
},
function (?string $id): Location {
function (?string $id): ?Location {
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
}
))
;
));
}
if ($activityType->isVisible('emergency')) {
@ -347,7 +379,7 @@ class ActivityType extends AbstractType
->addModelTransformer($durationTimeTransformer);
$builder->get($fieldName)
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $formEvent) use (
->addEventListener(FormEvents::PRE_SET_DATA, static function (FormEvent $formEvent) use (
$timeChoices,
$builder,
$durationTimeTransformer,
@ -356,24 +388,25 @@ class ActivityType extends AbstractType
) {
// set the timezone to GMT, and fix the difference between current and GMT
// the datetimetransformer will then handle timezone as GMT
$timezoneUTC = new \DateTimeZone('GMT');
/* @var $data \DateTime */
$data = $formEvent->getData() === NULL ?
\DateTime::createFromFormat('U', 300) :
$timezoneUTC = new DateTimeZone('GMT');
/** @var DateTime $data */
$data = $formEvent->getData() === null ?
DateTime::createFromFormat('U', '300') :
$formEvent->getData();
$seconds = $data->getTimezone()->getOffset($data);
$data->setTimeZone($timezoneUTC);
$data->add(new \DateInterval('PT'.$seconds.'S'));
$data->add(new DateInterval('PT' . $seconds . 'S'));
// test if the timestamp is in the choices.
// If not, recreate the field with the new timestamp
if (!in_array($data->getTimestamp(), $timeChoices)) {
if (!in_array($data->getTimestamp(), $timeChoices, true)) {
// the data are not in the possible values. add them
$timeChoices[$data->format('H:i')] = $data->getTimestamp();
$form = $builder->create($fieldName, ChoiceType::class, array_merge(
$durationTimeOptions, [
$durationTimeOptions,
[
'choices' => $timeChoices,
'auto_initialize' => false
'auto_initialize' => false,
]
));
$form->addModelTransformer($durationTimeTransformer);
@ -383,12 +416,10 @@ class ActivityType extends AbstractType
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Activity::class
'data_class' => Activity::class,
]);
$resolver
@ -396,8 +427,7 @@ class ActivityType extends AbstractType
->setAllowedTypes('center', ['null', 'Chill\MainBundle\Entity\Center'])
->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role')
->setAllowedTypes('activityType', \Chill\ActivityBundle\Entity\ActivityType::class)
->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null'])
;
->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null']);
}
public function getBlockPrefix(): string

View File

@ -1,14 +1,23 @@
<?php
/**
* 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\ActivityBundle\Form;
use Chill\ActivityBundle\Entity\ActivityTypeCategory;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
class ActivityTypeCategoryType extends AbstractType
{
@ -16,24 +25,23 @@ class ActivityTypeCategoryType extends AbstractType
{
$builder
->add('name', TranslatableStringFormType::class)
->add('active', ChoiceType::class, array(
'choices' => array(
->add('active', ChoiceType::class, [
'choices' => [
'Yes' => true,
'No' => false
),
'expanded' => true
))
'No' => false,
],
'expanded' => true,
])
->add('ordering', NumberType::class, [
'required' => true,
'scale' => 5
])
;
'scale' => 5,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(array(
'data_class' => ActivityTypeCategory::class
));
$resolver->setDefaults([
'data_class' => ActivityTypeCategory::class,
]);
}
}

View File

@ -1,18 +1,27 @@
<?php
/**
* 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\ActivityBundle\Form;
use Chill\ActivityBundle\Entity\ActivityTypeCategory;
use Chill\ActivityBundle\Form\Type\ActivityFieldPresence;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
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\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
class ActivityTypeType extends AbstractType
{
@ -30,9 +39,9 @@ class ActivityTypeType extends AbstractType
->add('active', ChoiceType::class, [
'choices' => [
'Yes' => true,
'No' => false
'No' => false,
],
'expanded' => true
'expanded' => true,
])
->add('category', EntityType::class, [
'class' => ActivityTypeCategory::class,
@ -42,20 +51,20 @@ class ActivityTypeType extends AbstractType
])
->add('ordering', NumberType::class, [
'required' => true,
'scale' => 5
])
;
'scale' => 5,
]);
$fields = [
'persons', 'user', 'date', 'place', 'persons',
'persons', 'user', 'date', 'location', 'persons',
'thirdParties', 'durationTime', 'travelTime', 'attendee',
'reasons', 'comment', 'sentReceived', 'documents',
'emergency', 'accompanyingPeriod', 'socialData', 'users'
'emergency', 'socialIssues', 'socialActions', 'users',
];
foreach ($fields as $field) {
$builder
->add($field.'Visible', ActivityFieldPresence::class)
->add($field.'Label', TextType::class, [
->add($field . 'Visible', ActivityFieldPresence::class)
->add($field . 'Label', TextType::class, [
'required' => false,
'empty_data' => '',
]);
@ -64,8 +73,8 @@ class ActivityTypeType extends AbstractType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => \Chill\ActivityBundle\Entity\ActivityType::class
));
$resolver->setDefaults([
'data_class' => \Chill\ActivityBundle\Entity\ActivityType::class,
]);
}
}

View File

@ -1,5 +1,14 @@
<?php
/**
* 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\ActivityBundle\Form\Type;
use Chill\ActivityBundle\Entity\ActivityType;
@ -9,21 +18,21 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class ActivityFieldPresence extends AbstractType
{
public function getParent()
{
return ChoiceType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
[
'choices' => [
'Invisible' => ActivityType::FIELD_INVISIBLE,
'Optional' => ActivityType::FIELD_OPTIONAL,
'Required' => ActivityType::FIELD_REQUIRED,
],
)
]
);
}
public function getParent()
{
return ChoiceType::class;
}
}

View File

@ -1,54 +1,39 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2020, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\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 Doctrine\ORM\EntityRepository;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Templating\Entity\ActivityReasonRender;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* FormType to choose amongst activity reasons
*
* FormType to choose amongst activity reasons.
*/
class TranslatableActivityReason extends AbstractType
{
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/**
*
* @var ActivityReasonRender
*/
protected $reasonRender;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
public function __construct(
TranslatableStringHelper $translatableStringHelper,
ActivityReasonRender $reasonRender
@ -57,6 +42,30 @@ class TranslatableActivityReason extends AbstractType
$this->reasonRender = $reasonRender;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'class' => 'ChillActivityBundle:ActivityReason',
'choice_label' => function (ActivityReason $choice) {
return $this->reasonRender->renderString($choice, []);
},
'group_by' => function (ActivityReason $choice): ?string {
if (null !== $category = $choice->getCategory()) {
return $this->translatableStringHelper->localize($category->getName());
}
return null;
},
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('r')
->where('r.active = true');
},
'attr' => ['class' => ' select2 '],
]
);
}
public function getBlockPrefix()
{
return 'translatable_activity_reason';
@ -66,28 +75,4 @@ class TranslatableActivityReason extends AbstractType
{
return EntityType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'class' => 'ChillActivityBundle:ActivityReason',
'choice_label' => function(ActivityReason $choice) {
return $this->reasonRender->renderString($choice, []);
},
'group_by' => function(ActivityReason $choice): ?string {
if (null !== $category = $choice->getCategory()) {
return $this->translatableStringHelper->localize($category->getName());
}
return null;
},
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('r')
->where('r.active = true');
},
'attr' => [ 'class' => ' select2 ']
)
);
}
}

View File

@ -1,40 +1,25 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Description of TranslatableActivityReasonCategory
*
* @author Champs-Libres Coop
* Description of TranslatableActivityReasonCategory.
*/
class TranslatableActivityReasonCategory extends AbstractType
{
/**
@ -47,6 +32,21 @@ class TranslatableActivityReasonCategory extends AbstractType
$this->requestStack = $requestStack;
}
public function configureOptions(OptionsResolver $resolver)
{
$locale = $this->requestStack->getCurrentRequest()->getLocale();
$resolver->setDefaults(
[
'class' => 'ChillActivityBundle:ActivityReasonCategory',
'choice_label' => 'name[' . $locale . ']',
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.active = true');
},
]
);
}
public function getBlockPrefix()
{
return 'translatable_activity_reason_category';
@ -56,19 +56,4 @@ class TranslatableActivityReasonCategory extends AbstractType
{
return EntityType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$locale = $this->requestStack->getCurrentRequest()->getLocale();
$resolver->setDefaults(
array(
'class' => 'ChillActivityBundle:ActivityReasonCategory',
'choice_label' => 'name['.$locale.']',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.active = true');
}
)
);
}
}

View File

@ -1,60 +1,66 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Description of TranslatableActivityType
*
* @author Champs-Libres Coop
*/
class TranslatableActivityType extends AbstractType
{
protected ActivityTypeRepository $activityTypeRepository;
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
protected $activityTypeRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
TranslatableStringHelper $helper,
EntityRepository $activityTypeRepository
)
{
TranslatableStringHelperInterface $helper,
ActivityTypeRepository $activityTypeRepository
) {
$this->translatableStringHelper = $helper;
$this->activityTypeRepository = $activityTypeRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var QueryBuilder $qb */
$qb = $options['query_builder'];
if (true === $options['active_only']) {
$qb->where($qb->expr()->eq('at.active', ':active'));
$qb->setParameter('active', true, Types::BOOLEAN);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'class' => ActivityType::class,
'active_only' => true,
'query_builder' => $this->activityTypeRepository
->createQueryBuilder('at'),
'choice_label' => function (ActivityType $type) {
return $this->translatableStringHelper->localize($type->getName());
},
]
);
}
public function getBlockPrefix()
{
return 'translatable_activity_type';
@ -64,30 +70,4 @@ class TranslatableActivityType extends AbstractType
{
return EntityType::class;
}
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options) {
/* @var $qb \Doctrine\ORM\QueryBuilder */
$qb = $options['query_builder'];
if ($options['active_only'] === true) {
$qb->where($qb->expr()->eq('at.active', ':active'));
$qb->setParameter('active', true, \Doctrine\DBAL\Types\Type::BOOLEAN);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'class' => 'ChillActivityBundle:ActivityType',
'active_only' => true,
'query_builder' => $this->activityTypeRepository
->createQueryBuilder('at'),
'choice_label' => function (ActivityType $type) {
return $this->translatableStringHelper->localize($type->getName());
}
)
);
}
}

View File

@ -1,47 +1,54 @@
<?php
/**
* 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\ActivityBundle\Menu;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{
protected TokenStorageInterface $tokenStorage;
protected AuthorizationHelper $authorizationHelper;
protected Security $security;
protected TranslatorInterface $translator;
public function __construct(
TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper,
Security $security,
TranslatorInterface $translator
) {
$this->security = $security;
$this->translator = $translator;
$this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage;
}
public static function getMenuIds(): array
{
return ['accompanyingCourse'];
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$period = $parameters['accompanyingCourse'];
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) {
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()
&& $this->security->isGranted(ActivityVoter::SEE, $period)) {
$menu->addChild($this->translator->trans('Activity'), [
'route' => 'chill_activity_activity_list',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
]])
], ])
->setExtras(['order' => 40]);
}
}
public static function getMenuIds(): array
{
return ['accompanyingCourse'];
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* 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\ActivityBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use function in_array;
final class AdminMenuBuilder implements LocalMenuBuilderInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
return;
}
if (in_array($menuId, ['admin_index', 'admin_section'], true)) {
$menu->addChild('Activities', [
'route' => 'chill_admin_activity_index',
])
->setExtras([
'order' => 2000,
'explain' => 'Activity configuration',
]);
} else {
$menu
->addChild('Activities', [
'route' => 'chill_admin_activity_index',
])
->setExtras([
'order' => '60',
]);
}
}
public static function getMenuIds(): array
{
return ['admin_index', 'admin_section', 'admin_activity'];
}
}

View File

@ -1,68 +1,56 @@
<?php
/*
* Copyright (C) 2018 Julien Fastré <julien.fastre@champs-libres.coop>
/**
* 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 <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Menu;
declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Symfony\Component\Translation\TranslatorInterface;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class PersonMenuBuilder implements LocalMenuBuilderInterface
{
/**
*
* @var TranslatorInterface
*/
protected $translator;
/**
*
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
/**
* @var TranslatorInterface
*/
protected $translator;
public function __construct(
AuthorizationCheckerInterface $authorizationChecker,
TranslatorInterface $translator)
{
TranslatorInterface $translator
) {
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
}
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(ActivityVoter::SEE, $person)) {
$menu->addChild(
$this->translator->trans('Activity list'), [
$this->translator->trans('Activity list'),
[
'route' => 'chill_activity_activity_list',
'routeParameters' => [ 'person_id' => $person->getId() ],
])
->setExtra('order', 201)
;
'routeParameters' => ['person_id' => $person->getId()],
]
)
->setExtra('order', 201);
}
}

View File

@ -1,17 +1,21 @@
<?php
/**
* 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\ActivityBundle\Notification;
use Chill\MainBundle\Entity\Notification;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\Entity\Notification;
final class ActivityNotificationRenderer
{
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() == Activity::class;
}
public function getTemplate()
{
return '@ChillActivity/Activity/showInNotification.html.twig';
@ -21,4 +25,9 @@ final class ActivityNotificationRenderer
{
return ['notification' => $notification];
}
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() === Activity::class;
}
}

View File

@ -1,60 +1,47 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2021, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Scope;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query\Expr\Orx;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
use function count;
use function in_array;
final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
{
private AuthorizationHelper $authorizationHelper;
private TokenStorageInterface $tokenStorage;
private ActivityRepository $repository;
private CenterResolverDispatcherInterface $centerResolverDispatcher;
private EntityManagerInterface $em;
private ActivityRepository $repository;
private Security $security;
private CenterResolverDispatcher $centerResolverDispatcher;
private TokenStorageInterface $tokenStorage;
public function __construct(
AuthorizationHelper $authorizationHelper,
CenterResolverDispatcher $centerResolverDispatcher,
CenterResolverDispatcherInterface $centerResolverDispatcher,
TokenStorageInterface $tokenStorage,
ActivityRepository $repository,
EntityManagerInterface $em,
@ -68,34 +55,11 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
$this->security = $security;
}
/**
* @param Person $person
* @param string $role
* @param int|null $start
* @param int|null $limit
* @param array $orderBy
* @return array|Activity[]
*/
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
{
$user = $this->security->getUser();
$center = $this->centerResolverDispatcher->resolveCenter($person);
if (0 === count($orderBy)) {
$orderBy = ['date' => 'DESC'];
}
$reachableScopes = $this->authorizationHelper
->getReachableCircles($user, $role, $center);
return $this->em->getRepository(Activity::class)
->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start);
;
}
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
{
$user = $this->security->getUser();
$center = $this->centerResolverDispatcher->resolveCenter($period);
if (0 === count($orderBy)) {
$orderBy = ['date' => 'DESC'];
}
@ -107,6 +71,27 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy);
}
/**
* @param array $orderBy
*
* @return Activity[]|array
*/
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
{
$user = $this->security->getUser();
$center = $this->centerResolverDispatcher->resolveCenter($person);
if (0 === count($orderBy)) {
$orderBy = ['date' => 'DESC'];
}
$reachableScopes = $this->authorizationHelper
->getReachableCircles($user, $role, $center);
return $this->em->getRepository(Activity::class)
->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start);
}
public function queryTimelineIndexer(string $context, array $args = []): array
{
$metadataActivity = $this->em->getClassMetadata(Activity::class);
@ -115,15 +100,15 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
[$where, $parameters] = $this->getWhereClause($context, $args);
return [
'id' => $metadataActivity->getTableName()
.'.'.$metadataActivity->getColumnName('id'),
'type' => 'activity',
'date' => $metadataActivity->getTableName()
.'.'.$metadataActivity->getColumnName('date'),
'FROM' => $from,
'WHERE' => $where,
'parameters' => $parameters
];
'id' => $metadataActivity->getTableName()
. '.' . $metadataActivity->getColumnName('id'),
'type' => 'activity',
'date' => $metadataActivity->getTableName()
. '.' . $metadataActivity->getColumnName('date'),
'FROM' => $from,
'WHERE' => $where,
'parameters' => $parameters,
];
}
private function getFromClauseCenter(array $args): string
@ -132,13 +117,12 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
$metadataPerson = $this->em->getClassMetadata(Person::class);
$associationMapping = $metadataActivity->getAssociationMapping('person');
return $metadataActivity->getTableName().' JOIN '
.$metadataPerson->getTableName().' ON '
.$metadataPerson->getTableName().'.'.
return $metadataActivity->getTableName() . ' JOIN '
. $metadataPerson->getTableName() . ' ON '
. $metadataPerson->getTableName() . '.' .
$associationMapping['joinColumns'][0]['referencedColumnName']
.' = '
.$associationMapping['joinColumns'][0]['name']
;
. ' = '
. $associationMapping['joinColumns'][0]['name'];
}
private function getWhereClause(string $context, array $args): array
@ -149,21 +133,22 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
$metadataActivity = $this->em->getClassMetadata(Activity::class);
$metadataPerson = $this->em->getClassMetadata(Person::class);
$activityToPerson = $metadataActivity->getAssociationMapping('person')['joinColumns'][0]['name'];
$activityToScope = $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'];
$activityToScope = $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'];
$personToCenter = $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'];
// acls:
$role = new Role(ActivityVoter::SEE);
$reachableCenters = $this->authorizationHelper->getReachableCenters($this->tokenStorage->getToken()->getUser(),
$role);
$reachableCenters = $this->authorizationHelper->getReachableCenters(
$this->tokenStorage->getToken()->getUser(),
$role
);
if (count($reachableCenters) === 0) {
// insert a dummy condition
return 'FALSE = TRUE';
}
if ($context === 'person') {
if ('person' === $context) {
// we start with activities having the person_id linked to person
$where .= sprintf('%s = ? AND ', $activityToPerson);
$parameters[] = $person->getId();
@ -172,21 +157,22 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
// we add acl (reachable center and scopes)
$where .= '('; // first loop for the for centers
$centersI = 0; // like centers#i
foreach ($reachableCenters as $center) {
// we pass if not in centers
if (!\in_array($center, $args['centers'])) {
if (!in_array($center, $args['centers'], true)) {
continue;
}
// we get all the reachable scopes for this center
$reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), $role, $center);
// we get the ids for those scopes
$reachablesScopesId = array_map(
function(Scope $scope) { return $scope->getId(); },
static function (Scope $scope) { return $scope->getId(); },
$reachableScopes
);
// if not the first center
if ($centersI > 0) {
if (0 < $centersI) {
$where .= ') OR (';
}
@ -199,21 +185,20 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
$scopesI = 0; //like scope#i
foreach ($reachablesScopesId as $scopeId) {
if ($scopesI > 0) {
if (0 < $scopesI) {
$where .= ' OR ';
}
$where .= sprintf(' %s.%s = ? ', $metadataActivity->getTableName(), $activityToScope);
$parameters[] = $scopeId;
$scopesI ++;
++$scopesI;
}
// close loop for scopes
$where .= ') ';
$centersI++;
++$centersI;
}
// close loop for centers
$where .= ')';
return [$where, $parameters];
}
}

View File

@ -1,5 +1,14 @@
<?php
/**
* 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\ActivityBundle\Repository;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
@ -8,12 +17,12 @@ use Chill\PersonBundle\Entity\Person;
interface ActivityACLAwareRepositoryInterface
{
/**
* @return array|Activity[]
*/
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
/**
* @return array|Activity[]
* @return Activity[]|array
*/
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
/**
* @return Activity[]|array
*/
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 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\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\ActivityReasonCategory;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method ActivityReasonCategory|null find($id, $lockMode = null, $lockVersion = null)
* @method ActivityReasonCategory|null findOneBy(array $criteria, array $orderBy = null)
* @method ActivityReasonCategory[] findAll()
* @method ActivityReasonCategory[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ActivityReasonCategoryRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ActivityReasonCategory::class);
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 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\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\ActivityReason;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method ActivityReason|null find($id, $lockMode = null, $lockVersion = null)
* @method ActivityReason|null findOneBy(array $criteria, array $orderBy = null)
* @method ActivityReason[] findAll()
* @method ActivityReason[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ActivityReasonRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ActivityReason::class);
}
}

View File

@ -1,25 +1,14 @@
<?php
/*
/**
* Chill is a software for social workers
*
* Copyright (C) 2021, Champs Libres Cooperative SCRLFS,
* <http://www.champs-libres.coop>, <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\Activity;
@ -29,10 +18,10 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method AccompanyingPeriodParticipation|null find($id, $lockMode = null, $lockVersion = null)
* @method AccompanyingPeriodParticipation|null findOneBy(array $criteria, array $orderBy = null)
* @method AccompanyingPeriodParticipation[] findAll()
* @method AccompanyingPeriodParticipation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
* @method Activity|null find($id, $lockMode = null, $lockVersion = null)
* @method Activity|null findOneBy(array $criteria, array $orderBy = null)
* @method Activity[] findAll()
* @method Activity[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ActivityRepository extends ServiceEntityRepository
{
@ -42,14 +31,49 @@ class ActivityRepository extends ServiceEntityRepository
}
/**
* @param $person
* @param array $scopes
* @param string[] $orderBy
* @param int $limit
* @param int $offset
* @return array|Activity[]
* @deprecated use @see{ActivityACLAwareRepositoryInterface::findByAccompanyingPeriod}
*
* @return Activity[]
*/
public function findByPersonImplied(Person $person, array $scopes, ?array $orderBy = [ 'date' => 'DESC'], ?int $limit = 100, ?int $offset = 0): array
public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $scopes, ?bool $allowNullScope = false, ?int $limit = 100, ?int $offset = 0, array $orderBy = ['date' => 'desc']): array
{
$qb = $this->createQueryBuilder('a');
$qb->select('a');
if (!$allowNullScope) {
$qb
->where($qb->expr()->in('a.scope', ':scopes'))
->setParameter('scopes', $scopes);
} else {
$qb
->where(
$qb->expr()->orX(
$qb->expr()->in('a.scope', ':scopes'),
$qb->expr()->isNull('a.scope')
)
)
->setParameter('scopes', $scopes);
}
$qb
->andWhere(
$qb->expr()->eq('a.accompanyingPeriod', ':period')
)
->setParameter('period', $period);
foreach ($orderBy as $k => $dir) {
$qb->addOrderBy('a.' . $k, $dir);
}
$qb->setMaxResults($limit)->setFirstResult($offset);
return $qb->getQuery()->getResult();
}
/**
* @return Activity[]
*/
public function findByPersonImplied(Person $person, array $scopes, ?array $orderBy = ['date' => 'DESC'], ?int $limit = 100, ?int $offset = 0): array
{
$qb = $this->createQueryBuilder('a');
$qb->select('a');
@ -63,63 +87,14 @@ class ActivityRepository extends ServiceEntityRepository
':person MEMBER OF a.persons'
)
)
->setParameter('person', $person)
;
->setParameter('person', $person);
foreach ($orderBy as $k => $dir) {
$qb->addOrderBy('a.'.$k, $dir);
$qb->addOrderBy('a.' . $k, $dir);
}
$qb->setMaxResults($limit)->setFirstResult($offset);
return $qb->getQuery()
->getResult();
}
/**
* @param AccompanyingPeriod $period
* @param array $scopes
* @param int|null $limit
* @param int|null $offset
* @param array|string[] $orderBy
* @return array|Activity[]
*/
public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $scopes, ?bool $allowNullScope = false, ?int $limit = 100, ?int $offset = 0, array $orderBy = ['date' => 'desc']): array
{
$qb = $this->createQueryBuilder('a');
$qb->select('a');
if (!$allowNullScope) {
$qb
->where($qb->expr()->in('a.scope', ':scopes'))
->setParameter('scopes', $scopes)
;
} else {
$qb
->where(
$qb->expr()->orX(
$qb->expr()->in('a.scope', ':scopes'),
$qb->expr()->isNull('a.scope')
)
)
->setParameter('scopes', $scopes)
;
}
$qb
->andWhere(
$qb->expr()->eq('a.accompanyingPeriod', ':period')
)
->setParameter('period', $period)
;
foreach ($orderBy as $k => $dir) {
$qb->addOrderBy('a.'.$k, $dir);
}
$qb->setMaxResults($limit)->setFirstResult($offset);
return $qb->getQuery()
->getResult();
return $qb->getQuery()->getResult();
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 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\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\ActivityTypeCategory;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method ActivityTypeCategory|null find($id, $lockMode = null, $lockVersion = null)
* @method ActivityTypeCategory|null findOneBy(array $criteria, array $orderBy = null)
* @method ActivityTypeCategory[] findAll()
* @method ActivityTypeCategory[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ActivityTypeCategoryRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ActivityTypeCategory::class);
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 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\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\ActivityType;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method ActivityType|null find($id, $lockMode = null, $lockVersion = null)
* @method ActivityType|null findOneBy(array $criteria, array $orderBy = null)
* @method ActivityType[] findAll()
* @method ActivityType[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ActivityTypeRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ActivityType::class);
}
}

View File

@ -24,14 +24,16 @@ div.new-activity-select-type {
}
//// ACTIVITY LIST PAGE
// precise badge-title specific details
// precise dashboard specific details
p.date-label {
display: inline-block;
margin: 0 0.5em 0 0;
font-weight: 700;
font-size: 18pt;
}
div.dashboard,
h2.badge-title {
div.duration {
font-size: smaller;
padding-left: 1em;
margin-top: 1em;
}
ul.list-content {
font-size: 70%;
list-style-type: none;
@ -39,16 +41,13 @@ h2.badge-title {
margin: 0;
li {
margin-bottom: 0.2em;
// exception: change bg color for action badges above badge-title
// exception: change bg color for action badges above dashboard
.bg-light {
background-color: $chill-light-gray !important;
}
}
}
}
div.main {
padding: 1em;
}
//// ACTIVITY SHOW AND FORM PAGES
// Exceptions for flex-bloc in concerned-groups

View File

@ -1,7 +1,7 @@
<template>
<concerned-groups></concerned-groups>
<social-issues-acc></social-issues-acc>
<location></location>
<concerned-groups v-if="hasPerson"></concerned-groups>
<social-issues-acc v-if="hasSocialIssues"></social-issues-acc>
<location v-if="hasLocation"></location>
</template>
<script>
@ -11,6 +11,7 @@ import Location from './components/Location.vue';
export default {
name: "App",
props: ['hasSocialIssues', 'hasLocation', 'hasPerson'],
components: {
ConcernedGroups,
SocialIssuesAcc,

View File

@ -1,4 +1,5 @@
import { getSocialIssues } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api.js';
import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods';
/*
* Load socialActions by socialIssue (id)
@ -12,33 +13,20 @@ const getSocialActionByIssue = (id) => {
});
};
/*
* Load Locations
*/
const getLocations = () => {
const url = `/api/1.0/main/location.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
const getLocations = () => fetchResults('/api/1.0/main/location.json');
const getLocationTypes = () => fetchResults('/api/1.0/main/location-type.json');
/*
* Load Location Types
* Load Location Type by defaultFor
* @param {string} entity - can be "person" or "thirdparty"
*/
const getLocationTypes = () => {
const url = `/api/1.0/main/location-type.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
const getLocationTypeByDefaultFor = (entity) => {
return getLocationTypes().then(results =>
results.filter(t => t.defaultFor === entity)[0]
);
};
/*
* Post a Location
*/
const postLocation = (body) => {
const url = `/api/1.0/main/location.json`;
return fetch(url, {
@ -59,5 +47,6 @@ export {
getSocialActionByIssue,
getLocations,
getLocationTypes,
getLocationTypeByDefaultFor,
postLocation
};

View File

@ -1,33 +1,33 @@
<template>
<teleport to="#add-persons">
<teleport to="#add-persons" v-if="isComponentVisible">
<div class="flex-bloc concerned-groups" :class="getContext">
<persons-bloc
v-for="bloc in contextPersonsBlocs"
v-bind:key="bloc.key"
v-bind:bloc="bloc"
v-bind:setPersonsInBloc="setPersonsInBloc">
</persons-bloc>
</div>
<div class="flex-bloc concerned-groups" :class="getContext">
<persons-bloc
v-for="bloc in contextPersonsBlocs"
v-bind:key="bloc.key"
v-bind:bloc="bloc"
v-bind:blocWidth="getBlocWidth"
v-bind:setPersonsInBloc="setPersonsInBloc">
</persons-bloc>
</div>
<div v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0">
<ul class="list-suggest add-items inline">
<li v-for="p in suggestedEntities" @click="addSuggestedEntity(p)">
<span>{{ p.text }}</span>
</li>
</ul>
</div>
<div v-if="getContext === 'accompanyingCourse' && filterSuggestedPersons.length > 0">
<ul>
<li v-for="p in filterSuggestedPersons" @click="addNewPerson(p)">
{{ p.text }}
</li>
</ul>
</div>
<add-persons
buttonTitle="activity.add_persons"
modalTitle="activity.add_persons"
v-bind:key="addPersons.key"
v-bind:options="addPersonsOptions"
@addNewPersons="addNewPersons"
ref="addPersons">
</add-persons>
<add-persons
buttonTitle="activity.add_persons"
modalTitle="activity.add_persons"
v-bind:key="addPersons.key"
v-bind:options="addPersons.options"
@addNewPersons="addNewPersons"
ref="addPersons">
</add-persons>
</teleport>
</teleport>
</template>
<script>
@ -52,38 +52,35 @@ export default {
{ key: 'personsAssociated',
title: 'activity.bloc_persons_associated',
persons: [],
included: false
included: window.activity ? window.activity.activityType.personsVisible !== 0 : true
},
{ key: 'personsNotAssociated',
title: 'activity.bloc_persons_not_associated',
persons: [],
included: false
included: window.activity ? window.activity.activityType.personsVisible !== 0 : true
},
{ key: 'thirdparty',
title: 'activity.bloc_thirdparty',
persons: [],
included: true
included: window.activity ? window.activity.activityType.thirdPartiesVisible !== 0 : true
},
{ key: 'users',
title: 'activity.bloc_users',
persons: [],
included: true
included: window.activity ? window.activity.activityType.usersVisible !== 0 : true
},
],
addPersons: {
key: 'activity',
options: {
type: ['person', 'thirdparty', 'user'],
priority: null,
uniq: false,
button: {
size: 'btn-sm'
}
}
key: 'activity'
}
}
},
computed: {
isComponentVisible() {
return window.activity
? (window.activity.activityType.personsVisible !== 0 || window.activity.activityType.thirdPartiesVisible !== 0 || window.activity.activityType.usersVisible !== 0)
: true
},
...mapState({
persons: state => state.activity.persons,
thirdParties: state => state.activity.thirdParties,
@ -91,14 +88,41 @@ export default {
accompanyingCourse: state => state.activity.accompanyingPeriod
}),
...mapGetters([
'filterSuggestedPersons'
'suggestedEntities'
]),
getContext() {
return (this.accompanyingCourse) ? "accompanyingCourse" : "person";
},
contextPersonsBlocs() {
return this.personsBlocs.filter(bloc => bloc.included !== false);
}
},
addPersonsOptions() {
let optionsType = [];
if (window.activity) {
if (window.activity.activityType.personsVisible !== 0) {
optionsType.push('person')
}
if (window.activity.activityType.thirdPartiesVisible !== 0) {
optionsType.push('thirdparty')
}
if (window.activity.activityType.usersVisible !== 0) {
optionsType.push('user')
}
} else {
optionsType = ['person', 'thirdparty', 'user'];
}
return {
type: optionsType,
priority: null,
uniq: false,
button: {
size: 'btn-sm'
}
}
},
getBlocWidth() {
return Math.round(100/(this.contextPersonsBlocs.length)) + '%';
}
},
mounted() {
this.setPersonsInBloc();
@ -168,7 +192,7 @@ export default {
},
addNewPersons({ selected, modal }) {
console.log('@@@ CLICK button addNewPersons', selected);
selected.forEach(function(item) {
selected.forEach((item) => {
this.$store.dispatch('addPersonsInvolved', item);
}, this
);
@ -176,7 +200,7 @@ export default {
modal.showModal = false;
this.setPersonsInBloc();
},
addNewPerson(person) {
addSuggestedEntity(person) {
this.$store.dispatch('addPersonsInvolved', { result: person, type: 'person' });
this.setPersonsInBloc();
},

View File

@ -1,12 +1,8 @@
<template>
<li>
<span class="badge bg-primary" :title="person.text">
<span class="chill_denomination">
{{ textCutted }}
</span>
<a class="fa fa-fw fa-times"
@click.prevent="$emit('remove', person)">
</a>
<span :title="person.text">
<span class="chill_denomination">{{ textCutted }}</span>
<a @click.prevent="$emit('remove', person)"></a>
</span>
</li>
</template>

View File

@ -1,11 +1,11 @@
<template>
<div class="item-bloc">
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
<div class="item-row">
<div class="item-col">
<h4>{{ $t(bloc.title) }}</h4>
</div>
<div class="item-col">
<ul class="list-content">
<ul class="list-suggest remove-items">
<person-badge
v-for="person in bloc.persons"
v-bind:key="person.id"
@ -25,7 +25,7 @@ export default {
components: {
PersonBadge
},
props: ['bloc', 'setPersonsInBloc'],
props: ['bloc', 'setPersonsInBloc', 'blocWidth'],
methods: {
removePerson(item) {
console.log('@@ CLICK remove person: item', item);

View File

@ -2,10 +2,9 @@
<teleport to="#location">
<div class="mb-3 row">
<label class="col-form-label col-sm-4">
{{ $t('activity.location') }}
{{ $t("activity.location") }}
</label>
<div class="col-sm-8">
<VueMultiselect
name="selectLocation"
id="selectLocation"
@ -17,81 +16,159 @@
:placeholder="$t('activity.choose_location')"
:custom-label="customLabel"
:options="locations"
v-model="location">
group-values="locations"
group-label="locationGroup"
v-model="location"
>
</VueMultiselect>
<new-location @saveNewLocation="saveNewLocation"></new-location>
<new-location v-bind:locations="locations"></new-location>
</div>
</div>
</teleport>
</template>
<script>
import { mapState } from "vuex";
import VueMultiselect from 'vue-multiselect';
import NewLocation from './Location/NewLocation.vue';
import { getLocations, postLocation } from '../api.js';
import { mapState, mapGetters } from "vuex";
import VueMultiselect from "vue-multiselect";
import NewLocation from "./Location/NewLocation.vue";
import { getLocations, getLocationTypeByDefaultFor } from "../api.js";
export default {
name: "Location",
components: {
NewLocation,
VueMultiselect
VueMultiselect,
},
data() {
return {
locations: []
}
locations: [],
};
},
computed: {
...mapState(['activity']),
...mapState(["activity"]),
...mapGetters(["suggestedEntities"]),
location: {
get() {
return this.activity.location;
},
set(value) {
this.$store.dispatch('updateLocation', value);
}
}
this.$store.dispatch("updateLocation", value);
},
},
},
mounted() {
this.getLocationsList();
getLocations().then(
(results) => {
getLocationTypeByDefaultFor('person').then(
(personLocationType) => {
if (personLocationType) {
const personLocation = this.makeAccompanyingPeriodLocation(personLocationType);
const concernedPersonsLocation =
this.makeConcernedPersonsLocation(personLocationType);
getLocationTypeByDefaultFor('thirdparty').then(
thirdpartyLocationType => {
const concernedThirdPartiesLocation =
this.makeConcernedThirdPartiesLocation(thirdpartyLocationType);
this.locations = [
{
locationGroup: 'Localisation du parcours',
locations: [personLocation]
},
{
locationGroup: 'Parties concernées',
locations: [...concernedPersonsLocation, ...concernedThirdPartiesLocation]
},
{
locationGroup: 'Autres localisations',
locations: results
}
];
}
)
} else {
this.locations = [
{
locationGroup: 'Localisations',
locations: response.results
}
];
}
if (window.default_location_id) {
let location = this.locations.filter(
(l) => l.id === window.default_location_id
);
this.$store.dispatch("updateLocation", location);
}
}
)
})
},
methods: {
getLocationsList() {
getLocations().then(response => new Promise(resolve => {
console.log('getLocations', response);
this.locations = response.results;
resolve();
}))
labelAccompanyingCourseLocation(value) {
return `${value.address.text} (${value.locationType.title.fr})`
},
customLabel(value) {
return `${value.locationType.title.fr} ${value.name}`;
return value.locationType
? value.name
? value.name === '__AccompanyingCourseLocation__'
? this.labelAccompanyingCourseLocation(value)
: `${value.name} (${value.locationType.title.fr})`
: value.locationType.title.fr
: '';
},
saveNewLocation(selected) {
console.log('saveNewLocation', selected);
let body = {
makeConcernedPersonsLocation(locationType) {
let locations = [];
this.suggestedEntities.forEach(
(e) => {
if (e.type === 'person' && e.current_household_address !== null){
locations.push({
type: 'location',
id: -this.suggestedEntities.indexOf(e)*10,
onthefly: true,
name: e.text,
address: {
id: e.current_household_address.address_id,
},
locationType: locationType
});
}
}
)
return locations;
},
makeConcernedThirdPartiesLocation(locationType) {
let locations = [];
this.suggestedEntities.forEach(
(e) => {
if (e.type === 'thirdparty' && e.address !== null){
locations.push({
type: 'location',
id: -this.suggestedEntities.indexOf(e)*10,
onthefly: true,
name: e.text,
address: { id: e.address.address_id },
locationType: locationType
});
}
}
)
return locations;
},
makeAccompanyingPeriodLocation(locationType) {
const accPeriodLocation = this.activity.accompanyingPeriod.location;
return {
type: 'location',
name: selected.name,
id: -1,
onthefly: true,
name: '__AccompanyingCourseLocation__',
address: {
id: selected.addressId
id: accPeriodLocation.address_id,
text: `${accPeriodLocation.text} - ${accPeriodLocation.postcode.code} ${accPeriodLocation.postcode.name}`
},
locationType: {
id: selected.type,
type: 'location-type'
},
phonenumber1: selected.phonenumber1,
phonenumber2: selected.phonenumber2,
email: selected.email,
locationType: locationType
}
postLocation(body).then(location => new Promise(resolve => {
console.log('postLocation', location);
this.locations.push(location);
this.$store.dispatch('updateLocation', location);
resolve();
}));
}
}
}
},
};
</script>

View File

@ -1,75 +1,87 @@
<template>
<div>
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-create" @click="openModal">
{{ $t('activity.create_new_location') }}
</a>
</li>
</ul>
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-create" @click="openModal">
{{ $t('activity.create_new_location') }}
</a>
</li>
</ul>
<teleport to="body">
<modal v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false">
<teleport to="body">
<modal v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false">
<template v-slot:header>
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
</template>
<template v-slot:body>
<form>
<div class="form-floating mb-3">
<p v-if="errors.length">
<b>{{ $t('activity.errors') }}</b>
<ul>
<li v-for="error in errors" :key="error">{{ error }}</li>
</ul>
</p>
</div>
<template v-slot:header>
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
</template>
<template v-slot:body>
<div class="form-floating mb-3">
<select class="form-select form-select-lg" id="type" required v-model="selectType">
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
<option v-for="t in locationTypes" :value="t" :key="t.id">
{{ t.title.fr }}
</option>
</select>
<label>{{ $t('activity.location_fields.type') }}</label>
</div>
<add-address
:context="addAddress.context"
:options="addAddress.options"
:addressChangedCallback="submitNewAddress"
ref="addAddress">
</add-address>
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
<label for="name">{{ $t('activity.location_fields.name') }}</label>
</div>
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
<label for="name">{{ $t('activity.location_fields.name') }}</label>
</div>
<add-address
:context="addAddress.context"
:options="addAddress.options"
:addressChangedCallback="submitNewAddress"
v-if="showAddAddress"
ref="addAddress">
</add-address>
<div class="form-floating mb-3">
<select class="form-select form-select-lg" id="type" v-model="selectType">
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
<option v-for="t in locationTypes" :value="t.id">
{{ t.title.fr }}
</option>
</select>
<label>{{ $t('activity.location_fields.type') }}</label>
</div>
<div class="form-floating mb-3" v-if="showContactData">
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label>
</div>
<div class="form-floating mb-3" v-if="hasPhonenumber1">
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
<label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label>
</div>
<div class="form-floating mb-3" v-if="showContactData">
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
<label for="email">{{ $t('activity.location_fields.email') }}</label>
</div>
</form>
</template>
<template v-slot:footer>
<button class="btn btn-save"
@click.prevent="saveNewLocation"
>
{{ $t('action.save') }}
</button>
</template>
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label>
</div>
<div class="form-floating mb-3" v-if="hasPhonenumber1">
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
<label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label>
</div>
<div class="form-floating mb-3">
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
<label for="email">{{ $t('activity.location_fields.email') }}</label>
</div>
</template>
<template v-slot:footer>
<button class="btn btn-save"
@click.prevent="$emit('saveNewLocation', selected); modal.showModal = false;">
{{ $t('action.save') }}
</button>
</template>
</modal>
</teleport>
</modal>
</teleport>
</div>
</template>
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import { mapState } from "vuex";
import { getLocationTypes } from "../../api";
import { getLocationTypes, postLocation } from "../../api";
export default {
name: "NewLocation",
@ -77,11 +89,12 @@ export default {
Modal,
AddAddress,
},
emits: ['saveNewLocation'],
props: ['locations'],
data() {
return {
errors: [],
selected: {
type: {},
type: null,
name: null,
addressId: null,
phonenumber1: null,
@ -117,7 +130,7 @@ export default {
return this.selected.type;
},
set(value) {
this.selected.type = value
this.selected.type = value;
}
},
inputName: {
@ -154,22 +167,96 @@ export default {
},
hasPhonenumber1() {
return this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== "";
}
},
showAddAddress() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.addressRequired !== 'never') {
cond = true;
}
}
return cond;
},
showContactData() {
let cond = false;
if (this.selected.type) {
if (this.selected.type.contactData !== 'never') {
cond = true;
}
}
return cond;
},
},
mounted() {
this.getLocationTypesList();
},
methods: {
checkForm() {
let cond = true;
this.errors = [];
if (!this.selected.type) {
this.errors.push('Type de localisation requis');
cond = false;
} else {
if (this.selected.type.addressRequired === 'required' && !this.selected.addressId) {
this.errors.push('Adresse requise');
cond = false;
}
if (this.selected.type.contactData === 'required' && !this.selected.phonenumber1) {
this.errors.push('Numéro de téléphone requis');
cond = false;
}
if (this.selected.type.contactData === 'required' && !this.selected.email) {
this.errors.push('Adresse email requise');
cond = false;
}
}
return cond;
},
getLocationTypesList() {
getLocationTypes().then(response => new Promise(resolve => {
console.log('getLocationTypes', response);
this.locationTypes = response.results.filter(t => t.availableForUsers === true);
resolve();
}))
getLocationTypes().then(results => {
this.locationTypes = results.filter(t => t.availableForUsers === true);
})
},
openModal() {
this.modal.showModal = true;
},
saveNewLocation() {
if (this.checkForm()) {
console.log('saveNewLocation', this.selected);
let body = {
type: 'location',
name: this.selected.name,
locationType: {
id: this.selected.type.id,
type: 'location-type'
},
phonenumber1: this.selected.phonenumber1,
phonenumber2: this.selected.phonenumber2,
email: this.selected.email,
};
if (this.selected.addressId) {
body = Object.assign(body, {
address: {
id: this.selected.addressId
}
});
}
postLocation(body)
.then(
location => new Promise(resolve => {
this.locations.push(location);
this.$store.dispatch('updateLocation', location);
resolve();
this.modal.showModal = false;
})
).catch(
err => {
this.errors.push(err.message);
}
);
};
},
submitNewAddress(payload) {
console.log('submitNewAddress', payload);
this.selected.addressId = payload.addressId;

View File

@ -50,19 +50,26 @@
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
</div>
<check-social-action
v-if="socialIssuesSelected.length || socialActionsSelected.length"
v-for="action in socialActionsList"
v-bind:key="action.id"
v-bind:action="action"
v-bind:selection="socialActionsSelected"
@updateSelected="updateActionsSelected">
</check-social-action>
<span v-else class="inline-choice chill-no-data-statement mt-3">
<span v-else-if="socialIssuesSelected.length === 0" class="inline-choice chill-no-data-statement mt-3">
{{ $t('activity.select_first_a_social_issue') }}
</span>
<template v-else-if="socialActionsList.length > 0">
<check-social-action
v-if="socialIssuesSelected.length || socialActionsSelected.length"
v-for="action in socialActionsList"
v-bind:key="action.id"
v-bind:action="action"
v-bind:selection="socialActionsSelected"
@updateSelected="updateActionsSelected">
</check-social-action>
</template>
<span v-else-if="actionAreLoaded && socialActionsList.length === 0" class="inline-choice chill-no-data-statement mt-3">
{{ $t('activity.social_action_list_empty') }}
</span>
</div>
</div>
@ -85,7 +92,8 @@ export default {
data() {
return {
issueIsLoading: false,
actionIsLoading: false
actionIsLoading: false,
actionAreLoaded: false,
}
},
computed: {
@ -109,6 +117,7 @@ export default {
/* Load others issues in multiselect
*/
this.issueIsLoading = true;
this.actionAreLoaded = false;
getSocialIssues().then(response => new Promise((resolve, reject) => {
this.$store.commit('updateIssuesOther', response.results);
@ -141,6 +150,8 @@ export default {
this.$store.commit('filterList', 'actions');
this.issueIsLoading = false;
this.actionAreLoaded = true;
this.updateActionsList();
resolve();
}));
},
@ -173,7 +184,6 @@ export default {
to get social actions concerned
*/
updateActionsList() {
//console.log('updateActionsList');
this.resetActionsList();
this.socialIssuesSelected.forEach(item => {
@ -188,6 +198,7 @@ export default {
this.$store.commit('filterList', 'actions');
this.actionIsLoading = false;
this.actionAreLoaded = true;
resolve();
}));
}, this);
@ -196,6 +207,7 @@ export default {
*/
resetActionsList() {
this.$store.commit('resetActionsList');
this.actionAreLoaded = false;
this.socialActionsSelected.forEach(item => {
this.$store.commit('addActionInList', item);
}, this);

View File

@ -1,18 +1,18 @@
<template>
<span class="inline-choice">
<div class="form-check">
<input class="form-check-input"
type="checkbox"
type="checkbox"
v-model="selected"
name="action"
v-bind:id="action.id"
v-bind:id="action.id"
v-bind:value="action"
/>
<label class="form-check-label" v-bind:for="action.id">
{{ action.text }}
<span class="badge bg-light text-dark">{{ action.text }}</span>
</label>
</div>
</span>
</template>
@ -34,3 +34,15 @@ export default {
}
}
</script>
<style lang="scss" scoped>
@import 'ChillMainAssets/module/bootstrap/shared';
@import 'ChillPersonAssets/chill/scss/mixins';
@import 'ChillMainAssets/chill/scss/chill_variables';
span.badge {
@include badge_social($social-action-color);
font-size: 95%;
margin-bottom: 5px;
margin-right: 1em;
}
</style>

View File

@ -1,18 +1,18 @@
<template>
<span class="inline-choice">
<div class="form-check">
<input class="form-check-input"
type="checkbox"
type="checkbox"
v-model="selected"
name="issue"
v-bind:id="issue.id"
v-bind:id="issue.id"
v-bind:value="issue"
/>
<label class="form-check-label" v-bind:for="issue.id">
{{ issue.text }}
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
</label>
</div>
</span>
</template>
@ -34,3 +34,15 @@ export default {
}
}
</script>
<style lang="scss" scoped>
@import 'ChillMainAssets/module/bootstrap/shared';
@import 'ChillPersonAssets/chill/scss/mixins';
@import 'ChillMainAssets/chill/scss/chill_variables';
span.badge {
@include badge_social($social-issue-color);
font-size: 95%;
margin-bottom: 5px;
margin-right: 1em;
}
</style>

View File

@ -4,10 +4,12 @@ const activityMessages = {
fr: {
activity: {
//
errors: "Le formulaire contient des erreurs",
social_issues: "Problématiques sociales",
choose_other_social_issue: "Ajouter une autre problématique sociale...",
social_actions: "Actions d'accompagnement",
select_first_a_social_issue: "Sélectionnez d'abord une problématique sociale",
social_action_list_empty: "Aucune action sociale disponible",
//
add_persons: "Ajouter des personnes concernées",

View File

@ -7,8 +7,19 @@ import App from './App.vue';
const i18n = _createI18n(activityMessages);
const hasSocialIssues = document.querySelector('#social-issues-acc') !== null;
const hasLocation = document.querySelector('#location') !== null;
const hasPerson = document.querySelector('#add-persons') !== null;
const app = createApp({
template: `<app></app>`,
template: `<app :hasSocialIssues="hasSocialIssues", :hasLocation="hasLocation", :hasPerson="hasPerson"></app>`,
data() {
return {
hasSocialIssues,
hasLocation,
hasPerson,
};
}
})
.use(store)
.use(i18n)

View File

@ -1,190 +1,338 @@
import 'es6-promise/auto';
import { createStore } from 'vuex';
import { postLocation } from './api';
const debug = process.env.NODE_ENV !== 'production';
//console.log('window.activity', window.activity);
const addIdToValue = (string, id) => {
let array = string ? string.split(',') : [];
array.push(id.toString());
let str = array.join();
return str;
let array = string ? string.split(',') : [];
array.push(id.toString());
let str = array.join();
return str;
};
const removeIdFromValue = (string, id) => {
let array = string.split(',');
array = array.filter(el => el !== id.toString());
let str = array.join();
return str;
let array = string.split(',');
array = array.filter(el => el !== id.toString());
let str = array.join();
return str;
};
const store = createStore({
strict: debug,
state: {
activity: window.activity,
socialIssuesOther: [],
socialActionsList: [],
},
getters: {
filterSuggestedPersons(state) {
if (typeof(state.activity.accompanyingPeriod) === 'undefined') {
return [];
}
let existingPersonIds = state.activity.persons.map(p => p.id);
return state.activity.accompanyingPeriod.participations.filter(p => p.endDate === null)
.map(p => p.person)
.filter(p => !existingPersonIds.includes(p.id))
}
},
mutations: {
// SocialIssueAcc
addIssueInList(state, issue) {
//console.log('add issue list', issue.id);
state.activity.accompanyingPeriod.socialIssues.push(issue);
},
addIssueSelected(state, issue) {
//console.log('add issue selected', issue.id);
state.activity.socialIssues.push(issue);
},
updateIssuesSelected(state, issues) {
//console.log('update issues selected', issues);
state.activity.socialIssues = issues;
},
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);
},
resetActionsList(state) {
//console.log('reset list actions');
state.socialActionsList = [];
},
addActionInList(state, action) {
//console.log('add action list', action.id);
state.socialActionsList.push(action);
},
updateActionsSelected(state, actions) {
//console.log('update actions selected', actions);
state.activity.socialActions = actions;
},
filterList(state, list) {
const filterList = (list) => {
// remove duplicates entries
list = list.filter((value, index) => list.findIndex(array => array.id === value.id) === index);
// alpha sort
list.sort((a,b) => (a.text > b.text) ? 1 : ((b.text > a.text) ? -1 : 0));
return list;
};
if (list === 'issues') {
state.activity.accompanyingPeriod.socialIssues = filterList(state.activity.accompanyingPeriod.socialIssues);
}
if (list === 'actions') {
state.socialActionsList = filterList(state.socialActionsList);
}
strict: debug,
state: {
activity: window.activity,
socialIssuesOther: [],
socialActionsList: [],
},
getters: {
suggestedEntities(state) {
if (typeof state.activity.accompanyingPeriod === "undefined") {
return [];
}
const allEntities = [
...store.getters.suggestedPersons,
...store.getters.suggestedRequestor,
...store.getters.suggestedUser,
...store.getters.suggestedResources,
];
const uniqueIds = [
...new Set(allEntities.map((i) => `${i.type}-${i.id}`)),
];
return Array.from(
uniqueIds,
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0]
);
},
suggestedPersons(state) {
const existingPersonIds = state.activity.persons.map((p) => p.id);
return state.activity.activityType.personsVisible === 0
? []
: state.activity.accompanyingPeriod.participations
.filter((p) => p.endDate === null)
.map((p) => p.person)
.filter((p) => !existingPersonIds.includes(p.id));
},
suggestedRequestor(state) {
if (state.activity.accompanyingPeriod.requestor === null) {
return [];
}
const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(
(p) => p.id
);
// ConcernedGroups
addPersonsInvolved(state, payload) {
//console.log('### mutation addPersonsInvolved', payload.result.type);
switch (payload.result.type) {
case 'person':
state.activity.persons.push(payload.result);
break;
case 'thirdparty':
state.activity.thirdParties.push(payload.result);
break;
case 'user':
state.activity.users.push(payload.result);
break;
};
return [state.activity.accompanyingPeriod.requestor].filter(
(r) =>
(r.type === "person" &&
!existingPersonIds.includes(r.id) &&
state.activity.activityType.personsVisible !== 0) ||
(r.type === "thirdparty" &&
!existingThirdPartyIds.includes(r.id) &&
state.activity.activityType.thirdPartiesVisible !== 0)
);
},
suggestedUser(state) {
const existingUserIds = state.activity.users.map((p) => p.id);
return state.activity.activityType.usersVisible === 0
? []
: [state.activity.accompanyingPeriod.user].filter(
(u) => u !== null && !existingUserIds.includes(u.id)
);
},
suggestedResources(state) {
const resources = state.activity.accompanyingPeriod.resources;
const existingPersonIds = state.activity.persons.map((p) => p.id);
const existingThirdPartyIds = state.activity.thirdParties.map(
(p) => p.id
);
return state.activity.accompanyingPeriod.resources
.map((r) => r.resource)
.filter(
(r) =>
(r.type === "person" &&
!existingPersonIds.includes(r.id) &&
state.activity.activityType.personsVisible !== 0) ||
(r.type === "thirdparty" &&
!existingThirdPartyIds.includes(r.id) &&
state.activity.activityType.thirdPartiesVisible !== 0)
);
},
},
removePersonInvolved(state, payload) {
//console.log('### mutation removePersonInvolved', payload.type);
switch (payload.type) {
case 'person':
state.activity.persons = state.activity.persons.filter(person => person !== payload);
break;
case 'thirdparty':
state.activity.thirdParties = state.activity.thirdParties.filter(thirdparty => thirdparty !== payload);
break;
case 'user':
state.activity.users = state.activity.users.filter(user => user !== payload);
break;
};
mutations: {
// SocialIssueAcc
addIssueInList(state, issue) {
//console.log('add issue list', issue.id);
state.activity.accompanyingPeriod.socialIssues.push(issue);
},
addIssueSelected(state, issue) {
//console.log('add issue selected', issue.id);
state.activity.socialIssues.push(issue);
},
updateIssuesSelected(state, issues) {
//console.log('update issues selected', issues);
state.activity.socialIssues = issues;
},
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
);
},
resetActionsList(state) {
//console.log('reset list actions');
state.socialActionsList = [];
},
addActionInList(state, action) {
//console.log('add action list', action.id);
state.socialActionsList.push(action);
},
updateActionsSelected(state, actions) {
//console.log('update actions selected', actions);
state.activity.socialActions = actions;
},
filterList(state, list) {
const filterList = (list) => {
// remove duplicates entries
list = list.filter(
(value, index) =>
list.findIndex((array) => array.id === value.id) ===
index
);
// alpha sort
list.sort((a, b) =>
a.text > b.text ? 1 : b.text > a.text ? -1 : 0
);
return list;
};
if (list === "issues") {
state.activity.accompanyingPeriod.socialIssues = filterList(
state.activity.accompanyingPeriod.socialIssues
);
}
if (list === "actions") {
state.socialActionsList = filterList(state.socialActionsList);
}
},
// ConcernedGroups
addPersonsInvolved(state, payload) {
console.log("### mutation addPersonsInvolved", payload);
switch (payload.result.type) {
case "person":
state.activity.persons.push(payload.result);
break;
case "thirdparty":
state.activity.thirdParties.push(payload.result);
break;
case "user":
state.activity.users.push(payload.result);
break;
}
},
removePersonInvolved(state, payload) {
//console.log('### mutation removePersonInvolved', payload.type);
switch (payload.type) {
case "person":
state.activity.persons = state.activity.persons.filter(
(person) => person !== payload
);
break;
case "thirdparty":
state.activity.thirdParties =
state.activity.thirdParties.filter(
(thirdparty) => thirdparty !== payload
);
break;
case "user":
state.activity.users = state.activity.users.filter(
(user) => user !== payload
);
break;
}
},
updateLocation(state, value) {
console.log("### mutation: updateLocation", value);
state.activity.location = value;
},
},
updateLocation(state, value) {
console.log('### mutation: updateLocation', value);
state.activity.location = value;
}
},
actions: {
addIssueSelected({ commit }, issue) {
let aSocialIssues = document.getElementById("chill_activitybundle_activity_socialIssues");
aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id);
commit('addIssueSelected', issue);
actions: {
addIssueSelected({ commit }, issue) {
let aSocialIssues = document.getElementById(
"chill_activitybundle_activity_socialIssues"
);
aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id);
commit("addIssueSelected", issue);
},
updateIssuesSelected({ commit }, payload) {
let aSocialIssues = document.getElementById(
"chill_activitybundle_activity_socialIssues"
);
aSocialIssues.value = "";
payload.forEach((item) => {
aSocialIssues.value = addIdToValue(
aSocialIssues.value,
item.id
);
});
commit("updateIssuesSelected", payload);
},
updateActionsSelected({ commit }, payload) {
let aSocialActions = document.getElementById(
"chill_activitybundle_activity_socialActions"
);
aSocialActions.value = "";
payload.forEach((item) => {
aSocialActions.value = addIdToValue(
aSocialActions.value,
item.id
);
});
commit("updateActionsSelected", payload);
},
addPersonsInvolved({ commit }, payload) {
//console.log('### action addPersonsInvolved', payload.result.type);
switch (payload.result.type) {
case "person":
let aPersons = document.getElementById(
"chill_activitybundle_activity_persons"
);
aPersons.value = addIdToValue(
aPersons.value,
payload.result.id
);
break;
case "thirdparty":
let aThirdParties = document.getElementById(
"chill_activitybundle_activity_thirdParties"
);
aThirdParties.value = addIdToValue(
aThirdParties.value,
payload.result.id
);
break;
case "user":
let aUsers = document.getElementById(
"chill_activitybundle_activity_users"
);
aUsers.value = addIdToValue(
aUsers.value,
payload.result.id
);
break;
}
commit("addPersonsInvolved", payload);
},
removePersonInvolved({ commit }, payload) {
//console.log('### action removePersonInvolved', payload);
switch (payload.type) {
case "person":
let aPersons = document.getElementById(
"chill_activitybundle_activity_persons"
);
aPersons.value = removeIdFromValue(
aPersons.value,
payload.id
);
break;
case "thirdparty":
let aThirdParties = document.getElementById(
"chill_activitybundle_activity_thirdParties"
);
aThirdParties.value = removeIdFromValue(
aThirdParties.value,
payload.id
);
break;
case "user":
let aUsers = document.getElementById(
"chill_activitybundle_activity_users"
);
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
break;
}
commit("removePersonInvolved", payload);
},
updateLocation({ commit }, value) {
console.log("### action: updateLocation", value);
let hiddenLocation = document.getElementById(
"chill_activitybundle_activity_location"
);
if (value.onthefly) {
const body = {
"type": "location",
"name": value.name === '__AccompanyingCourseLocation__' ? null : value.name,
"locationType": {
"id": value.locationType.id,
"type": "location-type"
}
};
if (value.address.id) {
Object.assign(body, {
"address": {
"id": value.address.id
},
})
}
postLocation(body)
.then(
location => hiddenLocation.value = location.id
).catch(
err => {
console.log(err.message);
}
);
} else {
hiddenLocation.value = value.id;
}
commit("updateLocation", value);
},
},
updateIssuesSelected({ commit }, payload) {
let aSocialIssues = document.getElementById("chill_activitybundle_activity_socialIssues");
aSocialIssues.value = '';
payload.forEach(item => {
aSocialIssues.value = addIdToValue(aSocialIssues.value, item.id);
});
commit('updateIssuesSelected', payload);
},
updateActionsSelected({ commit }, payload) {
let aSocialActions = document.getElementById("chill_activitybundle_activity_socialActions");
aSocialActions.value = '';
payload.forEach(item => {
aSocialActions.value = addIdToValue(aSocialActions.value, item.id);
});
commit('updateActionsSelected', payload);
},
addPersonsInvolved({ commit }, payload) {
//console.log('### action addPersonsInvolved', payload.result.type);
switch (payload.result.type) {
case 'person':
let aPersons = document.getElementById("chill_activitybundle_activity_persons");
aPersons.value = addIdToValue(aPersons.value, payload.result.id);
break;
case 'thirdparty':
let aThirdParties = document.getElementById("chill_activitybundle_activity_thirdParties");
aThirdParties.value = addIdToValue(aThirdParties.value, payload.result.id);
break;
case 'user':
let aUsers = document.getElementById("chill_activitybundle_activity_users");
aUsers.value = addIdToValue(aUsers.value, payload.result.id);
break;
};
commit('addPersonsInvolved', payload);
},
removePersonInvolved({ commit }, payload) {
//console.log('### action removePersonInvolved', payload);
switch (payload.type) {
case 'person':
let aPersons = document.getElementById("chill_activitybundle_activity_persons");
aPersons.value = removeIdFromValue(aPersons.value, payload.id);
break;
case 'thirdparty':
let aThirdParties = document.getElementById("chill_activitybundle_activity_thirdParties");
aThirdParties.value = removeIdFromValue(aThirdParties.value, payload.id);
break;
case 'user':
let aUsers = document.getElementById("chill_activitybundle_activity_users");
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
break;
};
commit('removePersonInvolved', payload);
},
updateLocation({ commit }, value) {
console.log('### action: updateLocation', value);
let hiddenLocation = document.getElementById("chill_activitybundle_activity_location");
hiddenLocation.value = value.id;
commit('updateLocation', value);
}
}
});
export default store;

View File

@ -1,13 +1,38 @@
<?php
use Symfony\Component\HttpKernel\Kernel;
/**
* 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);
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
{
/**
* @return string
*/
public function getCacheDir()
{
return sys_get_temp_dir() . '/ActivityBundle/cache';
}
/**
* @return string
*/
public function getLogDir()
{
return sys_get_temp_dir() . '/ActivityBundle/logs';
}
public function registerBundles()
{
return array(
return [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Chill\CustomFieldsBundle\ChillCustomFieldsBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
@ -20,28 +45,12 @@ class AppKernel extends Kernel
new \Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
new Symfony\Bundle\MonologBundle\MonologBundle(),
#add here all the required bundle (some bundle are not required)
);
//add here all the required bundle (some bundle are not required)
];
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
}
/**
* @return string
*/
public function getCacheDir()
{
return sys_get_temp_dir().'/ActivityBundle/cache';
}
/**
* @return string
*/
public function getLogDir()
{
return sys_get_temp_dir().'/ActivityBundle/logs';
$loader->load($this->getRootDir() . '/config/config_' . $this->getEnvironment() . '.yml');
}
}

View File

@ -1,11 +1,20 @@
<?php
use Doctrine\Common\Annotations\AnnotationRegistry;
/**
* 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);
use Composer\Autoload\ClassLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;
/** @var ClassLoader $loader */
$loader = require __DIR__.'/../../../../../vendor/autoload.php';
$loader = require __DIR__ . '/../../../../../vendor/autoload.php';
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
return $loader;

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,16 @@
<?php
use Symfony\Component\HttpFoundation\Request;
/**
* 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);
use Symfony\Component\Debug\Debug;
use Symfony\Component\HttpFoundation\Request;
// If you don't want to setup permissions the proper way, just uncomment the following PHP line
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information
@ -11,16 +20,17 @@ use Symfony\Component\Debug\Debug;
// Feel free to remove this, extend it, or make something more sophisticated.
if (isset($_SERVER['HTTP_CLIENT_IP'])
|| isset($_SERVER['HTTP_X_FORWARDED_FOR'])
|| !(in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1')) || php_sapi_name() === 'cli-server')
|| !(in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', 'fe80::1', '::1'], true) || \PHP_SAPI === 'cli-server')
) {
header('HTTP/1.0 403 Forbidden');
exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
exit('You are not allowed to access this file. Check ' . basename(__FILE__) . ' for more information.');
}
$loader = require_once __DIR__.'/../app/bootstrap.php.cache';
$loader = require_once __DIR__ . '/../app/bootstrap.php.cache';
Debug::enable();
require_once __DIR__.'/../app/AppKernel.php';
require_once __DIR__ . '/../app/AppKernel.php';
$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();

View File

@ -1,88 +0,0 @@
<h2 class="badge-title">
<span class="title_label">
{% if activity.date %}
<h3>{{ activity.date|format_date('short') }}</h3>
{% endif %}
<div class="duration">
{% if activity.durationTime and t.durationTimeVisible %}
<p>
<abbr class="fa fa-fw fa-hourglass-end" title="{{ 'Duration Time'|trans }}"></abbr>
{{ activity.durationTime|date('H:i') }}
</p>
{% endif %}
{% if activity.travelTime and t.travelTimeVisible %}
<p>
<abbr class="fa fa-fw fa-car" title="{{ 'Travel time'|trans }}"></abbr>
{{ activity.travelTime|date('H:i') }}
</p>
{% endif %}
</div>
</span>
<span class="title_action">
{{ activity.type.name | localize_translatable_string }}
<ul class="small_in_title">
<li>
<abbr title="{{ 'location'|trans }}">{{ 'location'|trans ~ ': ' }}</abbr>
{# TODO {% if activity.location %}{{ activity.location }}{% endif %} #}
Domicile de l'usager
</li>
{% if activity.user and t.userVisible %}
<li>
<abbr title="{{ 'Referrer'|trans }}">{{ 'Referrer'|trans ~ ': ' }}</abbr>
{{ activity.user.usernameCanonical }}
</li>
{% endif %}
</ul>
<ul class="list-content">
{%- if t.reasonsVisible -%}
{%- if activity.reasons is not empty -%}
<li class="reasons">
{% for r in activity.reasons %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialIssuesVisible %}
{%- if activity.socialIssues is not empty -%}
<li class="social-issues">
{% for r in activity.socialIssues %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialActionsVisible -%}
{%- if activity.socialActions is not empty -%}
<li class="social-actions">
{% for r in activity.socialActions %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
</ul>
</span>
</h2>
{#
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
<div class="mt-3">
<a class="btn btn-sm btn-outline-primary"
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
href="{{ chill_path_add_return_path(
"chill_person_accompanying_course_index",
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
) }}"><i class="fa fa-random"></i>
</a>
</div>
{% endif %}
#}

Some files were not shown because too many files have changed in this diff Show More