Compare commits

..

6 Commits

Author SHA1 Message Date
148221cdc7 fix namespace of 'self' 2021-04-27 23:21:15 +02:00
58047faac6 force type for connection parameter 2021-04-27 23:20:49 +02:00
40dda65006 fix import namespacees 2021-04-27 23:20:25 +02:00
Pol Dellaiera
054a28ecf4 refactor: Update source code based on PHP conventions. 2021-04-26 17:18:38 +02:00
Pol Dellaiera
6b2eda0f94 chore: Add/update static files. 2021-04-26 15:44:37 +02:00
Pol Dellaiera
1359f1ba58 chore: Update composer.json 2021-04-26 14:02:26 +02:00
2373 changed files with 35272 additions and 109157 deletions

View File

@@ -7,7 +7,6 @@ charset = utf-8
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
indent_size = 4
[*.{php,html,twig}]
indent_style = space

View File

@@ -3,3 +3,4 @@
# Run tests from root to adapt your own environment
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres?serverVersion=12&charset=utf8

3
.gitignore vendored
View File

@@ -19,6 +19,3 @@ docs/build/
/phpunit.xml
.phpunit.result.cache
###< phpunit/phpunit ###
/.php-cs-fixer.cache
/.idea/

View File

@@ -1,100 +1,37 @@
---
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/
- .cache
paths:
- tests/app/vendor/
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
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install
- php tests/app/bin/console doctrine:migrations:migrate -n
- php tests/app/bin/console doctrine:fixtures:load -n
# 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: postgres:12
alias: db
- name: redis
alias: redis
# Set any variables we need
variables:
GIT_DEPTH: 1
# Configure postgres environment variables (https://hub.docker.com/r/_/postgres/)
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
# configure database access
DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres?serverVersion=12&charset=utf8
# 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
# 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
stages:
- Composer install
- Tests
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/
phpstan_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/
psalm_tests:
stage: Tests
image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4
script:
- bin/grumphp run --tasks=psalm
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=4G bin/phpunit --colors=never
artifacts:
expire_in: 30 min
paths:
- bin
- tests/app/vendor/
# Run our tests
test:
script:
- bin/phpunit --colors=never

View File

@@ -1,24 +0,0 @@
# 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 -->

25
.php_cs.dist Normal file
View File

@@ -0,0 +1,25 @@
<?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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
$config = require __DIR__ . '/vendor/drupol/php-conventions/config/php73/php_cs_fixer.config.php';
$config
->getFinder()
->ignoreDotFiles(false)
->name(['.php_cs.dist']);
$rules = $config->getRules();
$rules['header_comment']['header'] = trim(file_get_contents(__DIR__ . '/resource/header.txt'));
return $config->setRules($rules);

View File

@@ -1,79 +0,0 @@
<?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

@@ -1,329 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to
* [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for stable releases;
* date versioning for test releases
## Unreleased
<!-- write down unreleased development here -->
* vuejs: add validation on required fields for AddPerson, Address and Location components
* vuejs: treat 422 validation errors in locations and AddPerson components
## Test releases
### test release 2022-01-12
* fix thirdparty normalizer on telephone field: https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/322
### test release 2022-01-11
* vuejs: translate in French all multiselect widgets
* [address] define address lines according postal standards for France and Belgium (default) and change AddressRender, chill_entity_render_box and AddressRenderBox.vue
* [household] change translations (champs-libres/departement-de-la-vendee/accent-suivi-developpement#109)
* [household] add address i18n in household component (champs-libres/departement-de-la-vendee/accent-suivi-developpement#158)
* [household] add on the fly i18n in household component
* [household] redirect to the household page when a household is created from a person (champs-libres/departement-de-la-vendee/accent-suivi-developpement#175)
* [household] household member editor: display alert if some members have already an household (champs-libres/departement-de-la-vendee/accent-suivi-developpement#172)
* [household] household member editor: do not add in new members if the member is included in the members of household (champs-libres/departement-de-la-vendee/accent-suivi-developpement#123)
* [household] household member editor: remove markNoAddress button (champs-libres/departement-de-la-vendee/accent-suivi-developpement#109)
* [person]: ordering fields in add person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/61)
* [person]: Add email and alt names in add person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/61)
* [accompanyingCourse] Add a delete action and delete buttons to delete a accompanying course when step = DRAFT (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/64)
* [accompanyingCourse] Add a administrative location in the accompanying course, set the user current location as default, allow to select a location in a select field and do not allow to confirm the accompanying course if location is empty.
* [accompanyingCourse] Add the administrative location in the available variables for document generation
* AddAddress: optimize loading: wait for the user finish typing;
* UserPicker: fix bug with deprecated role
* docgen: add base context + tests
* docgen: add age for person
* [household menu] fix filiation order https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/265
* [AddAddress]: optimize loading: wait for the user finish typing;
* [UserPicker]: fix bug with deprecated role
* [docgen]: add base context + tests
* [docgen]: add age for person
* [task]: fix dropdown menu style + fix bug in singleTaskController (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/338)
* Household: fix bug when moving person on the same day (see https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/281)
* Household: show date validFrom and validTo when moving
* address reference: add index for refid
* [accompanyingCourse_work] fix styles conflicts + fix bug with remove goal (remove goals one at a time)
* [accompanyingCourse] improve masonry on resume page, add origin
* [notification] new notification interface, can be associated to AccompanyingCourse/Period, Activities.
* List notifications, show, and comment in User section
* Notify button and contextual notification box on associated objects pages
* [accompanyingCourse] add a comment for each resource associated. A modal allow to save comment. Comment is displayed in on-the-fly show modal of the accompanyingCourse context (edit page + resume page).
### test release 2021-12-14
* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262)
* [vendee/person] fix typo "situation professionelle" => "situation professionnelle"
* [main] add availableForUsers condition from locationType in the location API endpoint (champs-libres/departement-de-la-vendee/accent-suivi-developpement#248)
* [main] add the current location of the user as API point + add it in the activity location list (champs-libres/departement-de-la-vendee/accent-suivi-developpement#247)
* [activity] improve show/new/edit templates, fix SEE and SEE_DETAILS acl
* [badges] create specific badge for TMS, and make person/thirdparty badges clickable with on-the-fly modal in :
* concerned groups items (activity, calendar)
* accompanyingCourseWork lists
* accompanyingCourse lists
* [acompanyingCourse] add initial comment on Resume page
* [person] create button full width (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/330)
### test release 2021-12-11
* [main] add order field to civility
* [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)
* [vendee/person] fix typo "situation professionelle" => "situation professionnelle"
* [accompanyingcourse_work] Changes in layout/behavior of edit form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/321)
* [badge-entity] design coherency between pills badge-person and 3 kinds of badge-thirdparty
* [AddPersons] suggestions row are clickable, not only checkbox
### 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
### 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
* [person]: accompanying course work: remove creation date display the list of work + handle case when end date is null
* [main]: Add new pages with a menu for managing location and location type in the admin
* [main]: Add some fixtures for location type
* [calendar]: Pass the location when transforming a calendar item (rdv) into an activity
* [calendar]: Add a user menu for "my calendar"
### Test release 2021-10-18
* [3party]: french translation of contact and company
* [3party]: show parent in list
* [3party]: change color for badge "child"
* [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.
* [household]: add relationship page with dynamic data visualisation graph
## Test releases
### Test release 2021-10-11
* Address: zoom on postal code geometry + fix origin of manually entered postal code
* in the Address vue component, order the postal code and street address by alphabetic and numeric order
* add 3 new fields to PostalCode and adapt postal code command and fixtures
* [Aside activity] Fixes for aside activity
* categories with child
* fast creation buttons
* add ordering for types
* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities;
* Improve badges behaviour with small screens;
* [ThirdParty]:
* third party list
* create a kind contact/institution when create a new thirdparty, and set contact embedded as kind=child;
* filter thirdparties in list
* [FilterOrder]: add development kit for generating filter and ordering in list
* [Capitalization of names] person names are capitalized on creation, on prePersist event
* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ;
* [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ;
### test release 2021-10-04
* [Household editor][UI] Update how household suggestion and addresses are picked;
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/80
* [AddAddress] Handle address suggestion;
* [CenterType][Create a person] when overriding the ACL rules, allow to show a PickCenterType
when no centers are reachable by the default ACL.
* [Household] Show comment event if no address are associated with the household;
* [Person results] Add requestor into search results:
* a badge "requestor" is shown into search results;
* periods where the person is only requestor (without participating) are also shown;
Issues:
* https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/13
* https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/199
* [Person form] "accept sms" not required:
https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37
https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221
* [Household editor] suggest only temporarily addresses;
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/82
* On-The-Fly modale works for showing, editing and creating person and thirdparty ;
* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ;
* [AddAddress] Handle address suggestion;
* [AddAddress][Entity address]: add a link between address and address reference;
* [Household editor] suggest household by comparing the temporary addresses from courses;
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/81
* On-The-Fly modale works for showing, editing and creating person and thirdparty
## Test released
<!--
Coming soon...
DO NOT ADD unreleased items here. Add them under "Unreleased" title
### Test release yyyy-mm-dd
-->
## Stable releases
No stable releases for v2+

View File

@@ -1,411 +0,0 @@
# Conventions Chill
en cours de rédaction
## Assets: nommage des entrypoints
Trois types d'entrypoint:
* application vue (souvent spécifique à une page) -> préfixé par `vue_`;
* code js/css qui est réutilisé à plusieurs endroits:
* ckeditor
* async_upload (utilisé pour un formulaire)
* bootstrap
* chill.js
* ...
=> on préfixe `mod_`
* code css ou js pour une seule page
* ré-utilise parfois des "foncitionnalités": ShowHide, ...
=> on préfixe `page_`
Arborescence:
```
# Sous Resources/public
- chill/ => theme (chill)
- chillmain.scss -> push dans l'entrypoint chill
- lib/ => ne vont jamais dans un entrypoint, mais sont ré-utilisés par d'autres
- ShowHide
- Collection
- Select2
- module/ => termine dans des entrypoints ré-utilisables (mod_)
- bootstrap
- custom.scss
- custom/
- variables.scss
- ..
- forkawesome
- AsyncUpload
- vue/ => uniquement application vue (vue_)
- _components
- app
- page/ => uniquement pour une seule page (page_)
- login
- person
- personvendee
- household_edit_metadata
- index.js
```
## Organisation des feuilles de styles
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.
* 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.
* 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 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.
## 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 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```.
```html
<div class="bloc bloc-dark mon-bloc">
<h3>mon titre</h3>
<ul class="record_actions">
<li>
<a class="btn btn-edit"></a>
</li>
<li></li>
<li></li>
</ul>
</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.
Par exemple pour mettre un style au titre on précise juste h3 dans la cascade bloc.
```scss
div.bloc {
// un bloc générique, utilisé à plusieurs endroits
&.bloc-dark {
// la version sombre du bloc
}
h3 {}
ul {
// une liste standard dans bloc
li {
// des items de liste standard dans bloc
}
}
}
div.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
li {
//...
}
}
.btn {
// les boutons de bootstrap
.btn-edit {
// chill étends les boutons bootstrap pour ses propres besoins
}
}
</style>
```
## Render box
## URL
### Nommage des routes
:::warning
Ces règles n'ont pas toujours été utilisées par le passé. Elles sont souhaitées pour le futur.
:::
Les routes sont nommées de cette manière:
`chill_bundle_entite_action`
1. d'abord chill_ (pour tous les modules chill)
2. ensuite une string qui est identique, par bundle
3. si le point est un point d'api (json), alors ajouter la string `api`
4. ensuite une string qui indique sur quelle entité porte la route, voire également les sous-entités
5. ensuite une action (`list`, `view`, `edit`, `new`, ...)
Le fait d'indiquer `api` en 3 permet de distinguer les routes d'api qui sont générées par la configuration (qui sont toutes préfixées par `chill_api`, de celles générées manuellement. (Exemple: `chill_api_household__index`, et `chill_person_api_household_members_move`)
Si les points 4 et 5 sont inexistants, alors ils sont remplacés par d'autres éléments de manière à garantir l'unicité de la route, et sa bonne compréhension.
### URL
Les URL respectent également une convention:
#### Pour les pages html
:::warning
Ces règles n'ont pas toujours été utilisées par le passé. Elles sont souhaitées pour le futur.
:::
Syntaxe:
```
/{_locale}/bundle/entity/{id}/action
/{_locale}/bundle/entity/sub-entity/{id}/action
```
Les éléments suivants devraient se trouver dans la liste:
1. la locale;
2. un identifiant du bundle
3. l'entité auquel il se rapporte
4. les éventuelles sous-entités auxquelles l'url se rapport
5. l'action
Ces éléments peuvent être entrecoupés de l'identifiant d'une entité. Dans ce cas, cet identifiant se place juste après l'entité auquel il se rapporte.
Exemple:
```
# liste des échanges pour une personne
/fr/activity/person/25/activity/list
# nouvelle activité
/fr/activity/activity/new?person_id=25
```
#### Pour les API
:::info
Les routes générées automatiquement sont préfixées par chill_api
:::
Syntaxe:
```
/api/1.0/bundle/entity/{id}/action
/api/1.0/bundle/entity/sub-entity/{id}/action
```
Les éléments suivants devraient se trouver dans la liste:
1. la string `/api/` et puis la version (1.0)
2. un identifiant du bundle
3. l'entité auquel il se rapporte
4. les éventuelles sous-entités auxquelles l'url se rapport
5. l'action
Ces éléments peuvent être entrecoupés de l'identifiant d'une entité. Dans ce cas, cet identifiant se place juste après l'entité auquel il se rapporte.
## Règles UI chill
### Titre des pages
#### Chaque page contient un titre
Chaque page contient un titre dans la balise head. Ce titre est normalement identique à celui de l'entête de la page.
Astuce: il est possible d'utiliser la fonction `block` de twig pour cela:
```htmlmixed=
{% block title "Titre de la page" %}
{% block content %}
<h1>
{{ block('title')}}
</h1>
{% endblock %}
```
### Utilisation des `entity_render`
#### En twig
Les templates twig doivent toujours utiliser la fonction chill_entity_render_box pour effectuer le rendu des éléments suivants:
* User
* Person
* SocialIssue
* SocialAction
* Address
* ThirdParty
* ...
Exemple:
```
address|chill_entity_render_box
```
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
* pour simplifier le code twig
A prevoir:
* toujours trois positions:
* inline
* 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]
#### En vue
Il existe systématiquement une "box" équivalente en vue.
#### Lien vers des sections
A chaque fois qu'on indique le nom d'une personne, un parcours, un ménage, il y a toujours:
* un lien pour accéder à son dossier (pour autant que l'utilisateur ait les droits d'accès);
* à moins qu'il ne soit indiqué dans une phrase, l'icône de son dossier avant ou après (donc un bonhomme pour la personne, une maison pour le ménage, un fa-random pour les parcours);
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 ?
> 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]
### Formulaires
#### Vocabulaire:
Utiliser toujours:
* `Créer` dans un `bt bt-create` pour les **liens** vers le formulairep pour créer une entité (pour parvenir au formulaire);
* `Enregistrer` dans un `bt bt-save` pour les boutons "Enregistrer" (dans un formulaire édition **ou** création);
* `Enregistrer et nouveau`
* `Enregistrer et voir`
* `Modifier` dans un `bt bt-edit` pour les **liens** vers le formulaire d'édition
* `Dupliquer` (préciser là où on peut le voir)
* `Annuler` pour quitter une page d'édition avec un lien vers la liste, ou le `returnPath`
#### Retour après un enregistrement
Après avoir cliqué sur "Créer" ou "Sauver", la page devrait revenir:
* vers le returnPath, s'il existe;
* sinon, vers la page "vue".
### Bandeaux contenant les boutons d'actions
Les boutons sont toujours dans un bandeau "sticky-form" dans le bas du formulaire ou de la page de liste.
Si pertinent:
* Le bandeau contient un bouton "Annuler" qui retourne à la page précédente. Il est obligatoire pour les formulaires, optionnel pour les listes ou les pages "résumés"
* Ce bouton "annuler" est toujours à gauche
```
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a href="{{ chill_entity_return_path('route_name' { 'route': 'option' } )}}">{{ return_path_label }}</a>
</li>
<li>
<!-- action 1 -->
</li>
</ul>
```
### Messages flash
#### A la création d'une entité
A chaque fois qu'un élément est créé par un formulaire, un message flash doit apparaitre. Il indique:
> "L'élément a été créé"
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éé
> * ...
#### A l'enregistrement d'une entité
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)
En tête d'un formulaire, un message flash doit indiquer que des validations n'ont pas réussi:
> Ce formulaire contient des erreurs
Les erreurs doivent apparaitre attachée au champ qui les concerne. Toutefois, il est acceptable d'afficher les erreurs à la racine du formulaire s'il était complexe, techniquement, d'attacher les erreurs.
### Liens de retour
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,
* 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
</span>
</li>
</ul>
```
Créer un titre enlevable (avec une croix rouge cliquable, l'ancre a est vide)
```html
<div class="item-title">
<span>title</span>
</div>
```
Les classes `cols` ou `inline` peuvent être ajoutées à côté de `list-suggest` pour modifier la disposition de la liste.
Dans le dernier exemple, on met une classe `removable` sur le span, si on veut pouvoir enlever l'élément.

View File

@@ -1,9 +0,0 @@
# Chill framework
Documentation of the Chill software.
The online documentation can be found at http://docs.chill.social
See the [`docs`][1] directory for more.
[1]: docs/README.md

View File

@@ -1,88 +1,9 @@
{
"name": "chill-project/chill-bundles",
"description": "Most used bundles for chill-project",
"license": "AGPL-3.0-only",
"type": "library",
"keywords": [
"chill",
"social worker"
],
"require": {
"php": "^7.4",
"champs-libres/async-uploader-bundle": "dev-sf4#d57134aee8e504a83c902ff0cf9f8d36ac418290",
"champs-libres/wopi-bundle": "dev-master#59b468503b9413f8d588ef9e626e7675560db3d8",
"champs-libres/wopi-lib": "dev-master#0e1da19bb6de820080b8651867a7e475be590060",
"doctrine/doctrine-bundle": "^2.1",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.7",
"erusev/parsedown": "^1.7",
"graylog2/gelf-php": "^1.5",
"knplabs/knp-menu-bundle": "^3.0",
"knplabs/knp-time-bundle": "^1.12",
"league/csv": "^9.7.1",
"nyholm/psr7": "^1.4",
"ocramius/package-versions": "^1.10",
"phpoffice/phpspreadsheet": "^1.16",
"ramsey/uuid-doctrine": "^1.7",
"sensio/framework-extra-bundle": "^5.5",
"spomky-labs/base64url": "^2.0",
"symfony/asset": "^4.4",
"symfony/browser-kit": "^4.4",
"symfony/css-selector": "^4.4",
"symfony/expression-language": "^4.4",
"symfony/form": "^4.4",
"symfony/framework-bundle": "^4.4",
"symfony/intl": "^4.4",
"symfony/mailer": "^5.4",
"symfony/mime": "^5.4",
"symfony/monolog-bundle": "^3.5",
"symfony/security-bundle": "^4.4",
"symfony/serializer": "^5.3",
"symfony/swiftmailer-bundle": "^3.5",
"symfony/templating": "^4.4",
"symfony/translation": "^4.4",
"symfony/twig-bundle": "^4.4",
"symfony/validator": "^4.4",
"symfony/webpack-encore-bundle": "^1.11",
"symfony/workflow": "^4.4",
"symfony/yaml": "^4.4",
"twig/extra-bundle": "^3.0",
"twig/intl-extra": "^3.0",
"twig/markdown-extra": "^3.3",
"twig/string-extra": "^3.3",
"twig/twig": "^3.0"
},
"conflict": {
"symfony/symfony": "*"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3",
"drupol/php-conventions": "^5",
"fakerphp/faker": "^1.13",
"nelmio/alice": "^3.8",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": ">= 7.5",
"symfony/debug-bundle": "^5.1",
"symfony/dotenv": "^4.4",
"symfony/maker-bundle": "^1.20",
"symfony/phpunit-bridge": "^4.4",
"symfony/stopwatch": "^4.4",
"symfony/var-dumper": "^4.4",
"symfony/web-profiler-bundle": "^4.4"
},
"config": {
"bin-dir": "bin",
"optimize-autoloader": true,
"sort-packages": true,
"vendor-dir": "tests/app/vendor",
"allow-plugins": {
"composer/package-versions-deprecated": true,
"phpstan/extension-installer": true,
"ergebnis/composer-normalize": true,
"phpro/grumphp": true
}
},
"description": "Most used bundles for chill-project",
"keywords": ["chill", "social worker"],
"autoload": {
"psr-4": {
"Chill\\ActivityBundle\\": "src/Bundle/ChillActivityBundle",
@@ -95,36 +16,73 @@
"Chill\\PersonBundle\\": "src/Bundle/ChillPersonBundle",
"Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle",
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
"Chill\\AsideActivityBundle\\": "src/Bundle/ChillAsideActivityBundle/src",
"Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle",
"Chill\\CalendarBundle\\": "src/Bundle/ChillCalendarBundle",
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src"
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle"
}
},
"autoload-dev": {
"psr-4": {
"App\\": "tests/app/src/",
"Chill\\DocGeneratorBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests"
"App\\": "tests/app/src/"
}
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"phpstan/extension-installer": true,
"ergebnis/composer-normalize": true,
"phpro/grumphp": true,
"ocramius/package-versions": true
},
"bin-dir": "bin",
"optimize-autoloader": true,
"sort-packages": true,
"vendor-dir": "tests/app/vendor"
"require": {
"champs-libres/async-uploader-bundle": "dev-sf4",
"graylog2/gelf-php": "^1.5",
"symfony/form": "4.*",
"symfony/twig-bundle": "^4.4",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0",
"composer/package-versions-deprecated": "^1.10",
"doctrine/doctrine-bundle": "^2.1",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.7",
"symfony/asset": "4.*",
"symfony/monolog-bundle": "^3.5",
"symfony/security-bundle": "4.*",
"symfony/translation": "4.*",
"symfony/validator": "4.*",
"sensio/framework-extra-bundle": "^5.5",
"symfony/yaml": "4.*",
"symfony/webpack-encore-bundle": "^1.11",
"knplabs/knp-menu": "^3.1",
"knplabs/knp-menu-bundle": "^3.0",
"symfony/templating": "4.*",
"twig/intl-extra": "^3.0",
"symfony/workflow": "4.*",
"symfony/expression-language": "4.*",
"knplabs/knp-time-bundle": "^1.12",
"symfony/intl": "4.*",
"symfony/swiftmailer-bundle": "^3.5",
"league/csv": "^9.6",
"phpoffice/phpspreadsheet": "^1.16",
"symfony/browser-kit": "^5.2",
"symfony/css-selector": "^5.2",
"twig/markdown-extra": "^3.3",
"erusev/parsedown": "^1.7",
"symfony/serializer": "^5.2"
},
"conflict": {
"symfony/symfony": "*"
},
"require-dev": {
"drupol/php-conventions": "4.*",
"fakerphp/faker": "^1.13",
"phpunit/phpunit": "^7.0",
"symfony/dotenv": "^5.1",
"symfony/maker-bundle": "^1.20",
"doctrine/doctrine-fixtures-bundle": "^3.3",
"symfony/stopwatch": "^5.1",
"symfony/web-profiler-bundle": "^5.0",
"symfony/var-dumper": "4.*",
"symfony/debug-bundle": "^5.1",
"symfony/phpunit-bridge": "^5.2"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
}
},
"config": {
"bin-dir": "bin"
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -54,13 +56,13 @@ class CountPerson implements ExportInterface
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
// 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
// for all value, we do not process them and return them
// immediatly
return $value;
}
};

View File

@@ -23,196 +23,15 @@ 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.
@@ -223,7 +42,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, ...).
@@ -233,38 +52,8 @@ 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
@@ -292,7 +81,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 :
@@ -311,23 +100,34 @@ Just use the symfony way-of-doing, but do not forget to associate the entity you
And in template :
.. code-block:: twig
.. code-block:: html+jinja
{{ if is_granted('CHILL_ENTITY_SEE', entity) %}print something{% endif %}
Retrieving reachable scopes and centers for a user
--------------------------------------------------
Retrieving reachable scopes and centers
----------------------------------------
The class :class:`Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelperInterface` helps you to get centers and scope reachable by a user.
The class :class:`Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelper` 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.
@@ -352,7 +152,7 @@ To create your own roles, you should:
Declare your role
^^^^^^^^^^^^^^^^^^
------------------
To declare new role, implement the class :class:`Chill\\MainBundle\\Security\\ProvideRoleInterface`.
@@ -412,8 +212,69 @@ 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.
@@ -451,484 +312,3 @@ 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,747 +0,0 @@
.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled "GNU
Free Documentation License".
.. _api:
API
###
Chill provides a basic framework to build REST api.
Basic configuration
*******************
Configure a route
=================
Follow those steps to build a REST api:
1. Create your model;
2. Configure the API;
You can also:
* hook into the controller to customize some steps;
* add more route and steps
.. note::
Useful links:
* `How to use annotation to configure serialization <https://symfony.com/doc/current/serializer.html>`_
* `How to create your custom normalizer <https://symfony.com/doc/current/serializer/custom_normalizer.html>`_
Auto-loading the routes
=======================
Ensure that those lines are present in your file `app/config/routing.yml`:
.. code-block:: yaml
chill_cruds:
resource: 'chill_main_crud_route_loader:load'
type: service
Create your model
=================
Create your model on the usual way:
.. code-block:: php
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\OriginRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=OriginRepository::class)
* @ORM\Table(name="chill_person_accompanying_period_origin")
*/
class Origin
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="json")
*/
private $label;
/**
* @ORM\Column(type="date_immutable", nullable=true)
*/
private $noActiveAfter;
// .. getters and setters
}
Configure api
=============
Configure the api using Yaml (see the full configuration: :ref:`api_full_configuration`):
.. code-block:: yaml
# config/packages/chill_main.yaml
chill_main:
apis:
accompanying_period_origin:
base_path: '/api/1.0/person/accompanying-period/origin'
class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin'
name: accompanying_period_origin
base_role: 'ROLE_USER'
actions:
_index:
methods:
GET: true
HEAD: true
_entity:
methods:
GET: true
HEAD: true
.. note::
If you are working on a shared bundle (aka "The chill bundles"), you should define your configuration inside the class :code:`ChillXXXXBundleExtension`, using the "prependConfig" feature:
.. code-block:: php
namespace Chill\PersonBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Class ChillPersonExtension
* Loads and manages your bundle configuration
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
* @package Chill\PersonBundle\DependencyInjection
*/
class ChillPersonExtension extends Extension implements PrependExtensionInterface
{
public function prepend(ContainerBuilder $container)
{
$this->prependCruds($container);
}
/**
* @param ContainerBuilder $container
*/
protected function prependCruds(ContainerBuilder $container)
{
$container->prependExtensionConfig('chill_main', [
'apis' => [
[
'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Origin::class,
'name' => 'accompanying_period_origin',
'base_path' => '/api/1.0/person/accompanying-period/origin',
'controller' => \Chill\PersonBundle\Controller\OpeningApiController::class,
'base_role' => 'ROLE_USER',
'actions' => [
'_index' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true
],
],
'_entity' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true
]
],
]
]
]
]);
}
}
The :code:`_index` and :code:`_entity` action
*********************************************
The :code:`_index` and :code:`_entity` action are default actions:
* they will call a specific method in the default controller;
* they will generate defined routes:
Index:
Name: :code:`chill_api_single_accompanying_period_origin__index`
Path: :code:`/api/1.0/person/accompanying-period/origin.{_format}`
Entity:
Name: :code:`chill_api_single_accompanying_period_origin__entity`
Path: :code:`/api/1.0/person/accompanying-period/origin/{id}.{_format}`
Role
****
By default, the key `base_role` is used to check ACL. Take care of creating the :code:`Voter` required to take that into account.
For index action, the role will be called with :code:`NULL` as :code:`$subject`. The retrieved entity will be the subject for single queries.
You can also define a role for each method. In this case, this role is used for the given method, and, if any, the base role is taken into account.
.. code-block:: yaml
# config/packages/chill_main.yaml
chill_main:
apis:
accompanying_period_origin:
base_path: '/api/1.0/person/bla/bla'
class: 'Chill\PersonBundle\Entity\Blah'
name: bla
actions:
_entity:
methods:
GET: true
HEAD: true
roles:
GET: MY_ROLE_SEE
HEAD: MY ROLE_SEE
Customize the controller
************************
You can customize the controller by hooking into the default actions. Take care of extending :code:`Chill\MainBundle\CRUD\Controller\ApiController`.
.. code-block:: php
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class OpeningApiController extends ApiController
{
protected function customizeQuery(string $action, Request $request, $qb): void
{
$qb->where($qb->expr()->gt('e.noActiveAfter', ':now'))
->orWhere($qb->expr()->isNull('e.noActiveAfter'));
$qb->setParameter('now', new \DateTime('now'));
}
}
And set your controller in configuration:
.. code-block:: yaml
chill_main:
apis:
accompanying_period_origin:
base_path: '/api/1.0/person/accompanying-period/origin'
class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin'
name: accompanying_period_origin
# add a controller
controller: 'Chill\PersonBundle\Controller\OpeningApiController'
base_role: 'ROLE_USER'
actions:
_index:
methods:
GET: true
HEAD: true
_entity:
methods:
GET: true
HEAD: true
Create your own actions
***********************
You can add your own actions:
.. code-block:: yaml
chill_main:
apis:
-
class: Chill\PersonBundle\Entity\AccompanyingPeriod
name: accompanying_course
base_path: /api/1.0/person/accompanying-course
controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController
actions:
# add a custom participation:
participation:
methods:
POST: true
DELETE: true
GET: false
HEAD: false
PUT: false
roles:
POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
GET: null
HEAD: null
PUT: null
single-collection: single
The key :code:`single-collection` with value :code:`single` will add a :code:`/{id}/ + "action name"` (in this example, :code:`/{id}/participation`) into the path, after the base path. If the value is :code:`collection`, no id will be set, but the action name will be append to the path.
Then, create the corresponding action into your controller:
.. code-block:: php
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
use Chill\PersonBundle\Entity\Person;
class AccompanyingCourseApiController extends ApiController
{
protected EventDispatcherInterface $eventDispatcher;
protected ValidatorInterface $validator;
public function __construct(EventDispatcherInterface $eventDispatcher, $validator)
{
$this->eventDispatcher = $eventDispatcher;
$this->validator = $validator;
}
public function participationApi($id, Request $request, $_format)
{
/** @var AccompanyingPeriod $accompanyingPeriod */
$accompanyingPeriod = $this->getEntity('participation', $id, $request);
$person = $this->getSerializer()
->deserialize($request->getContent(), Person::class, $_format, []);
if (NULL === $person) {
throw new BadRequestException('person id not found');
}
$this->onPostCheckACL('participation', $request, $accompanyingPeriod, $_format);
switch ($request->getMethod()) {
case Request::METHOD_POST:
$participation = $accompanyingPeriod->addPerson($person);
break;
case Request::METHOD_DELETE:
$participation = $accompanyingPeriod->removePerson($person);
break;
default:
throw new BadRequestException("This method is not supported");
}
$errors = $this->validator->validate($accompanyingPeriod);
if ($errors->count() > 0) {
// only format accepted
return $this->json($errors);
}
$this->getDoctrine()->getManager()->flush();
return $this->json($participation);
}
}
Managing association
********************
ManyToOne association
=====================
In ManyToOne association, you can add associated entities using the :code:`PATCH` request. By default, the serializer deserialize entities only with their id and discriminator type, if any.
Example:
.. code-block:: bash
curl -X 'PATCH' \
'http://localhost:8001/api/1.0/person/accompanying-course/2668.json' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
# see the data sent to the server: \
-d '{
"type": "accompanying_period",
"id": 2668,
"origin": { "id": 11 }
}'
ManyToMany associations
=======================
In OneToMany association, you can easily create route for adding and removing entities, using :code:`POST` and :code:`DELETE` requests.
Prepare your entity, creating the methods :code:`addYourEntity` and :code:`removeYourEntity`:
.. code-block:: php
namespace Chill\PersonBundle\Entity;
use Chill\MainBundle\Entity\Scope;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
/**
* AccompanyingPeriod Class
*
* @ORM\Entity
* @ORM\Table(name="chill_person_accompanying_period")
* @DiscriminatorMap(typeProperty="type", mapping={
* "accompanying_period"=AccompanyingPeriod::class
* })
*/
class AccompanyingPeriod
{
/**
* @var Collection
* @ORM\ManyToMany(
* targetEntity=Scope::class,
* cascade={}
* )
* @Groups({"read"})
*/
private $scopes;
public function addScope(Scope $scope): self
{
$this->scopes[] = $scope;
return $this;
}
public function removeScope(Scope $scope): void
{
$this->scopes->removeElement($scope);
}
Create your route into the configuration:
.. code-block:: yaml
chill_main:
apis:
-
class: Chill\PersonBundle\Entity\AccompanyingPeriod
name: accompanying_course
base_path: /api/1.0/person/accompanying-course
controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController
actions:
scope:
methods:
POST: true
DELETE: true
GET: false
HEAD: false
PUT: false
PATCH: false
roles:
POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
GET: null
HEAD: null
PUT: null
PATCH: null
controller_action: null
path: null
single-collection: single
This will create a new route, which will accept two methods: DELETE and POST:
.. code-block:: raw
+--------------+---------------------------------------------------------------------------------------+
| Property | Value |
+--------------+---------------------------------------------------------------------------------------+
| Route Name | chill_api_single_accompanying_course_scope |
| Path | /api/1.0/person/accompanying-course/{id}/scope.{_format} |
| Path Regex | {^/api/1\.0/person/accompanying\-course/(?P<id>[^/]++)/scope\.(?P<_format>[^/]++)$}sD |
| Host | ANY |
| Host Regex | |
| Scheme | ANY |
| Method | POST|DELETE |
| Requirements | {id}: \d+ |
| Class | Symfony\Component\Routing\Route |
| Defaults | _controller: csapi_accompanying_course_controller:scopeApi |
| Options | compiler_class: Symfony\Component\Routing\RouteCompiler |
+--------------+---------------------------------------------------------------------------------------+
Then, create the controller action. Call the method:
.. code-block:: php
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Chill\MainBundle\Entity\Scope;
class MyController extends ApiController
{
public function scopeApi($id, Request $request, string $_format): Response
{
return $this->addRemoveSomething('scope', $id, $request, $_format, 'scope', Scope::class, [ 'groups' => [ 'read' ] ]);
}
}
This will allow to add a scope by his id, and delete them.
Curl requests:
.. code-block:: bash
# add a scope with id 5
curl -X 'POST' \
'http://localhost:8001/api/1.0/person/accompanying-course/2868/scope.json' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"type": "scope",
"id": 5
}'
# remove a scope with id 5
curl -X 'DELETE' \
'http://localhost:8001/api/1.0/person/accompanying-course/2868/scope.json' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"id": 5,
"type": "scope"
}'
Deserializing an association where multiple types are allowed
=============================================================
Sometimes, multiples types are allowed as association to one entity:
.. code-block:: php
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\ORM\Mapping as ORM;
class Resource
{
/**
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
* @ORM\JoinColumn(nullable=true)
*/
private $thirdParty;
/**
* @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\JoinColumn(nullable=true)
*/
private $person;
/**
*
* @param $resource Person|ThirdParty
*/
public function setResource($resource): self
{
// ...
}
/**
* @return ThirdParty|Person
* @Groups({"read", "write"})
*/
public function getResource()
{
return $this->person ?? $this->thirdParty;
}
}
This is not well taken into account by the Symfony serializer natively.
You must, then, create your own CustomNormalizer. You can help yourself using this:
.. code-block:: php
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
use Chill\PersonBundle\Repository\AccompanyingPeriod\ResourceRepository;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
use Symfony\Component\Serializer\Exception;
use Chill\MainBundle\Serializer\Normalizer\DiscriminatedObjectDenormalizer;
class AccompanyingPeriodResourceNormalizer implements DenormalizerInterface, DenormalizerAwareInterface
{
use DenormalizerAwareTrait;
use ObjectToPopulateTrait;
public function __construct(ResourceRepository $repository)
{
$this->repository = $repository;
}
public function denormalize($data, string $type, string $format = null, array $context = [])
{
// .. snipped for brevity
if ($resource === NULL) {
$resource = new Resource();
}
if (\array_key_exists('resource', $data)) {
$res = $this->denormalizer->denormalize(
$data['resource'],
// call for a "multiple type"
DiscriminatedObjectDenormalizer::TYPE,
$format,
// into the context, we add the list of allowed types:
[
DiscriminatedObjectDenormalizer::ALLOWED_TYPES =>
[
Person::class, ThirdParty::class
]
]
);
$resource->setResource($res);
}
return $resource;
}
public function supportsDenormalization($data, string $type, string $format = null)
{
return $type === Resource::class;
}
}
Serialization for collection
****************************
A specific model has been defined for returning collection:
.. code-block:: json
{
"count": 49,
"results": [
],
"pagination": {
"more": true,
"next": "/api/1.0/search.json&q=xxxx......&page=2",
"previous": null,
"first": 0,
"items_per_page": 1
}
}
Where this is relevant, this model should be re-used in custom controller actions.
In custom actions, this can be achieved quickly by assembling results into a :code:`Chill\MainBundle\Serializer\Model\Collection`. The pagination information is given by using :code:`Paginator` (see :ref:`Pagination <pagination-ref>`).
.. code-block:: php
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Chill\MainBundle\Pagination\PaginatorInterface;
class MyController extends AbstractController
{
protected function serializeCollection(PaginatorInterface $paginator, $entities): Response
{
$model = new Collection($entities, $paginator);
return $this->json($model, Response::HTTP_OK, [], $context);
}
}
.. _api_full_configuration:
Full configuration example
**************************
.. code-block:: yaml
apis:
-
class: Chill\PersonBundle\Entity\AccompanyingPeriod
name: accompanying_course
base_path: /api/1.0/person/accompanying-course
controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController
actions:
_entity:
roles:
GET: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
HEAD: null
POST: null
DELETE: null
PUT: null
controller_action: null
path: null
single-collection: single
methods:
GET: true
HEAD: true
POST: false
DELETE: false
PUT: false
participation:
methods:
POST: true
DELETE: true
GET: false
HEAD: false
PUT: false
roles:
POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
GET: null
HEAD: null
PUT: null
controller_action: null
# the requirements for the route. Will be set to `[ 'id' => '\d+' ]` if left empty.
requirements: []
path: null
single-collection: single
base_role: null

View File

@@ -16,7 +16,6 @@ As Chill rely on the `symfony <http://symfony.com>`_ framework, reading the fram
Instructions to create a new bundle <create-a-new-bundle.rst>
CRUD (Create - Update - Delete) for one entity <crud.rst>
Helpers for building a REST API <api.rst>
Routing <routing.rst>
Menus <menus.rst>
Forms <forms.rst>

View File

@@ -7,8 +7,6 @@
Free Documentation License".
.. _pagination-ref:
Pagination
##########
@@ -17,7 +15,7 @@ The Bundle :code:`Chill\MainBundle` provides a **Pagination** api which allow yo
A simple example
****************
In the controller, get the :code:`Chill\Main\Pagination\PaginatorFactory` from the `Container` and use this :code:`PaginatorFactory` to create a :code:`Paginator` instance.
In the controller, get the :class:`Chill\Main\Pagination\PaginatorFactory` from the `Container` and use this :code:`PaginatorFactory` to create a :code:`Paginator` instance.
.. literalinclude:: pagination/example.php
@@ -28,7 +26,7 @@ Then, render the pagination using the dedicated twig function.
.. code-block:: html+twig
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% extends "ChillPersonBundle::layout.html.twig" %}
{% block title 'Item list'|trans %}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -97,7 +97,7 @@ The has the following signature :
*
* @param string $context
* @param mixed[] $args the argument to the context.
* @return TimelineSingleQuery
* @return string[]
* @throw \LogicException if the context is not supported
*/
public function fetchQuery($context, array $args);
@@ -163,16 +163,18 @@ The has the following signature :
The `fetchQuery` function
^^^^^^^^^^^^^^^^^^^^^^^^^
The fetchQuery function help to build the UNION query to gather events. This function should return an instance of :code:`TimelineSingleQuery`. For you convenience, this object may be build using an associative array with the following keys:
The fetchQuery function help to build the UNION query to gather events. This function should return an associative array MUST have the following key :
* `id` : the name of the id column
* `type`: a string to indicate the type
* `date`: the name of the datetime column, used to order entities by date
* `FROM`: the FROM clause. May contains JOIN instructions
* `WHERE`: the WHERE clause;
* `parameters`: the parameters to pass to the query
* `FROM` (in capital) : the FROM clause. May contains JOIN instructions
The parameters should be replaced into the query by :code:`?`. They will be replaced into the query using prepared statements.
Those key are optional:
* `WHERE` (in capital) : the WHERE clause.
Where relevant, the data must be quoted to avoid SQL injection.
`$context` and `$args` are defined by the bundle which will call the timeline rendering. You may use them to build a different query depending on this context.
@@ -184,15 +186,6 @@ For instance, if the context is `'person'`, the args will be this array :
'person' => $person //a \Chill\PersonBundle\Entity\Person entity
);
For the context :code:`center`, the args will be:
.. code-block:: php
array(
'centers' => [ ] // an array of \Chill\MainBundle\Entity\Center entities
);
You should find in the bundle documentation which contexts are arguments the bundle defines.
.. note::
@@ -206,12 +199,13 @@ Example of an implementation :
namespace Chill\ReportBundle\Timeline;
use Chill\MainBundle\Timeline\TimelineProviderInterface;
use Chill\MainBundle\Timeline\TimelineSingleQuery;
use Doctrine\ORM\EntityManager;
/**
* Provide report for inclusion in timeline
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
*/
class TimelineReportProvider implements TimelineProviderInterface
{
@@ -233,17 +227,16 @@ Example of an implementation :
$metadata = $this->em->getClassMetadata('ChillReportBundle:Report');
return TimelineSingleQuery::fromArray([
return array(
'id' => $metadata->getColumnName('id'),
'type' => 'report',
'date' => $metadata->getColumnName('date'),
'FROM' => $metadata->getTableName(),
'WHERE' => sprintf('%s = ?',
'WHERE' => sprintf('%s = %d',
$metadata
->getAssociationMapping('person')['joinColumns'][0]['name'])
)
'parameters' => [ $args['person']->getId() ]
]);
->getAssociationMapping('person')['joinColumns'][0]['name'],
$args['person']->getId())
);
}
//....

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -149,7 +149,7 @@ It proposes a new block :
* where to display the admin content
@ChillPersonBundle/Person/layout.html.twig
ChillPersonBundle::layout.html.twig
-----------------------------------
This layout extend `ChillMainBundle::layoutWithVerticalMenu.html.twig` add the person details in the block `top_banner`, set the menu `person` as the vertical menu.

View File

@@ -1,20 +1,24 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
// Chill\MainBundle\DependencyInjection\Configuration.php
namespace Chill\MainBundle\DependencyInjection;
use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Configure the main bundle.
@@ -38,6 +42,9 @@ class ChillMainConfiguration implements ConfigurationInterface
$this->containerBuilder = $containerBuilder;
}
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();

View File

@@ -1,14 +1,18 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
//Chill\MainBundle\DependencyInjection\ChillMainExtension.php
namespace Chill\MainBundle\DependencyInjection;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;

View File

@@ -1,14 +1,18 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
// Chill/PersonBundle/Widget/PersonListWidgetFactory
namespace Chill\PersonBundle\Widget;
use Chill\MainBundle\DependencyInjection\Widget\Factory\AbstractWidgetFactory;

View File

@@ -1,14 +1,18 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
// Chill/PersonBundle/Widget/PersonListWidget.php
namespace Chill\PersonBundle\Widget;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;

View File

@@ -1,14 +1,18 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
// Chill/PersonBundle/DependencyInjection/ChillPersonExtension.php
namespace Chill\PersonBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -22,6 +26,9 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
*/
class ChillPersonExtension extends Extension implements PrependExtensionInterface
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
// ...

View File

@@ -82,7 +82,7 @@ Chill will be available at ``http://localhost:8001.`` Currently, there isn't any
.. code-block:: bash
docker-compose exec --user $(id -u) php bin/console doctrine:fixtures:load --purge-with-truncate
docker-compose exec --user $(id -u) php bin/console doctrine:fixtures:load
There are several users available:

View File

@@ -1,39 +1,8 @@
imports:
- {
resource: tests/app/vendor/drupol/php-conventions/config/php73/grumphp.yml,
}
- { resource: 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
tasks.phpstan.blocking: true
tasks.phpstan.ignore_patterns:
- "/.github/"
- "/.idea/"
- "/build/"
- "/benchmarks/"
- "/docs/"
- "/node_modules/"
- "/resource/"
- "/spec/"
- "/var/"
- "/vendor/"
# Psalm
tasks.psalm.blocking: true
tasks.psalm.ignore_patterns:
- "/.github/"
- "/.idea/"
- "/build/"
- "/benchmarks/"
- "/node_modules/"
- "/resource/"
- "/spec/"
- "/var/"
- "/vendor/"
tasks.phpcsfixer.config: .php_cs.dist
tasks.license.name: AGPL-3.0
tasks.license.holder: Champs-Libres
tasks.license.date_from: 2001

View File

@@ -1,3 +0,0 @@
add npm/yarn dependency in package.json :
"select2-bootstrap-theme": "0.1.0-beta.10",

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,492 +0,0 @@
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: 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

View File

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

@@ -2,7 +2,7 @@
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="tests/app/vendor/phpunit/phpunit/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/app/tests/bootstrap.php"
@@ -10,7 +10,7 @@
<php>
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
<server name="SHELL_VERBOSITY" value="-1" />
</php>
@@ -18,29 +18,12 @@
<testsuite name="MainBundle">
<directory suffix="Test.php">src/Bundle/ChillMainBundle/Tests/</directory>
</testsuite>
<testsuite name="PersonBundle">
<directory suffix="Test.php">src/Bundle/ChillPersonBundle/Tests/</directory>
<!-- test for export will be runned later -->
<exclude>src/Bundle/ChillPersonBundle/Tests/Export/*</exclude>
<!-- we are rewriting accompanying periods... Work in progress -->
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingPeriodControllerTest.php</exclude>
<!-- we are rewriting address, Work in progress -->
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonAddressControllerTest.php</exclude>
<!-- find a solution to create multiple configs -->
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php</exclude>
<!-- temporarily removed, the time to find a fix -->
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
</testsuite>
<testsuite name="AsideActivityBundle">
<directory suffix="Test.php">src/Bundle/ChillAsideActivityBundle/src/Tests/</directory>
</testsuite>
<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>
</testsuites>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config tests/app/vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="./tests/" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@@ -1,5 +1,6 @@
Chill is a software for social workers
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.
@see https://www.champs-libres.coop/

View File

@@ -27,4 +27,3 @@ Version 1.5.5
=============
- [activity] replace dropdown for selecting reasons and use chillEntity for reason rendering
- fix bug: error when trying to edit activity of which the type has been deactivated

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,118 +14,132 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Controller;
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\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
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\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 Symfony\Component\Serializer\SerializerInterface;
use function array_key_exists;
final class ActivityController extends AbstractController
class ActivityController extends AbstractController
{
private AccompanyingPeriodRepository $accompanyingPeriodRepository;
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
private ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
private ActivityRepository $activityRepository;
private ActivityTypeCategoryRepository $activityTypeCategoryRepository;
private ActivityTypeRepository $activityTypeRepository;
private EntityManagerInterface $entityManager;
private EventDispatcherInterface $eventDispatcher;
private LocationRepository $locationRepository;
private LoggerInterface $logger;
private PersonRepository $personRepository;
private SerializerInterface $serializer;
private ThirdPartyRepository $thirdPartyRepository;
/**
* @var LoggerInterface
*/
protected $logger;
/**
* ActivityController constructor.
*/
public function __construct(
ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
ActivityTypeRepository $activityTypeRepository,
ActivityTypeCategoryRepository $activityTypeCategoryRepository,
PersonRepository $personRepository,
ThirdPartyRepository $thirdPartyRepository,
LocationRepository $locationRepository,
ActivityRepository $activityRepository,
AccompanyingPeriodRepository $accompanyingPeriodRepository,
EntityManagerInterface $entityManager,
EventDispatcherInterface $eventDispatcher,
LoggerInterface $logger,
SerializerInterface $serializer
AuthorizationHelper $authorizationHelper,
LoggerInterface $logger
) {
$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;
}
/**
* Creates a new Activity entity.
*
* @param mixed $person_id
*/
public function createAction($person_id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$person = $em->getRepository('ChillPersonBundle:Person')->find($person_id);
if (null === $person) {
throw $this->createNotFoundException('person not found');
}
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
$entity = new Activity();
$entity->setPerson($person);
$form = $this->createCreateForm($entity, $person);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$this->denyAccessUnlessGranted(
'CHILL_ACTIVITY_CREATE',
$entity,
'creation of this activity not allowed'
);
$em->persist($entity);
$em->flush();
$this->get('session')
->getFlashBag()
->add(
'success',
$this->get('translator')
->trans('Success : activity created!')
);
return $this->redirect(
$this->generateUrl(
'chill_activity_activity_show',
['id' => $entity->getId(), 'person_id' => $person_id]
)
);
}
$this->get('session')
->getFlashBag()->add(
'danger',
$this->get('translator')
->trans('The form is not valid. The activity has not been created !')
);
return $this->render('ChillActivityBundle:Activity:new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
'person' => $person,
]);
}
/**
* Deletes a Activity entity.
*
* @param mixed $id
* @param mixed $person_id
*/
public function deleteAction(Request $request, $id)
public function deleteAction(Request $request, $id, $person_id)
{
$view = null;
$em = $this->getDoctrine()->getManager();
[$person, $accompanyingPeriod] = $this->getEntity($request);
$activity = $this->activityRepository->find($id);
/** @var Activity $activity */
$activity = $em->getRepository('ChillActivityBundle:Activity')
->find($id);
$person = $activity->getPerson();
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';
}
$this->denyAccessUnlessGranted('CHILL_ACTIVITY_DELETE', $activity);
// TODO
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_DELETE', $activity);
$form = $this->createDeleteForm($activity->getId(), $person, $accompanyingPeriod);
$form = $this->createDeleteForm($id, $person);
if ($request->getMethod() === Request::METHOD_DELETE) {
$form->handleRequest($request);
@@ -132,454 +148,321 @@ final class ActivityController extends AbstractController
$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,
'person_id' => $activity->getPerson()->getId(),
'comment' => $activity->getComment()->getComment(),
'scope_id' => $activity->getScope() ? $activity->getScope()->getId() : null,
'scope_id' => $activity->getScope()->getId(),
'reasons_ids' => $activity->getReasons()
->map(
static fn (ActivityReason $ar): int => $ar->getId()
)
->map(static function ($ar) {
return $ar->getId();
})
->toArray(),
'type_id' => $activity->getActivityType()->getId(),
'duration' => $activity->getDurationTime() ? $activity->getDurationTime()->format('U') : null,
'type_id' => $activity->getType()->getId(),
'duration' => $activity->getDurationTime()->format('U'),
'date' => $activity->getDate()->format('Y-m-d'),
'attendee' => $activity->getAttendee(),
]);
$this->entityManager->remove($activity);
$this->entityManager->flush();
$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);
return $this->redirect($this->generateUrl(
'chill_activity_activity_list',
[
'person_id' => $person_id,
]
));
}
}
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, [
return $this->render('ChillActivityBundle:Activity:confirm_delete.html.twig', [
'activity' => $activity,
'delete_form' => $form->createView(),
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
]);
}
/**
* Displays a form to edit an existing Activity entity.
*
* @param mixed $person_id
* @param mixed $id
*/
public function editAction(int $id, Request $request): Response
public function editAction($person_id, $id)
{
$view = null;
$em = $this->getDoctrine()->getManager();
$person = $em->getRepository('ChillPersonBundle:Person')->find($person_id);
[$person, $accompanyingPeriod] = $this->getEntity($request);
if (!$person) {
throw $this->createNotFoundException('person not found');
}
$entity = $this->activityRepository->find($id);
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
if (null === $entity) {
$entity = $em->getRepository('ChillActivityBundle:Activity')->find($id);
if (!$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);
$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);
$editForm = $this->createEditForm($entity);
$deleteForm = $this->createDeleteForm($id, $person);
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(
$event = new PrivacyEvent($person, [
'element_class' => Activity::class,
'element_id' => $entity->getId(),
'action' => 'edit'
));
'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, [
return $this->render('ChillActivityBundle:Activity:edit.html.twig', [
'entity' => $entity,
'edit_form' => $form->createView(),
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'activity_json' => $activity_array,
]);
}
/**
* Lists all Activity entities.
*
* @param mixed $person_id
*/
public function listAction(Request $request): Response
public function listAction($person_id, Request $request)
{
$view = null;
$activities = [];
// TODO: add pagination
$em = $this->getDoctrine()->getManager();
$person = $em->getRepository('ChillPersonBundle:Person')->find($person_id);
[$person, $accompanyingPeriod] = $this->getEntity($request);
if ($person instanceof Person) {
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $person);
$activities = $this->activityACLAwareRepository
->findByPerson($person, ActivityVoter::SEE, 0, null);
$event = new PrivacyEvent($person, [
'element_class' => Activity::class,
'action' => 'list',
]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
$view = 'ChillActivityBundle:Activity:listPerson.html.twig';
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod);
$activities = $this->activityACLAwareRepository
->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE);
$view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig';
if (null === $person) {
throw $this->createNotFoundException('Person not found');
}
return $this->render(
$view,
[
'activities' => $activities,
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
]
);
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
$reachableScopes = $this->authorizationHelper
->getReachableScopes(
$this->getUser(),
new Role('CHILL_ACTIVITY_SEE'),
$person->getCenter()
);
$activities = $em->getRepository('ChillActivityBundle:Activity')
->findBy(
['person' => $person, 'scope' => $reachableScopes],
['date' => 'DESC']
);
$event = new PrivacyEvent($person, [
'element_class' => Activity::class,
'action' => 'list',
]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
return $this->render('ChillActivityBundle:Activity:list.html.twig', [
'activities' => $activities,
'person' => $person,
]);
}
public function newAction(Request $request): Response
/**
* Displays a form to create a new Activity entity.
*
* @param mixed $person_id
*/
public function newAction($person_id)
{
$view = null;
$em = $this->getDoctrine()->getManager();
$person = $em->getRepository('ChillPersonBundle:Person')->find($person_id);
[$person, $accompanyingPeriod] = $this->getEntity($request);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:newAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:newPerson.html.twig';
if (null === $person) {
throw $this->createNotFoundException('Person not found');
}
$activityType_id = $request->get('activityType_id', 0);
$activityType = $this->activityTypeRepository->find($activityType_id);
if (isset($activityType) && !$activityType->isActive()) {
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()
) {
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
if (null !== $activityData) {
$params['activityData'] = $activityData;
}
return $this->redirectToRoute('chill_activity_activity_select_type', $params);
}
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
$entity = new Activity();
$entity->setUser($this->getUser());
if ($person instanceof Person) {
$entity->setPerson($person);
}
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$entity->setAccompanyingPeriod($accompanyingPeriod);
}
$entity->setActivityType($activityType);
$entity->setUser($this->get('security.token_storage')->getToken()->getUser());
$entity->setPerson($person);
$entity->setDate(new DateTime('now'));
if ($request->query->has('activityData')) {
$activityData = $request->query->get('activityData');
$this->denyAccessUnlessGranted('CHILL_ACTIVITY_CREATE', $entity);
if (array_key_exists('durationTime', $activityData)) {
$durationTimeInMinutes = $activityData['durationTime'];
$hours = floor($durationTimeInMinutes / 60);
$minutes = $durationTimeInMinutes % 60;
$duration = DateTime::createFromFormat('H:i', $hours . ':' . $minutes);
$form = $this->createCreateForm($entity, $person);
if ($duration) {
$entity->setDurationTime($duration);
}
}
if (array_key_exists('date', $activityData)) {
$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 = $this->personRepository->find($personId);
$entity->addPerson($concernedPerson);
}
}
if (array_key_exists('professionalsId', $activityData)) {
foreach ($activityData['professionalsId'] as $professionalsId) {
$professional = $this->thirdPartyRepository->find($professionalsId);
$entity->addThirdParty($professional);
}
}
if (array_key_exists('location', $activityData)) {
$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'));
$entity->setComment($comment);
}
}
$this->denyAccessUnlessGranted(ActivityVoter::CREATE, $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),
'role' => new Role('CHILL_ACTIVITY_CREATE'),
'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 created!'));
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
$params['id'] = $entity->getId();
return $this->redirectToRoute('chill_activity_activity_show', $params);
}
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, [
return $this->render('ChillActivityBundle:Activity:new.html.twig', [
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'entity' => $entity,
'form' => $form->createView(),
'activity_json' => $activity_array,
'default_location' => $defaultLocation,
]);
}
public function selectTypeAction(Request $request): Response
/**
* Finds and displays a Activity entity.
*
* @param mixed $person_id
* @param mixed $id
*/
public function showAction($person_id, $id)
{
$view = null;
$em = $this->getDoctrine()->getManager();
$person = $em->getRepository('ChillPersonBundle:Person')->find($person_id);
[$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';
if (!$person) {
throw $this->createNotFoundException('person not found');
}
$data = [];
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
$activityTypeCategories = $this
->activityTypeCategoryRepository
->findBy(['active' => true], ['ordering' => 'ASC']);
$entity = $em->getRepository('ChillActivityBundle:Activity')->find($id);
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);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:showPerson.html.twig';
}
$entity = $this->activityRepository->find($id);
if (null === $entity) {
if (!$entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
}
if (null !== $accompanyingPeriod) {
// @TODO: Properties created dynamically.
$entity->personsAssociated = $entity->getPersonsAssociated();
$entity->personsNotAssociated = $entity->getPersonsNotAssociated();
}
$this->denyAccessUnlessGranted('CHILL_ACTIVITY_SEE', $entity);
// 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);
$deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);
// TODO
/*
$event = new PrivacyEvent($person, array(
$event = new PrivacyEvent($person, [
'element_class' => Activity::class,
'element_id' => $entity->getId(),
'action' => 'show'
));
'action' => 'show',
]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
*/
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, [
return $this->render('ChillActivityBundle:Activity:show.html.twig', [
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'entity' => $entity,
'delete_form' => $deleteForm->createView(),
]);
}
private function buildParamsToUrl(?Person $person, ?AccompanyingPeriod $accompanyingPeriod): array
/**
* Edits an existing Activity entity.
*
* @param mixed $person_id
* @param mixed $id
*/
public function updateAction(Request $request, $person_id, $id)
{
$params = [];
$em = $this->getDoctrine()->getManager();
if (null !== $person) {
$params['person_id'] = $person->getId();
$person = $em->getRepository('ChillPersonBundle:Person')->find($person_id);
$entity = $em->getRepository('ChillActivityBundle:Activity')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
}
if (null !== $accompanyingPeriod) {
$params['accompanying_period_id'] = $accompanyingPeriod->getId();
$this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity);
$deleteForm = $this->createDeleteForm($id, $person);
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
$event = new PrivacyEvent($person, [
'element_class' => Activity::class,
'element_id' => $entity->getId(),
'action' => 'update',
]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
if ($editForm->isValid()) {
$em->flush();
$this->get('session')
->getFlashBag()
->add(
'success',
$this->get('translator')
->trans('Success : activity updated!')
);
return $this->redirect($this->generateUrl('chill_activity_activity_show', ['id' => $id, 'person_id' => $person_id]));
}
return $params;
$this->get('session')
->getFlashBag()
->add(
'error',
$this->get('translator')
->trans('This form contains errors')
);
return $this->render('ChillActivityBundle:Activity:edit.html.twig', [
'person' => $entity->getPerson(),
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
]);
}
/**
* Creates a form to create a Activity entity.
*
* @param Activity $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Activity $entity)
{
return $this->createForm(
ActivityType::class,
$entity,
[
'action' => $this->generateUrl('chill_activity_activity_create', [
'person_id' => $entity->getPerson()->getId(),
]),
'method' => 'POST',
'center' => $entity->getCenter(),
'role' => new Role('CHILL_ACTIVITY_CREATE'),
]
);
}
/**
* Creates a form to delete a Activity entity by id.
*
* @param mixed $id The entity id
* @param mixed $person
*
* @return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm(int $id, ?Person $person, ?AccompanyingPeriod $accompanyingPeriod): FormInterface
private function createDeleteForm($id, $person)
{
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
$params['id'] = $id;
return $this->createFormBuilder()
->setAction($this->generateUrl('chill_activity_activity_delete', $params))
->setAction($this->generateUrl(
'chill_activity_activity_delete',
['id' => $id, 'person_id' => $person->getId()]
))
->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}
private function getEntity(Request $request): array
/**
* Creates a form to edit a Activity entity.
*
* @param Activity $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(Activity $entity)
{
$person = $accompanyingPeriod = null;
if ($request->query->has('person_id')) {
$person_id = $request->get('person_id');
$person = $this->personRepository->find($person_id);
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 = $this->accompanyingPeriodRepository->find($accompanying_period_id);
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');
}
return [
$person,
$accompanyingPeriod,
];
return $this->createForm(ActivityType::class, $entity, [
'action' => $this->generateUrl(
'chill_activity_activity_update',
[
'id' => $entity->getId(),
'person_id' => $entity->getPerson()->getId(),
]
),
'method' => 'PUT',
'center' => $entity->getCenter(),
'role' => new Role('CHILL_ACTIVITY_UPDATE'),
]);
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -17,9 +19,6 @@ 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
{
/**

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -17,9 +19,6 @@ 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
{
/**

View File

@@ -0,0 +1,185 @@
<?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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Controller;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Form\ActivityTypeType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
class ActivityTypeController extends AbstractController
{
/**
* Creates a new ActivityType entity.
*/
public function createAction(Request $request)
{
$entity = new ActivityType();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activitytype_show', ['id' => $entity->getId()]));
}
return $this->render('ChillActivityBundle:ActivityType:new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
]);
}
/**
* Displays a form to edit an existing ActivityType entity.
*
* @param mixed $id
*/
public function editAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ChillActivityBundle:ActivityType')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityType entity.');
}
$editForm = $this->createEditForm($entity);
return $this->render('ChillActivityBundle:ActivityType:edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
}
/**
* Lists all ActivityType entities.
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('ChillActivityBundle:ActivityType')->findAll();
return $this->render('ChillActivityBundle:ActivityType:index.html.twig', [
'entities' => $entities,
]);
}
/**
* Displays a form to create a new ActivityType entity.
*/
public function newAction()
{
$entity = new ActivityType();
$form = $this->createCreateForm($entity);
return $this->render('ChillActivityBundle:ActivityType:new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
]);
}
/**
* Finds and displays a ActivityType entity.
*
* @param mixed $id
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ChillActivityBundle:ActivityType')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityType entity.');
}
return $this->render('ChillActivityBundle:ActivityType:show.html.twig', [
'entity' => $entity,
]);
}
/**
* Edits an existing ActivityType entity.
*
* @param mixed $id
*/
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ChillActivityBundle:ActivityType')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityType entity.');
}
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activitytype_edit', ['id' => $id]));
}
return $this->render('ChillActivityBundle:ActivityType:edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
}
/**
* Creates a form to create a ActivityType entity.
*
* @param ActivityType $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(ActivityType $entity)
{
$form = $this->createForm(ActivityTypeType::class, $entity, [
'action' => $this->generateUrl('chill_activity_activitytype_create'),
'method' => 'POST',
]);
$form->add('submit', SubmitType::class, ['label' => 'Create']);
return $form;
}
/**
* Creates a form to edit a ActivityType entity.
*
* @param ActivityType $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(ActivityType $entity)
{
$form = $this->createForm(ActivityTypeType::class, $entity, [
'action' => $this->generateUrl('chill_activity_activitytype_update', ['id' => $entity->getId()]),
'method' => 'PUT',
]);
$form->add('submit', SubmitType::class, ['label' => 'Update']);
return $form;
}
}

View File

@@ -1,30 +0,0 @@
<?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;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
class AdminActivityPresenceController extends CRUDController
{
/**
* @param \Doctrine\ORM\QueryBuilder|mixed $query
*
* @return \Doctrine\ORM\QueryBuilder|mixed
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
/** @var \Doctrine\ORM\QueryBuilder $query */
return $query->orderBy('e.id', 'ASC');
}
}

View File

@@ -1,30 +0,0 @@
<?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;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
class AdminActivityTypeCategoryController extends CRUDController
{
/**
* @param \Doctrine\ORM\QueryBuilder|mixed $query
*
* @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');
}
}

View File

@@ -1,31 +0,0 @@
<?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;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
class AdminActivityTypeController extends CRUDController
{
/**
* @param \Doctrine\ORM\QueryBuilder|mixed $query
*
* @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')
->addOrderBy('e.id', 'ASC');
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -14,28 +16,29 @@ 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 Symfony\Component\DependencyInjection\ContainerAwareInterface;
class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
use function in_array;
/**
* Load reports into DB.
*/
class LoadActivity extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface
{
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
private EntityManagerInterface $em;
/**
* @var \Faker\Generator
*/
private $faker;
public function __construct(EntityManagerInterface $em)
public function __construct()
{
$this->faker = FakerFactory::create('fr_FR');
$this->em = $em;
}
public function getOrder()
@@ -45,25 +48,23 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
public function load(ObjectManager $manager)
{
$persons = $this->em
->getRepository(Person::class)
$persons = $this->container->get('doctrine.orm.entity_manager')
->getRepository('ChillPersonBundle:Person')
->findAll();
foreach ($persons as $person) {
$activityNbr = mt_rand(0, 3);
for ($i = 0; $i < $activityNbr; ++$i) {
echo 'Creating an activity type for : ' . $person . "\n";
$activity = $this->newRandomActivity($person);
if (null !== $activity) {
$manager->persist($activity);
}
$manager->persist($activity);
}
}
$manager->flush();
}
public function newRandomActivity($person): ?Activity
public function newRandomActivity($person)
{
$activity = (new Activity())
->setUser($this->getRandomUser())
@@ -71,18 +72,15 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
->setDate($this->faker->dateTimeThisYear())
->setDurationTime($this->faker->dateTime(36000))
->setType($this->getRandomActivityType())
->setScope($this->getRandomScope());
->setScope($this->getRandomScope())
->setAttendee($this->faker->boolean());
// ->setAttendee($this->faker->boolean())
$usedId = [];
for ($i = 0; mt_rand(0, 4) > $i; ++$i) {
$reason = $this->getRandomActivityReason();
if (null !== $reason) {
$activity->addReason($reason);
} else {
return null;
}
$reason = $this->getRandomActivityReason($usedId);
$usedId[] = $reason->getId();
$activity->addReason($reason);
}
return $activity;
@@ -93,10 +91,15 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
*
* @return \Chill\ActivityBundle\Entity\ActivityReason
*/
private function getRandomActivityReason()
private function getRandomActivityReason(array $excludingIds)
{
$reasonRef = LoadActivityReason::$references[array_rand(LoadActivityReason::$references)];
if (in_array($this->getReference($reasonRef)->getId(), $excludingIds, true)) {
// we have a reason which should be excluded. Find another...
return $this->getRandomActivityReason($excludingIds);
}
return $this->getReference($reasonRef);
}

View File

@@ -1,47 +0,0 @@
<?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 Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\DataFixtures\ORM\LoadAbstractNotificationsTrait;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
/**
* Load notififications into database.
*/
class LoadActivityNotifications extends AbstractFixture implements DependentFixtureInterface
{
use LoadAbstractNotificationsTrait;
public $notifs = [
[
'message' => 'Hello !',
'entityClass' => Activity::class,
'entityRef' => 'activity_gerard depardieu',
'sender' => 'center a_social',
'addressees' => [
'center a_social',
'center a_administrative',
'center a_direction',
'multi_center',
],
],
];
public function getDependencies()
{
return [
LoadActivity::class,
];
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,14 +14,14 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\ActivityBundle\Entity\ActivityType;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
/**
* Description of LoadActivityType.
*/
class LoadActivityType extends Fixture implements OrderedFixtureInterface
class LoadActivityType extends AbstractFixture implements OrderedFixtureInterface
{
public static $references = [];
@@ -31,37 +33,17 @@ class LoadActivityType extends Fixture implements OrderedFixtureInterface
public function load(ObjectManager $manager)
{
$types = [
// Exange
[
'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' => 'Courriel', 'en' => 'Email', 'nl' => 'Email'],
'category' => 'exchange', ],
// Meeting
[
'name' => ['fr' => 'Point technique encadrant'],
'category' => 'meeting', ],
[
'name' => ['fr' => 'Réunion avec des partenaires'],
'category' => 'meeting', ],
[
'name' => ['fr' => 'Commission pluridisciplinaire et pluri-institutionnelle'],
'category' => 'meeting', ],
['name' => ['fr' => 'Appel téléphonique', 'en' => 'Telephone call', 'nl' => 'Telefoon appel']],
['name' => ['fr' => 'Entretien', 'en' => 'Interview', 'nl' => 'Vraaggesprek']],
['name' => ['fr' => 'Inspection', 'en' => 'Inspection', 'nl' => 'Inspectie']],
];
foreach ($types as $t) {
echo 'Creating activity type : ' . $t['name']['fr'] . ' (cat:' . $t['category'] . " \n";
echo 'Creating activity type : ' . $t['name']['en'] . "\n";
$activityType = (new ActivityType())
->setName(($t['name']))
->setCategory($this->getReference('activity_type_cat_' . $t['category']))
->setSocialIssuesVisible(1)
->setSocialActionsVisible(1);
->setName(($t['name']));
$manager->persist($activityType);
$reference = 'activity_type_' . $t['name']['fr'];
$reference = 'activity_type_' . $t['name']['en'];
$this->addReference($reference, $activityType);
static::$references[] = $reference;
}

View File

@@ -1,59 +0,0 @@
<?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 Chill\ActivityBundle\Entity\ActivityTypeCategory;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
/**
* Fixtures for ActivityTypeCategory.
*/
class LoadActivityTypeCategory extends Fixture implements OrderedFixtureInterface
{
public static $references = [];
public function getOrder()
{
return 16050;
}
public function load(ObjectManager $manager)
{
$categories = [
[
'name' => ['fr' => 'Échange avec usager', 'en' => 'Exchange with user'],
'ref' => 'exchange',
],
[
'name' => ['fr' => 'Réunion', 'en' => 'Meeting'],
'ref' => 'meeting',
],
];
foreach ($categories as $cat) {
echo 'Creating activity type category : ' . $cat['ref'] . "\n";
$newCat = (new ActivityTypeCategory())
->setName(($cat['name']));
$manager->persist($newCat);
$reference = 'activity_type_cat_' . $cat['ref'];
$this->addReference($reference, $newCat);
static::$references[] = $reference;
}
$manager->flush();
}
}

View File

@@ -1,31 +1,27 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
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.
*/
class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterface
{
public function getOrder()
@@ -48,10 +44,9 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac
}
break;
case 'administrative':
case 'direction':
if (in_array($scope->getName()['en'], ['administrative', 'social'], true)) {
if (\in_array($scope->getName()['en'], ['administrative', 'social'], true)) {
break 2; // we do not want any power on social or administrative
}
@@ -69,10 +64,7 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeUpdate);
$roleScopeCreate = (new RoleScope())
->setRole(ActivityVoter::CREATE_ACCOMPANYING_COURSE)
->setScope($scope);
$roleScopeCreate = (new RoleScope())
->setRole(ActivityVoter::CREATE_PERSON)
->setRole('CHILL_ACTIVITY_CREATE')
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeCreate);
$roleScopeDelete = (new RoleScope())

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -25,6 +27,9 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
*/
class ChillActivityExtension extends Extension implements PrependExtensionInterface
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
@@ -37,18 +42,16 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
$loader->load('services/export.yaml');
$loader->load('services/repositories.yaml');
$loader->load('services/fixtures.yaml');
$loader->load('services/menu.yaml');
$loader->load('services/controller.yaml');
$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)
{
$this->prependRoutes($container);
$this->prependAuthorization($container);
$this->prependCruds($container);
}
public function prependAuthorization(ContainerBuilder $container)
@@ -56,16 +59,9 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
$container->prependExtensionConfig('security', [
'role_hierarchy' => [
ActivityVoter::UPDATE => [ActivityVoter::SEE_DETAILS],
ActivityVoter::CREATE_PERSON => [ActivityVoter::SEE_DETAILS],
ActivityVoter::CREATE_ACCOMPANYING_COURSE => [ActivityVoter::SEE_DETAILS],
ActivityVoter::CREATE => [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,
],
],
]);
}
@@ -84,75 +80,4 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
],
]);
}
protected function prependCruds(ContainerBuilder $container)
{
$container->prependExtensionConfig('chill_main', [
'cruds' => [
[
'class' => \Chill\ActivityBundle\Entity\ActivityType::class,
'name' => 'activity_type',
'base_path' => '/admin/activity/type',
'form_class' => \Chill\ActivityBundle\Form\ActivityTypeType::class,
'controller' => \Chill\ActivityBundle\Controller\AdminActivityTypeController::class,
'actions' => [
'index' => [
'template' => '@ChillActivity/ActivityType/index.html.twig',
'role' => 'ROLE_ADMIN',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityType/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityType/edit.html.twig',
],
],
],
[
'class' => \Chill\ActivityBundle\Entity\ActivityTypeCategory::class,
'name' => 'activity_type_category',
'base_path' => '/admin/activity/type_category',
'form_class' => \Chill\ActivityBundle\Form\ActivityTypeCategoryType::class,
'controller' => \Chill\ActivityBundle\Controller\AdminActivityTypeCategoryController::class,
'actions' => [
'index' => [
'template' => '@ChillActivity/ActivityTypeCategory/index.html.twig',
'role' => 'ROLE_ADMIN',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityTypeCategory/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityTypeCategory/edit.html.twig',
],
],
],
[
'class' => \Chill\ActivityBundle\Entity\ActivityPresence::class,
'name' => 'activity_presence',
'base_path' => '/admin/activity/presence',
'form_class' => \Chill\ActivityBundle\Form\ActivityPresenceType::class,
'controller' => \Chill\ActivityBundle\Controller\AdminActivityPresenceController::class,
'actions' => [
'index' => [
'template' => '@ChillActivity/ActivityPresence/index.html.twig',
'role' => 'ROLE_ADMIN',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityPresence/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillActivity/ActivityPresence/edit.html.twig',
],
],
],
],
]);
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -23,6 +25,9 @@ use function is_int;
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('chill_activity');

View File

@@ -1,275 +1,131 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Entity;
use Chill\ActivityBundle\Validator\Constraints as ActivityValidator;
use Chill\DocStoreBundle\Entity\Document;
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 DateTime;
use Doctrine\Common\Collections\ArrayCollection;
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.
*
* @ORM\Entity(repositoryClass="Chill\ActivityBundle\Repository\ActivityRepository")
* @ORM\Entity
* @ORM\Table(name="activity")
* @ORM\HasLifecycleCallbacks
* @DiscriminatorMap(typeProperty="type", mapping={
* "activity": Activity::class
* })
* @ActivityValidator\ActivityValidity
*
* TODO see if necessary
* UserCircleConsistency(
* @UserCircleConsistency(
* "CHILL_ACTIVITY_SEE_DETAILS",
* getUserFunction="getUser",
* path="scope")
*/
class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCenterInterface, HasScopeInterface
class Activity implements HasCenterInterface, HasScopeInterface
{
public const SENTRECEIVED_RECEIVED = 'received';
public const SENTRECEIVED_SENT = 'sent';
/**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod")
* @Groups({"read"})
* @var bool
* @ORM\Column(type="boolean")
*/
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;
private $attendee;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_")
*/
private CommentEmbeddable $comment;
private $comment;
/**
* @var DateTime
* @ORM\Column(type="datetime")
*/
private DateTime $date;
private $date;
/**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"})
* @var DateTime
* @ORM\Column(type="time")
*/
private Collection $documents;
/**
* @ORM\Column(type="time", nullable=true)
*/
private ?DateTime $durationTime = null;
/**
* @ORM\Column(type="boolean", options={"default": false})
*/
private bool $emergency = false;
private $durationTime;
/**
* @var int
*
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"read"})
*/
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
* @groups({"read"})
*/
private ?Location $location = null;
private $id;
/**
* @var Person
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person")
*/
private ?Person $person = null;
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person")
* @Groups({"read"})
*/
private ?Collection $persons = null;
private $person;
/**
* @var ActivityReason
* @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason")
*/
private Collection $reasons;
private $reasons;
/**
* @var Scope
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
*/
private ?Scope $scope = null;
private $scope;
/**
* @ORM\Column(type="string", options={"default": ""})
* @var ActivityType
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityType")
*/
private string $sentReceived = '';
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialAction")
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialaction")
* @Groups({"read"})
*/
private Collection $socialActions;
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue")
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue")
* @Groups({"read"})
*/
private Collection $socialIssues;
/**
* @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
* @Groups({"read"})
*/
private ?Collection $thirdParties = null;
/**
* @ORM\Column(type="time", nullable=true)
*/
private ?DateTime $travelTime = null;
private $type;
/**
* @var User
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
*/
private User $user;
private $user;
/**
* @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User")
* @Groups({"read"})
* Activity constructor.
*/
private ?Collection $users = null;
public function __construct()
{
$this->reasons = new ArrayCollection();
$this->comment = new CommentEmbeddable();
$this->persons = new ArrayCollection();
$this->thirdParties = new ArrayCollection();
$this->documents = new ArrayCollection();
$this->users = new ArrayCollection();
$this->socialIssues = new ArrayCollection();
$this->socialActions = new ArrayCollection();
}
public function addDocument(Document $document): self
/**
* Add a reason.
*
* @return Activity
*/
public function addReason(ActivityReason $reason)
{
$this->documents[] = $document;
$this->reasons[] = $reason;
return $this;
}
/**
* Add a person to the person list.
* Get attendee.
*
* @return bool
*/
public function addPerson(?Person $person): self
{
if (null !== $person) {
if (!$this->persons->contains($person)) {
$this->persons[] = $person;
}
}
return $this;
}
public function addReason(ActivityReason $reason): self
{
$this->reasons->add($reason);
return $this;
}
public function addSocialAction(SocialAction $socialAction): self
{
if (!$this->socialActions->contains($socialAction)) {
$this->socialActions[] = $socialAction;
}
return $this;
}
public function addSocialIssue(SocialIssue $socialIssue): self
{
if (!$this->socialIssues->contains($socialIssue)) {
$this->socialIssues[] = $socialIssue;
}
return $this;
}
public function addThirdParty(?ThirdParty $thirdParty): self
{
if (null !== $thirdParty) {
if (!$this->thirdParties->contains($thirdParty)) {
$this->thirdParties[] = $thirdParty;
}
}
return $this;
}
public function addUser(?User $user): self
{
if (null !== $user) {
if (!$this->users->contains($user)) {
$this->users[] = $user;
}
}
return $this;
}
public function getAccompanyingPeriod(): ?AccompanyingPeriod
{
return $this->accompanyingPeriod;
}
public function getActivityType(): ActivityType
{
return $this->activityType;
}
public function getAttendee(): ?ActivityPresence
public function getAttendee()
{
return $this->attendee;
}
@@ -277,326 +133,202 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/**
* get the center
* center is extracted from person.
*
* @return Center
*/
public function getCenter(): ?Center
public function getCenter()
{
if ($this->person instanceof Person) {
return $this->person->getCenter();
}
return null;
return $this->person->getCenter();
}
public function getComment(): CommentEmbeddable
/**
* @return \Chill\MainBundle\Entity\Embeddalbe\CommentEmbeddable
*/
public function getComment()
{
return $this->comment;
}
public function getDate(): DateTime
/**
* Get date.
*
* @return DateTime
*/
public function getDate()
{
return $this->date;
}
public function getDocuments(): Collection
{
return $this->documents;
}
public function getDurationTime(): ?DateTime
/**
* Get durationTime.
*
* @return DateTime
*/
public function getDurationTime()
{
return $this->durationTime;
}
public function getEmergency(): bool
{
return $this->emergency;
}
public function getId(): ?int
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function getPerson(): ?Person
/**
* Get person.
*
* @return Person
*/
public function getPerson()
{
return $this->person;
}
public function getPersons(): Collection
{
return $this->persons;
}
public function getPersonsAssociated(): array
{
if (null !== $this->accompanyingPeriod) {
$personsAssociated = [];
foreach ($this->accompanyingPeriod->getParticipations() as $participation) {
if ($this->persons->contains($participation->getPerson())) {
$personsAssociated[] = $participation->getPerson();
}
}
return $personsAssociated;
}
return [];
}
public function getPersonsNotAssociated(): array
{
if (null !== $this->accompanyingPeriod) {
$personsNotAssociated = [];
// TODO better semantic with: return $this->persons->filter(...);
foreach ($this->persons as $person) {
if ($this->accompanyingPeriod->getOpenParticipationContainsPerson($person) === null) {
$personsNotAssociated[] = $person;
}
}
return $personsNotAssociated;
}
return [];
}
public function getReasons(): Collection
/**
* Get reasons.
*
* @return Collection
*/
public function getReasons()
{
return $this->reasons;
}
public function getScope(): ?Scope
/**
* Get scope.
*
* @return Scope
*/
public function getScope()
{
return $this->scope;
}
public function getSentReceived(): string
/**
* Get type.
*
* @return ActivityType
*/
public function getType()
{
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;
return $this->type;
}
/**
* @deprecated
* Get user.
*
* @return User
*/
public function getType(): ActivityType
{
return $this->activityType;
}
public function getUser(): User
public function getUser()
{
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
public function removeReason(ActivityReason $reason)
{
$this->reasons->removeElement($reason);
}
public function removeSocialAction(SocialAction $socialAction): self
{
$this->socialActions->removeElement($socialAction);
return $this;
}
public function removeSocialIssue(SocialIssue $socialIssue): self
{
$this->socialIssues->removeElement($socialIssue);
return $this;
}
public function removeThirdParty(ThirdParty $thirdParty): void
{
$this->thirdParties->removeElement($thirdParty);
}
public function removeUser(User $user): void
{
$this->users->removeElement($user);
}
public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self
{
$this->accompanyingPeriod = $accompanyingPeriod;
return $this;
}
public function setActivityType(ActivityType $activityType): self
{
$this->activityType = $activityType;
return $this;
}
public function setAttendee(ActivityPresence $attendee): self
/**
* Set attendee.
*
* @param bool $attendee
*
* @return Activity
*/
public function setAttendee($attendee)
{
$this->attendee = $attendee;
return $this;
}
public function setComment(CommentEmbeddable $comment): self
/**
* @param \Chill\MainBundle\Entity\Embeddalbe\CommentEmbeddable $comment
*/
public function setComment($comment)
{
$this->comment = $comment;
return $this;
}
public function setDate(DateTime $date): self
/**
* Set date.
*
* @param DateTime $date
*
* @return Activity
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
public function setDocuments(Collection $documents): self
{
$this->documents = $documents;
return $this;
}
public function setDurationTime(?DateTime $durationTime): self
/**
* Set durationTime.
*
* @param DateTime $durationTime
*
* @return Activity
*/
public function setDurationTime($durationTime)
{
$this->durationTime = $durationTime;
return $this;
}
public function setEmergency(bool $emergency): self
{
$this->emergency = $emergency;
return $this;
}
public function setLocation(?Location $location): Activity
{
$this->location = $location;
return $this;
}
public function setPerson(?Person $person): self
/**
* Set person.
*
* @return Activity
*/
public function setPerson(Person $person)
{
$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
/**
* Set scope.
*
* @return Activity
*/
public function setScope(Scope $scope)
{
$this->scope = $scope;
return $this;
}
public function setSentReceived(?string $sentReceived): self
/**
* Set type.
*
* @return Activity
*/
public function setType(ActivityType $type)
{
$this->sentReceived = (string) $sentReceived;
return $this;
}
public function setThirdParties(?Collection $thirdParties): self
{
$this->thirdParties = $thirdParties;
return $this;
}
public function setTravelTime(DateTime $travelTime): self
{
$this->travelTime = $travelTime;
$this->type = $type;
return $this;
}
/**
* @deprecated
* Set user.
*
* @return Activity
*/
public function setType(ActivityType $activityType): self
{
$this->activityType = $activityType;
return $this;
}
public function setUser(UserInterface $user): self
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
public function setUsers(?Collection $users): self
{
$this->users = $users;
return $this;
}
}

View File

@@ -1,87 +0,0 @@
<?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\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ActivityPresence.
*
* @ORM\Entity
* @ORM\Table(name="activitytpresence")
* @ORM\HasLifecycleCallbacks
*/
class ActivityPresence
{
/**
* @ORM\Column(type="boolean")
*/
private bool $active = true;
/**
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private ?int $id;
/**
* @ORM\Column(type="json")
*/
private array $name = [];
/**
* Get active
* return true if the category type is active.
*/
public function getActive(): bool
{
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.
*/
public function isActive(): bool
{
return $this->getActive();
}
/**
* Set active
* set to true if the category type is active.
*/
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function setName(array $name): self
{
$this->name = $name;
return $this;
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -14,8 +16,6 @@ namespace Chill\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ActivityReason.
*
* @ORM\Entity
* @ORM\Table(name="activityreason")
* @ORM\HasLifecycleCallbacks
@@ -47,7 +47,7 @@ class ActivityReason
/**
* @var array
* @ORM\Column(type="json")
* @ORM\Column(type="json_array")
*/
private $name;

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -15,8 +17,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ActivityReasonCategory.
*
* @ORM\Entity
* @ORM\Table(name="activityreasoncategory")
* @ORM\HasLifecycleCallbacks
@@ -40,7 +40,7 @@ class ActivityReasonCategory
/**
* @var string
* @ORM\Column(type="json")
* @ORM\Column(type="json_array")
*/
private $name;

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,771 +14,119 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class ActivityType.
*
* @ORM\Entity
* @ORM\Table(name="activitytype")
* @ORM\HasLifecycleCallbacks
*/
class ActivityType
{
public const FIELD_INVISIBLE = 0;
public const FIELD_OPTIONAL = 1;
public const FIELD_REQUIRED = 2;
/**
* @deprecated not in use
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $accompanyingPeriodLabel = '';
/**
* @deprecated not in use
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $accompanyingPeriodVisible = self::FIELD_INVISIBLE;
/**
* @var bool
* @ORM\Column(type="boolean")
* @Groups({"read"})
*/
private bool $active = true;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $attendeeLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $attendeeVisible = self::FIELD_OPTIONAL;
/**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityTypeCategory")
*/
private ?ActivityTypeCategory $category = null;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $commentLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $commentVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $dateLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 2})
*/
private int $dateVisible = self::FIELD_REQUIRED;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $documentsLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $documentsVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $durationTimeLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $durationTimeVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $emergencyLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $emergencyVisible = self::FIELD_INVISIBLE;
private $active = true;
/**
* @var int
*
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private ?int $id;
private $id;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
* @var array
* @ORM\Column(type="json_array")
*/
private string $locationLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $locationVisible = self::FIELD_INVISIBLE;
/**
* @ORM\Column(type="json")
* @Groups({"read"})
*/
private array $name = [];
/**
* @ORM\Column(type="float", options={"default": "0.0"})
*/
private float $ordering = 0.0;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $personLabel = '';
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $personsLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
* @Groups({"read"})
*/
private int $personsVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 2})
*/
private int $personVisible = self::FIELD_REQUIRED;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $reasonsLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $reasonsVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $sentReceivedLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $sentReceivedVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $socialActionsLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
* @Assert\EqualTo(propertyPath="socialIssuesVisible", message="This parameter must be equal to social issue parameter")
*/
private int $socialActionsVisible = self::FIELD_INVISIBLE;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*
* @deprecated not in use
*/
private string $socialDataLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*
* @deprecated not in use
*/
private int $socialDataVisible = self::FIELD_INVISIBLE;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $socialIssuesLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $socialIssuesVisible = self::FIELD_INVISIBLE;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $thirdPartiesLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
* @Groups({"read"})
*/
private int $thirdPartiesVisible = self::FIELD_INVISIBLE;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $travelTimeLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $travelTimeVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $userLabel = '';
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $usersLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
* @Groups({"read"})
*/
private int $usersVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 2})
*/
private int $userVisible = self::FIELD_REQUIRED;
private $name;
/**
* Get active
* return true if the type is active.
*
* @return bool
*/
public function getActive(): bool
public function getActive()
{
return $this->active;
}
public function getAttendeeLabel(): string
{
return $this->attendeeLabel;
}
public function getAttendeeVisible(): int
{
return $this->attendeeVisible;
}
public function getCategory(): ?ActivityTypeCategory
{
return $this->category;
}
public function getCommentLabel(): string
{
return $this->commentLabel;
}
public function getCommentVisible(): int
{
return $this->commentVisible;
}
public function getDateLabel(): string
{
return $this->dateLabel;
}
public function getDateVisible(): int
{
return $this->dateVisible;
}
public function getDocumentsLabel(): string
{
return $this->documentsLabel;
}
public function getDocumentsVisible(): int
{
return $this->documentsVisible;
}
public function getDurationTimeLabel(): string
{
return $this->durationTimeLabel;
}
public function getDurationTimeVisible(): int
{
return $this->durationTimeVisible;
}
public function getEmergencyLabel(): string
{
return $this->emergencyLabel;
}
public function getEmergencyVisible(): int
{
return $this->emergencyVisible;
}
/**
* Get id.
*
* @return int
*/
public function getId(): int
public function getId()
{
return $this->id;
}
public function getLabel(string $field): ?string
{
$property = $field . 'Label';
if (!property_exists($this, $property)) {
throw new InvalidArgumentException('Field "' . $field . '" not found');
}
return $this->{$property};
}
public function getLocationLabel(): ?string
{
return $this->locationLabel;
}
public function getLocationVisible(): ?int
{
return $this->locationVisible;
}
/**
* Get name.
*
* @param mixed|null $locale
*
* @return array | string
*/
public function getName(): array
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;
}
public function getOrdering(): float
{
return $this->ordering;
}
public function getPersonLabel(): string
{
return $this->personLabel;
}
public function getPersonsLabel(): string
{
return $this->personsLabel;
}
public function getPersonsVisible(): int
{
return $this->personsVisible;
}
public function getPersonVisible(): int
{
return $this->personVisible;
}
public function getReasonsLabel(): string
{
return $this->reasonsLabel;
}
public function getReasonsVisible(): int
{
return $this->reasonsVisible;
}
public function getSentReceivedLabel(): string
{
return $this->sentReceivedLabel;
}
public function getSentReceivedVisible(): int
{
return $this->sentReceivedVisible;
}
public function getSocialActionsLabel(): ?string
{
return $this->socialActionsLabel;
}
public function getSocialActionsVisible(): ?int
{
return $this->socialActionsVisible;
}
public function getSocialIssuesLabel(): ?string
{
return $this->socialIssuesLabel;
}
public function getSocialIssuesVisible(): ?int
{
return $this->socialIssuesVisible;
}
public function getThirdPartiesLabel(): string
{
return $this->thirdPartiesLabel;
}
public function getThirdPartiesVisible(): int
{
return $this->thirdPartiesVisible;
}
public function getTravelTimeLabel(): string
{
return $this->travelTimeLabel;
}
public function getTravelTimeVisible(): int
{
return $this->travelTimeVisible;
}
public function getUserLabel(): string
{
return $this->userLabel;
}
public function getUsersLabel(): string
{
return $this->usersLabel;
}
public function getUsersVisible(): int
{
return $this->usersVisible;
}
public function getUserVisible(): int
{
return $this->userVisible;
}
/**
* Is active
* return true if the type is active.
*
* @return bool
*/
public function isActive(): bool
public function isActive()
{
return $this->getActive();
}
public function isRequired(string $field): bool
{
$property = $field . 'Visible';
if (!property_exists($this, $property)) {
throw new InvalidArgumentException('Field "' . $field . '" not found');
}
return self::FIELD_REQUIRED === $this->{$property};
}
public function isVisible(string $field): bool
{
$property = $field . 'Visible';
if (!property_exists($this, $property)) {
throw new InvalidArgumentException('Field "' . $field . '" not found');
}
return self::FIELD_INVISIBLE !== $this->{$property};
}
/**
* Set active
* set to true if the type is active.
*
* @param bool $active
*
* @return ActivityType
*/
public function setActive(bool $active): self
public function setActive($active)
{
$this->active = $active;
return $this;
}
public function setAttendeeLabel(string $attendeeLabel): self
{
$this->attendeeLabel = $attendeeLabel;
return $this;
}
public function setAttendeeVisible(int $attendeeVisible): self
{
$this->attendeeVisible = $attendeeVisible;
return $this;
}
public function setCategory(?ActivityTypeCategory $category): self
{
$this->category = $category;
return $this;
}
public function setCommentLabel(string $commentLabel): self
{
$this->commentLabel = $commentLabel;
return $this;
}
public function setCommentVisible(int $commentVisible): self
{
$this->commentVisible = $commentVisible;
return $this;
}
public function setDateLabel(string $dateLabel): self
{
$this->dateLabel = $dateLabel;
return $this;
}
public function setDateVisible(int $dateVisible): self
{
$this->dateVisible = $dateVisible;
return $this;
}
public function setDocumentsLabel(string $documentsLabel): self
{
$this->documentsLabel = $documentsLabel;
return $this;
}
public function setDocumentsVisible(int $documentsVisible): self
{
$this->documentsVisible = $documentsVisible;
return $this;
}
public function setDurationTimeLabel(string $durationTimeLabel): self
{
$this->durationTimeLabel = $durationTimeLabel;
return $this;
}
public function setDurationTimeVisible(int $durationTimeVisible): self
{
$this->durationTimeVisible = $durationTimeVisible;
return $this;
}
public function setEmergencyLabel(string $emergencyLabel): self
{
$this->emergencyLabel = $emergencyLabel;
return $this;
}
public function setEmergencyVisible(int $emergencyVisible): self
{
$this->emergencyVisible = $emergencyVisible;
return $this;
}
public function setLocationLabel(string $locationLabel): self
{
$this->locationLabel = $locationLabel;
return $this;
}
public function setLocationVisible(int $locationVisible): self
{
$this->locationVisible = $locationVisible;
return $this;
}
/**
* Set name.
*
* @param array $name
*
* @return ActivityType
*/
public function setName(array $name): self
public function setName($name)
{
$this->name = $name;
return $this;
}
public function setOrdering(float $ordering): self
{
$this->ordering = $ordering;
return $this;
}
public function setPersonLabel(string $personLabel): self
{
$this->personLabel = $personLabel;
return $this;
}
public function setPersonsLabel(string $personsLabel): self
{
$this->personsLabel = $personsLabel;
return $this;
}
public function setPersonsVisible(int $personsVisible): self
{
$this->personsVisible = $personsVisible;
return $this;
}
public function setPersonVisible(int $personVisible): self
{
$this->personVisible = $personVisible;
return $this;
}
public function setReasonsLabel(string $reasonsLabel): self
{
$this->reasonsLabel = $reasonsLabel;
return $this;
}
public function setReasonsVisible(int $reasonsVisible): self
{
$this->reasonsVisible = $reasonsVisible;
return $this;
}
public function setSentReceivedLabel(string $sentReceivedLabel): self
{
$this->sentReceivedLabel = $sentReceivedLabel;
return $this;
}
public function setSentReceivedVisible(int $sentReceivedVisible): self
{
$this->sentReceivedVisible = $sentReceivedVisible;
return $this;
}
public function setSocialActionsLabel(string $socialActionsLabel): self
{
$this->socialActionsLabel = $socialActionsLabel;
return $this;
}
public function setSocialActionsVisible(int $socialActionsVisible): self
{
$this->socialActionsVisible = $socialActionsVisible;
return $this;
}
public function setSocialIssuesLabel(string $socialIssuesLabel): self
{
$this->socialIssuesLabel = $socialIssuesLabel;
return $this;
}
public function setSocialIssuesVisible(int $socialIssuesVisible): self
{
$this->socialIssuesVisible = $socialIssuesVisible;
return $this;
}
public function setThirdPartiesLabel(string $thirdPartiesLabel): self
{
$this->thirdPartiesLabel = $thirdPartiesLabel;
return $this;
}
public function setThirdPartiesVisible(int $thirdPartiesVisible): self
{
$this->thirdPartiesVisible = $thirdPartiesVisible;
return $this;
}
public function setTravelTimeLabel(string $TravelTimeLabel): self
{
$this->travelTimeLabel = $TravelTimeLabel;
return $this;
}
public function setTravelTimeVisible(int $TravelTimeVisible): self
{
$this->travelTimeVisible = $TravelTimeVisible;
return $this;
}
public function setUserLabel(string $userLabel): self
{
$this->userLabel = $userLabel;
return $this;
}
public function setUsersLabel(string $usersLabel): self
{
$this->usersLabel = $usersLabel;
return $this;
}
public function setUsersVisible(int $usersVisible): self
{
$this->usersVisible = $usersVisible;
return $this;
}
public function setUserVisible(int $userVisible): self
{
$this->userVisible = $userVisible;
return $this;
}
}

View File

@@ -1,108 +0,0 @@
<?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\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="activitytypecategory")
* @ORM\HasLifecycleCallbacks
*/
class ActivityTypeCategory
{
/**
* @ORM\Column(type="boolean")
*/
private bool $active = true;
/**
* @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 active
* return true if the category type is active.
*/
public function getActive(): bool
{
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.
*/
public function isActive(): bool
{
return $this->getActive();
}
/**
* Set active
* set to true if the category type is active.
*/
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
/**
* Set name.
*/
public function setName(array $name): self
{
$this->name = $name;
return $this;
}
public function setOrdering(float $ordering): self
{
$this->ordering = $ordering;
return $this;
}
}

View File

@@ -1,77 +0,0 @@
<?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,23 +1,23 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Repository\ActivityReasonCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use RuntimeException;
@@ -29,22 +29,33 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function array_key_exists;
use function count;
class ActivityReasonAggregator implements AggregatorInterface, ExportElementValidatedInterface
class ActivityReasonAggregator implements
AggregatorInterface,
ExportElementValidatedInterface
{
protected ActivityReasonCategoryRepository $activityReasonCategoryRepository;
/**
* @var EntityRepository
*/
protected $categoryRepository;
protected ActivityReasonRepository $activityReasonRepository;
/**
* @var EntityRepository
*/
protected $reasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
/**
* @var TranslatableStringHelper
*/
protected $stringHelper;
public function __construct(
ActivityReasonCategoryRepository $activityReasonCategoryRepository,
ActivityReasonRepository $activityReasonRepository,
TranslatableStringHelper $translatableStringHelper
EntityRepository $categoryRepository,
EntityRepository $reasonRepository,
TranslatableStringHelper $stringHelper
) {
$this->activityReasonCategoryRepository = $activityReasonCategoryRepository;
$this->activityReasonRepository = $activityReasonRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->categoryRepository = $categoryRepository;
$this->reasonRepository = $reasonRepository;
$this->stringHelper = $stringHelper;
}
public function addRole()
@@ -62,7 +73,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
$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);
@@ -79,8 +90,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
) {
$qb->add(
'join',
[
'activity' => new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons'),
['activity' => new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons'),
],
true
);
@@ -111,19 +121,15 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
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",
]
);
$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)
@@ -131,38 +137,46 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
// for performance reason, we load data from db only once
switch ($data['level']) {
case 'reasons':
$this->activityReasonRepository->findBy(['id' => $values]);
$this->reasonRepository->findBy(['id' => $values]);
break;
case 'categories':
$this->activityReasonCategoryRepository->findBy(['id' => $values]);
$this->categoryRepository->findBy(['id' => $values]);
break;
default:
throw new RuntimeException(sprintf("The level data '%s' is invalid.", $data['level']));
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';
return 'reasons' === $data['level'] ?
'Group by reasons'
:
'Group by categories of reason';
}
switch ($data['level']) {
case 'reasons':
$r = $this->activityReasonRepository->find($value);
/** @var \Chill\ActivityBundle\Entity\ActivityReason $r */
$r = $this->reasonRepository->find($value);
return sprintf(
'%s > %s',
$this->translatableStringHelper->localize($r->getCategory()->getName()),
$this->translatableStringHelper->localize($r->getName())
);
return $this->stringHelper->localize($r->getCategory()->getName())
. ' > '
. $this->stringHelper->localize($r->getName());
break;
case 'categories':
$c = $this->activityReasonCategoryRepository->find($value);
$c = $this->categoryRepository->find($value);
return $this->translatableStringHelper->localize($c->getName());
return $this->stringHelper->localize($c->getName());
break;
// no need for a default : the default was already set above
}
};
}
@@ -178,7 +192,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
return ['activity_categories_id'];
}
throw new RuntimeException('The data provided are not recognised.');
throw new RuntimeException('the data provided are not recognised');
}
public function getTitle()
@@ -189,8 +203,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['level']) {
$context
->buildViolation("The reasons's level should not be empty.")
$context->buildViolation("The reasons's level should not be empty")
->addViolation();
}
}

View File

@@ -1,21 +1,22 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Closure;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
@@ -25,16 +26,22 @@ class ActivityTypeAggregator implements AggregatorInterface
{
public const KEY = 'activity_type_aggregator';
protected ActivityTypeRepository $activityTypeRepository;
/**
* @var TranslatableStringHelper
*/
protected $stringHelper;
protected TranslatableStringHelperInterface $translatableStringHelper;
/**
* @var EntityRepository
*/
protected $typeRepository;
public function __construct(
ActivityTypeRepository $activityTypeRepository,
TranslatableStringHelperInterface $translatableStringHelper
EntityRepository $typeRepository,
TranslatableStringHelper $stringHelper
) {
$this->activityTypeRepository = $activityTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->typeRepository = $typeRepository;
$this->stringHelper = $stringHelper;
}
public function addRole()
@@ -48,7 +55,7 @@ class ActivityTypeAggregator implements AggregatorInterface
$qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY));
// add the "group by" part
$qb->addGroupBy(self::KEY);
$groupBy = $qb->addGroupBy(self::KEY);
}
public function applyOn()
@@ -61,23 +68,24 @@ class ActivityTypeAggregator implements AggregatorInterface
// no form required for this aggregator
}
public function getLabels($key, array $values, $data): Closure
public function getLabels($key, array $values, $data)
{
// for performance reason, we load data from db only once
$this->activityTypeRepository->findBy(['id' => $values]);
$this->typeRepository->findBy(['id' => $values]);
return function ($value): string {
return function ($value) {
if ('_header' === $value) {
return 'Activity type';
}
$t = $this->activityTypeRepository->find($value);
/** @var \Chill\ActivityBundle\Entity\ActivityType $r */
$t = $this->typeRepository->find($value);
return $this->translatableStringHelper->localize($t->getName());
return $this->stringHelper->localize($t->getName());
};
}
public function getQueryKeys($data): array
public function getQueryKeys($data)
{
return [self::KEY];
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,9 +14,10 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Closure;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
@@ -23,12 +26,14 @@ class ActivityUserAggregator implements AggregatorInterface
{
public const KEY = 'activity_user_id';
private UserRepository $userRepository;
/**
* @var EntityManagerInterface
*/
protected $em;
public function __construct(
UserRepository $userRepository
) {
$this->userRepository = $userRepository;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function addRole()
@@ -58,14 +63,18 @@ class ActivityUserAggregator implements AggregatorInterface
public function getLabels($key, $values, $data): Closure
{
// preload users at once
$this->userRepository->findBy(['id' => $values]);
$this->em->getRepository(User::class)
->findBy(['id' => $values]);
return function ($value) {
if ('_header' === $value) {
return 'activity user';
}
switch ($value) {
case '_header':
return 'activity user';
return $this->userRepository->find($value)->getUsername();
default:
return $this->em->getRepository(User::class)->find($value)
->getUsername();
}
};
}

View File

@@ -1,42 +1,45 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class CountActivity implements ExportInterface
{
protected ActivityRepository $activityRepository;
/**
* @var EntityManagerInterface
*/
protected $entityManager;
public function __construct(
ActivityRepository $activityRepository
EntityManagerInterface $em
) {
$this->activityRepository = $activityRepository;
$this->entityManager = $em;
}
public function buildForm(FormBuilderInterface $builder)
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
{
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_TABULAR];
return [\Chill\MainBundle\Export\FormatterInterface::TYPE_TABULAR];
}
public function getDescription()
@@ -50,7 +53,12 @@ class CountActivity implements ExportInterface
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Number of activities' : $value;
return static function ($value) {
return '_header' === $value ?
'Number of activities'
:
$value;
};
}
public function getQueryKeys($data)
@@ -75,16 +83,16 @@ class CountActivity implements ExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->entityManager->createQueryBuilder();
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$qb = $this
->activityRepository
->createQueryBuilder('activity')
->select('COUNT(activity.id) as export_count_activity')
$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'))
$qb->where($qb->expr()->in('person.center', ':centers'))
->setParameter('centers', $centers);
return $qb;

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,13 +14,11 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use DateTime;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
@@ -32,11 +32,17 @@ use function array_key_exists;
use function count;
use function in_array;
/**
* Create a list for all activities.
*/
class ListActivity implements ListInterface
{
protected EntityManagerInterface $entityManager;
/**
* @var EntityManagerInterface
*/
protected $entityManager;
protected array $fields = [
protected $fields = [
'id',
'date',
'durationTime',
@@ -50,24 +56,29 @@ class ListActivity implements ListInterface
'person_id',
];
protected TranslatableStringHelperInterface $translatableStringHelper;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
protected TranslatorInterface $translator;
private ActivityRepository $activityRepository;
/**
* @var TranslatorInterface
*/
protected $translator;
public function __construct(
EntityManagerInterface $em,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper,
ActivityRepository $activityRepository
TranslatableStringHelper $translatableStringHelper
) {
$this->entityManager = $em;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->activityRepository = $activityRepository;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('fields', ChoiceType::class, [
@@ -87,6 +98,11 @@ class ListActivity implements ListInterface
]);
}
/**
* {@inheritdoc}
*
* @return type
*/
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_LIST];
@@ -110,7 +126,6 @@ class ListActivity implements ListInterface
return $date->format('d-m-Y');
};
case 'attendee':
return static function ($value) {
if ('_header' === $value) {
@@ -119,16 +134,18 @@ class ListActivity implements ListInterface
return $value ? 1 : 0;
};
case 'list_reasons':
$activityRepository = $this->activityRepository;
/** @var EntityRepository $activityReasonsRepository */
$activityRepository = $this->entityManager
->getRepository('ChillActivityBundle:Activity');
return function ($value) use ($activityRepository): string {
return function ($value) use ($activityRepository) {
if ('_header' === $value) {
return 'activity reasons';
}
$activity = $activityRepository->find($value);
$activity = $activityRepository
->find($value);
return implode(', ', array_map(function (ActivityReason $r) {
return '"' .
@@ -138,23 +155,23 @@ class ListActivity implements ListInterface
. '"';
}, $activity->getReasons()->toArray()));
};
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));
return $this->translatableStringHelper
->localize(json_decode($value, true));
};
default:
@@ -196,7 +213,8 @@ class ListActivity implements ListInterface
// throw an error if any fields are present
if (!array_key_exists('fields', $data)) {
throw new InvalidArgumentException('Any fields have been checked.');
throw new \Doctrine\DBAL\Exception\InvalidArgumentException('any fields '
. 'have been checked');
}
$qb = $this->entityManager->createQueryBuilder();
@@ -215,40 +233,33 @@ class ListActivity implements ListInterface
$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

View File

@@ -1,23 +1,23 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
/**
@@ -31,29 +31,36 @@ class StatActivityDuration implements ExportInterface
/**
* The action for this report.
*
* @var string
*/
protected string $action;
private ActivityRepository $activityRepository;
protected $action;
/**
* @var EntityManagerInterface
*/
protected $entityManager;
/**
* constructor.
*
* @param string $action the stat to perform
*/
public function __construct(
ActivityRepository $activityRepository,
string $action = 'sum'
EntityManagerInterface $em,
$action = 'sum'
) {
$this->entityManager = $em;
$this->action = $action;
$this->activityRepository = $activityRepository;
}
public function buildForm(FormBuilderInterface $builder)
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
{
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_TABULAR];
return [\Chill\MainBundle\Export\FormatterInterface::TYPE_TABULAR];
}
public function getDescription()
@@ -66,12 +73,20 @@ class StatActivityDuration implements ExportInterface
public function getLabels($key, array $values, $data)
{
if ('export_stat_activity' !== $key) {
throw new LogicException(sprintf('The key %s is not used by this export', $key));
throw new LogicException("the key {$key} is not used by this export");
}
$header = self::SUM === $this->action ? 'Sum of activities duration' : false;
switch ($this->action) {
case self::SUM:
$header = 'Sum of activities duration';
}
return static fn (string $value) => '_header' === $value ? $header : $value;
return static function ($value) use ($header) {
return '_header' === $value ?
$header
:
$value;
};
}
public function getQueryKeys($data)
@@ -98,24 +113,23 @@ class StatActivityDuration implements ExportInterface
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;
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$qb = $this->entityManager->createQueryBuilder();
if (self::SUM === $this->action) {
$select = 'SUM(activity.durationTime) AS export_stat_activity';
}
return $qb->select($select)
$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 requiredRole()

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -15,9 +17,7 @@ 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;
@@ -25,7 +25,10 @@ use Symfony\Component\Translation\TranslatorInterface;
class ActivityDateFilter implements FilterInterface
{
protected TranslatorInterface $translator;
/**
* @var TranslatorInterface
*/
protected $translator;
public function __construct(TranslatorInterface $translator)
{
@@ -37,7 +40,7 @@ class ActivityDateFilter implements FilterInterface
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->between(
@@ -62,31 +65,23 @@ class ActivityDateFilter implements FilterInterface
return 'activity';
}
public function buildForm(FormBuilderInterface $builder)
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
{
$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_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->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 */
@@ -136,8 +131,7 @@ class ActivityDateFilter implements FilterInterface
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
],
];
], ];
}
public function getTitle()

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,12 +14,11 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
@@ -29,18 +30,28 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function array_key_exists;
use function count;
class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInterface
class ActivityReasonFilter implements
ExportElementValidatedInterface,
FilterInterface
{
protected ActivityReasonRepository $activityReasonRepository;
/**
* The repository for activity reasons.
*
* @var EntityRepository
*/
protected $reasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
public function __construct(
TranslatableStringHelper $helper,
ActivityReasonRepository $activityReasonRepository
EntityRepository $reasonRepository
) {
$this->translatableStringHelper = $helper;
$this->activityReasonRepository = $activityReasonRepository;
$this->reasonRepository = $reasonRepository;
}
public function addRole()
@@ -86,10 +97,17 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
public function buildForm(FormBuilderInterface $builder)
{
//create a local copy of translatableStringHelper
$helper = $this->translatableStringHelper;
$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()),
'class' => 'ChillActivityBundle:ActivityReason',
'choice_label' => static function (ActivityReason $reason) use ($helper) {
return $helper->localize($reason->getName());
},
'group_by' => static function (ActivityReason $reason) use ($helper) {
return $helper->localize($reason->getCategory()->getName());
},
'multiple' => true,
'expanded' => false,
]);
@@ -99,16 +117,14 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
{
// collect all the reasons'name used in this filter in one array
$reasonsNames = array_map(
fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"',
$this->activityReasonRepository->findBy(['id' => $data['reasons']->toArray()])
function (ActivityReason $r) {
return '"' . $this->translatableStringHelper->localize($r->getName()) . '"';
},
$this->reasonRepository->findBy(['id' => $data['reasons']->toArray()])
);
return [
'Filtered by reasons: only %list%',
[
'%list%' => implode(', ', $reasonsNames),
],
];
return ['Filtered by reasons: only %list%',
['%list%' => implode(', ', $reasonsNames)], ];
}
public function getTitle()
@@ -119,8 +135,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['reasons'] || count($data['reasons']) === 0) {
$context
->buildViolation('At least one reason must be chosen')
$context->buildViolation('At least one reason must be choosen')
->addViolation();
}
}
@@ -130,8 +145,10 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
*
* @param Join[] $joins
* @param mixed $alias
*
* @return bool
*/
private function checkJoinAlreadyDefined(array $joins, $alias): bool
private function checkJoinAlreadyDefined(array $joins, $alias)
{
foreach ($joins as $join) {
if ($join->getAlias() === $alias) {

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,11 +14,11 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
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 Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
@@ -27,18 +29,28 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInterface
class ActivityTypeFilter implements
ExportElementValidatedInterface,
FilterInterface
{
protected ActivityTypeRepository $activityTypeRepository;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
protected TranslatableStringHelperInterface $translatableStringHelper;
/**
* The repository for activity reasons.
*
* @var EntityRepository
*/
protected $typeRepository;
public function __construct(
TranslatableStringHelperInterface $translatableStringHelper,
ActivityTypeRepository $activityTypeRepository
TranslatableStringHelper $helper,
EntityRepository $typeRepository
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->activityTypeRepository = $activityTypeRepository;
$this->translatableStringHelper = $helper;
$this->typeRepository = $typeRepository;
}
public function addRole()
@@ -68,32 +80,31 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
public function buildForm(FormBuilderInterface $builder)
{
$builder->add(
'types',
EntityType::class,
[
'class' => ActivityType::class,
'choice_label' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getName()),
'multiple' => true,
'expanded' => false,
]
);
//create a local copy of translatableStringHelper
$helper = $this->translatableStringHelper;
$builder->add('types', EntityType::class, [
'class' => ActivityType::class,
'choice_label' => static function (ActivityType $type) use ($helper) {
return $helper->localize($type->getName());
},
'multiple' => true,
'expanded' => false,
]);
}
public function describeAction($data, $format = 'string')
{
// collect all the reasons'name used in this filter in one array
$reasonsNames = array_map(
fn (ActivityType $t): string => '"' . $this->translatableStringHelper->localize($t->getName()) . '"',
$this->activityTypeRepository->findBy(['id' => $data['types']->toArray()])
function (ActivityType $t) {
return '"' . $this->translatableStringHelper->localize($t->getName()) . '"';
},
$this->typeRepository->findBy(['id' => $data['types']->toArray()])
);
return [
'Filtered by activity type: only %list%',
[
'%list%' => implode(', ', $reasonsNames),
],
];
return ['Filtered by activity type: only %list%',
['%list%' => implode(', ', $reasonsNames)], ];
}
public function getTitle()
@@ -104,8 +115,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['types'] || count($data['types']) === 0) {
$context
->buildViolation('At least one type must be chosen')
$context->buildViolation('At least one type must be choosen')
->addViolation();
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,39 +14,46 @@ 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\EntityRepository;
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\FormInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface
class PersonHavingActivityBetweenDateFilter implements
ExportElementValidatedInterface,
FilterInterface
{
protected ActivityReasonRepository $activityReasonRepository;
/**
* @var EntityRepository
*/
protected $activityReasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
protected TranslatorInterface $translator;
/**
* @var TranslatorInterface
*/
protected $translator;
public function __construct(
TranslatableStringHelper $translatableStringHelper,
ActivityReasonRepository $activityReasonRepository,
EntityRepository $activityReasonRepository,
TranslatorInterface $translator
) {
$this->translatableStringHelper = $translatableStringHelper;
@@ -57,23 +66,23 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
public function alterQuery(\Doctrine\ORM\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');
// 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');
. ':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',
@@ -107,7 +116,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
return Declarations::PERSON_IMPLIED_IN;
}
public function buildForm(FormBuilderInterface $builder)
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
{
$builder->add('date_from', DateType::class, [
'label' => 'Implied in an activity after this date',
@@ -126,9 +135,15 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
]);
$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()),
'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());
},
'data' => $this->activityReasonRepository->findAll(),
'multiple' => true,
'expanded' => false,
@@ -136,7 +151,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var FormInterface $filterForm */
/** @var \Symfony\Component\Form\FormInterface $filterForm */
$filterForm = $event->getForm()->getParent();
$enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData();
@@ -184,15 +199,14 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
[
'%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']
)
),
],
];
'%reasons_name%' => implode(', ', array_map(
function (ActivityReason $r) {
return '"' . $this->translatableStringHelper->
localize($r->getName()) . '"';
},
$data['reasons']
)),
], ];
}
public function getTitle()
@@ -203,7 +217,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['reasons'] || count($data['reasons']) === 0) {
$context->buildViolation('At least one reason must be chosen')
$context->buildViolation('At least one reason must be choosen')
->addViolation();
}
}

View File

@@ -1,42 +0,0 @@
<?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;
class ActivityPresenceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TranslatableStringFormType::class)
->add('active', ChoiceType::class, [
'choices' => [
'Yes' => true,
'No' => false,
],
'expanded' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ActivityPresence::class,
]);
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,48 +1,35 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Form;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\MainBundle\Entity\Location;
use Chill\ActivityBundle\Form\Type\TranslatableActivityReason;
use Chill\ActivityBundle\Form\Type\TranslatableActivityType;
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\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\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;
@@ -53,43 +40,48 @@ use function in_array;
class ActivityType extends AbstractType
{
protected AuthorizationHelper $authorizationHelper;
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
protected ObjectManager $om;
/**
* @var ObjectManager
*/
protected $om;
protected SocialActionRender $socialActionRender;
protected $timeChoices;
protected SocialIssueRender $socialIssueRender;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
protected array $timeChoices;
protected TranslatableStringHelper $translatableStringHelper;
protected User $user;
/**
* the user running this form.
*
* @var User
*/
protected $user;
public function __construct(
TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper,
ObjectManager $om,
TranslatableStringHelper $translatableStringHelper,
array $timeChoices,
SocialIssueRender $socialIssueRender,
SocialActionRender $socialActionRender
array $timeChoices
) {
if (!$tokenStorage->getToken()->getUser() instanceof User) {
throw new RuntimeException('you should have a valid user');
}
$this->user = $tokenStorage->getToken()->getUser();
$this->authorizationHelper = $authorizationHelper;
$this->om = $om;
$this->translatableStringHelper = $translatableStringHelper;
$this->timeChoices = $timeChoices;
$this->socialIssueRender = $socialIssueRender;
$this->socialActionRender = $socialActionRender;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
public function buildForm(FormBuilderInterface $builder, array $options)
{
// handle times choices
$timeChoices = [];
@@ -104,296 +96,58 @@ class ActivityType extends AbstractType
'placeholder' => 'Choose the duration',
];
/** @var \Chill\ActivityBundle\Entity\ActivityType $activityType */
$activityType = $options['activityType'];
// TODO revoir la gestion des center au niveau du form des activité.
if ($options['center']) {
$builder->add('scope', ScopePickerType::class, [
$builder
->add('date', ChillDateType::class, [
'required' => true,
])
->add('durationTime', ChoiceType::class, $durationTimeOptions)
->add('attendee', ChoiceType::class, [
'expanded' => true,
'required' => false,
'choices' => [
'present' => true,
'not present' => false,
],
])
->add('user', UserPickerType::class, [
'center' => $options['center'],
'role' => $options['role'],
// TODO make required again once scope and rights are fixed
])
->add('scope', ScopePickerType::class, [
'center' => $options['center'],
'role' => $options['role'],
])
->add('reasons', TranslatableActivityReason::class, [
'multiple' => true,
'required' => false,
])
->add('type', TranslatableActivityType::class, [
'placeholder' => 'Choose a type',
'active_only' => true,
])
->add('comment', CommentType::class, [
'required' => false,
]);
}
/** @var ? \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingPeriod */
$accompanyingPeriod = null;
$builder->get('durationTime')
->addModelTransformer($durationTimeTransformer);
if ($options['accompanyingPeriod']) {
$accompanyingPeriod = $options['accompanyingPeriod'];
}
if ($activityType->isVisible('socialIssues') && $accompanyingPeriod) {
$builder->add('socialIssues', HiddenType::class);
$builder->get('socialIssues')
->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(
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')) {
$builder->add('date', ChillDateType::class, [
'label' => $activityType->getLabel('date'),
'required' => $activityType->isRequired('date'),
]);
}
if ($activityType->isVisible('durationTime')) {
$durationTimeOptions['label'] = $activityType->getLabel('durationTime');
$durationTimeOptions['required'] = $activityType->isRequired('durationTime');
$builder->add('durationTime', ChoiceType::class, $durationTimeOptions);
}
if ($activityType->isVisible('travelTime')) {
$durationTimeOptions['label'] = $activityType->getLabel('travelTime');
$durationTimeOptions['required'] = $activityType->isRequired('travelTime');
$builder->add('travelTime', ChoiceType::class, $durationTimeOptions);
}
if ($activityType->isVisible('attendee')) {
$builder->add('attendee', EntityType::class, [
'label' => $activityType->getLabel('attendee'),
'required' => $activityType->isRequired('attendee'),
'expanded' => true,
'class' => ActivityPresence::class,
'choice_label' => function (ActivityPresence $activityPresence) {
return $this->translatableStringHelper->localize($activityPresence->getName());
},
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('a')
->where('a.active = true');
},
]);
}
if ($activityType->isVisible('user') && $options['center']) {
$builder->add('user', UserPickerType::class, [
'label' => $activityType->getLabel('user'),
'required' => $activityType->isRequired('user'),
'center' => $options['center'],
'role' => $options['role'],
]);
}
if ($activityType->isVisible('reasons')) {
$builder->add('reasons', EntityType::class, [
'label' => $activityType->getLabel('reasons'),
'required' => $activityType->isRequired('reasons'),
'class' => ActivityReason::class,
'multiple' => true,
'choice_label' => function (ActivityReason $activityReason) {
return $this->translatableStringHelper->localize($activityReason->getName());
},
'attr' => ['class' => 'select2 '],
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('a')
->where('a.active = true');
},
]);
}
if ($activityType->isVisible('comment')) {
$builder->add('comment', CommentType::class, [
'label' => empty($activityType->getLabel('comment'))
? 'activity.comment' : $activityType->getLabel('comment'),
'required' => $activityType->isRequired('comment'),
]);
}
if ($activityType->isVisible('persons')) {
$builder->add('persons', HiddenType::class);
$builder->get('persons')
->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(
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')) {
$builder->add('documents', ChillCollectionType::class, [
'entry_type' => StoredObjectType::class,
'label' => $activityType->getLabel('documents'),
'required' => $activityType->isRequired('documents'),
'allow_add' => true,
'button_add_label' => 'activity.Insert a document',
'button_remove_label' => 'activity.Remove a document',
]);
}
if ($activityType->isVisible('users')) {
$builder->add('users', HiddenType::class);
$builder->get('users')
->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(
static function (?Location $location): string {
if (null === $location) {
return '';
}
return (string) $location->getId();
},
function (?string $id): ?Location {
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
}
));
}
if ($activityType->isVisible('emergency')) {
$builder->add('emergency', CheckboxType::class, [
'label' => $activityType->getLabel('emergency'),
'required' => $activityType->isRequired('emergency'),
]);
}
if ($activityType->isVisible('sentReceived')) {
$builder->add('sentReceived', ChoiceType::class, [
'label' => $activityType->getLabel('sentReceived'),
'required' => $activityType->isRequired('sentReceived'),
'choices' => [
'Sent' => Activity::SENTRECEIVED_SENT,
'Received' => Activity::SENTRECEIVED_RECEIVED,
],
]);
}
foreach (['durationTime', 'travelTime'] as $fieldName) {
if (!$activityType->isVisible($fieldName)) {
continue;
}
$builder->get($fieldName)
->addModelTransformer($durationTimeTransformer);
$builder->get($fieldName)
->addEventListener(FormEvents::PRE_SET_DATA, static function (FormEvent $formEvent) use (
$builder->get('durationTime')
->addEventListener(
FormEvents::PRE_SET_DATA,
static function (FormEvent $formEvent) use (
$timeChoices,
$builder,
$durationTimeTransformer,
$durationTimeOptions,
$fieldName
$durationTimeOptions
) {
// 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 DateTime $data */
$data = $formEvent->getData() === null ?
DateTime::createFromFormat('U', '300') :
$formEvent->getData();
DateTime::createFromFormat('U', 300) :
$formEvent->getData();
$seconds = $data->getTimezone()->getOffset($data);
$data->setTimeZone($timezoneUTC);
$data->add(new DateInterval('PT' . $seconds . 'S'));
@@ -403,35 +157,43 @@ class ActivityType extends AbstractType
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,
[
'choices' => $timeChoices,
'auto_initialize' => false,
]
));
$form = $builder->create(
'durationTime',
ChoiceType::class,
array_merge(
$durationTimeOptions,
[
'choices' => $timeChoices,
'auto_initialize' => false,
]
)
);
$form->addModelTransformer($durationTimeTransformer);
$formEvent->getForm()->getParent()->add($form->getForm());
}
});
}
}
);
}
public function configureOptions(OptionsResolver $resolver): void
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Activity::class,
'data_class' => 'Chill\ActivityBundle\Entity\Activity',
]);
$resolver
->setRequired(['center', 'role', 'activityType', 'accompanyingPeriod'])
->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']);
->setRequired(['center', 'role'])
->setAllowedTypes('center', 'Chill\MainBundle\Entity\Center')
->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role');
}
public function getBlockPrefix(): string
/**
* @return string
*/
public function getBlockPrefix()
{
return 'chill_activitybundle_activity';
}

View File

@@ -1,47 +0,0 @@
<?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;
class ActivityTypeCategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TranslatableStringFormType::class)
->add('active', ChoiceType::class, [
'choices' => [
'Yes' => true,
'No' => false,
],
'expanded' => true,
])
->add('ordering', NumberType::class, [
'required' => true,
'scale' => 5,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ActivityTypeCategory::class,
]);
}
}

View File

@@ -1,37 +1,26 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
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;
class ActivityTypeType extends AbstractType
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
@@ -42,39 +31,24 @@ class ActivityTypeType extends AbstractType
'No' => false,
],
'expanded' => true,
])
->add('category', EntityType::class, [
'class' => ActivityTypeCategory::class,
'choice_label' => function (ActivityTypeCategory $activityTypeCategory) {
return $this->translatableStringHelper->localize($activityTypeCategory->getName());
},
])
->add('ordering', NumberType::class, [
'required' => true,
'scale' => 5,
]);
$fields = [
'persons', 'user', 'date', 'location', 'persons',
'thirdParties', 'durationTime', 'travelTime', 'attendee',
'reasons', 'comment', 'sentReceived', 'documents',
'emergency', 'socialIssues', 'socialActions', 'users',
];
foreach ($fields as $field) {
$builder
->add($field . 'Visible', ActivityFieldPresence::class)
->add($field . 'Label', TextType::class, [
'required' => false,
'empty_data' => '',
]);
}
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => \Chill\ActivityBundle\Entity\ActivityType::class,
'data_class' => 'Chill\ActivityBundle\Entity\ActivityType',
]);
}
/**
* @return string
*/
public function getBlockPrefix()
{
return 'chill_activitybundle_activitytype';
}
}

View File

@@ -1,38 +0,0 @@
<?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;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ActivityFieldPresence extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'choices' => [
'Invisible' => ActivityType::FIELD_INVISIBLE,
'Optional' => ActivityType::FIELD_OPTIONAL,
'Required' => ActivityType::FIELD_REQUIRED,
],
]
);
}
public function getParent()
{
return ChoiceType::class;
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -12,37 +14,40 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Form\Type;
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 Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
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.
*/
class TranslatableActivityType extends AbstractType
{
protected ActivityTypeRepository $activityTypeRepository;
protected $activityTypeRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
public function __construct(
TranslatableStringHelperInterface $helper,
ActivityTypeRepository $activityTypeRepository
TranslatableStringHelper $helper,
EntityRepository $activityTypeRepository
) {
$this->translatableStringHelper = $helper;
$this->activityTypeRepository = $activityTypeRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options)
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options)
{
/** @var QueryBuilder $qb */
/** @var \Doctrine\ORM\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);
$qb->setParameter('active', true, \Doctrine\DBAL\Types\Type::BOOLEAN);
}
}
@@ -50,7 +55,7 @@ class TranslatableActivityType extends AbstractType
{
$resolver->setDefaults(
[
'class' => ActivityType::class,
'class' => 'ChillActivityBundle:ActivityType',
'active_only' => true,
'query_builder' => $this->activityTypeRepository
->createQueryBuilder('at'),

View File

@@ -1,56 +0,0 @@
<?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\PersonBundle\Entity\AccompanyingPeriod;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{
protected Security $security;
protected TranslatorInterface $translator;
public function __construct(
Security $security,
TranslatorInterface $translator
) {
$this->security = $security;
$this->translator = $translator;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$period = $parameters['accompanyingCourse'];
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

@@ -1,58 +0,0 @@
<?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

@@ -0,0 +1,88 @@
<?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.
*
* @see https://www.champs-libres.coop/
*/
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 Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Translation\TranslatorInterface;
class MenuBuilder implements LocalMenuBuilderInterface
{
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/**
* @var TokenStorageInterface
*/
protected $tokenStorage;
/**
* @var TranslatorInterface
*/
protected $translator;
public function __construct(
TokenStorageInterface $tokenStorage,
TranslatorInterface $translator,
AuthorizationHelper $authorizationHelper
) {
$this->tokenStorage = $tokenStorage;
$this->translator = $translator;
$this->authorizationHelper = $authorizationHelper;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
/** @var \Chill\PersonBundle\Entity\Person $person */
$person = $parameters['person'];
$user = $this->tokenStorage->getToken()->getUser();
$roleSee = new Role(ActivityVoter::SEE);
$roleAdd = new Role(ActivityVoter::CREATE);
if ($this->authorizationHelper->userHasAccess($user, $person, $roleSee)) {
$menu->addChild($this->translator->trans('Activity list'), [
'route' => 'chill_activity_activity_list',
'routeParameters' => [
'person_id' => $person->getId(),
],
])
->setExtras([
'order' => 201,
]);
}
if ($this->authorizationHelper->userHasAccess($user, $person, $roleAdd)) {
$menu->addChild($this->translator->trans('Add a new activity'), [
'route' => 'chill_activity_activity_new',
'routeParameters' => [
'person_id' => $person->getId(),
],
])
->setExtras([
'order' => 200,
]);
}
}
public static function getMenuIds(): array
{
return ['person'];
}
}

View File

@@ -1,10 +1,12 @@
<?php
/**
* Chill is a software for social workers
* 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.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
@@ -52,6 +54,17 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
)
->setExtra('order', 201);
}
if ($this->authorizationChecker->isGranted(ActivityVoter::CREATE, $person)) {
$menu->addChild(
$this->translator->trans('Add a new activity'),
[
'route' => 'chill_activity_activity_new',
'routeParameters' => ['person_id' => $person->getId()],
]
)
->setExtra('order', 200);
}
}
public static function getMenuIds(): array

View File

@@ -1,45 +0,0 @@
<?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\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
final class ActivityNotificationHandler implements NotificationHandlerInterface
{
private ActivityRepository $activityRepository;
public function __construct(ActivityRepository $activityRepository)
{
$this->activityRepository = $activityRepository;
}
public function getTemplate(Notification $notification, array $options = []): string
{
return '@ChillActivity/Activity/showInNotification.html.twig';
}
public function getTemplateData(Notification $notification, array $options = []): array
{
return [
'notification' => $notification,
'activity' => $this->activityRepository->find($notification->getRelatedEntityId()),
];
}
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() === Activity::class;
}
}

View File

@@ -1,207 +0,0 @@
<?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\Activity;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Scope;
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 Symfony\Component\Security\Core\Security;
use function count;
use function in_array;
final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
{
private AuthorizationHelper $authorizationHelper;
private CenterResolverDispatcherInterface $centerResolverDispatcher;
private EntityManagerInterface $em;
private ActivityRepository $repository;
private Security $security;
private TokenStorageInterface $tokenStorage;
public function __construct(
AuthorizationHelper $authorizationHelper,
CenterResolverDispatcherInterface $centerResolverDispatcher,
TokenStorageInterface $tokenStorage,
ActivityRepository $repository,
EntityManagerInterface $em,
Security $security
) {
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->tokenStorage = $tokenStorage;
$this->repository = $repository;
$this->em = $em;
$this->security = $security;
}
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'];
}
$scopes = $this->authorizationHelper
->getReachableCircles($user, $role, $center);
return $this->em->getRepository(Activity::class)
->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);
$from = $this->getFromClauseCenter($args);
[$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,
];
}
private function getFromClauseCenter(array $args): string
{
$metadataActivity = $this->em->getClassMetadata(Activity::class);
$metadataPerson = $this->em->getClassMetadata(Person::class);
$associationMapping = $metadataActivity->getAssociationMapping('person');
return $metadataActivity->getTableName() . ' JOIN '
. $metadataPerson->getTableName() . ' ON '
. $metadataPerson->getTableName() . '.' .
$associationMapping['joinColumns'][0]['referencedColumnName']
. ' = '
. $associationMapping['joinColumns'][0]['name'];
}
private function getWhereClause(string $context, array $args): array
{
$where = '';
$parameters = [];
$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'];
$personToCenter = $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'];
// acls:
$role = new Role(ActivityVoter::SEE);
$reachableCenters = $this->authorizationHelper->getReachableCenters(
$this->tokenStorage->getToken()->getUser(),
$role
);
if (count($reachableCenters) === 0) {
// insert a dummy condition
return 'FALSE = TRUE';
}
if ('person' === $context) {
// we start with activities having the person_id linked to person
$where .= sprintf('%s = ? AND ', $activityToPerson);
$parameters[] = $person->getId();
}
// 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'], 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(
static function (Scope $scope) {
return $scope->getId();
},
$reachableScopes
);
// if not the first center
if (0 < $centersI) {
$where .= ') OR (';
}
// condition for the center
$where .= sprintf(' %s.%s = ? ', $metadataPerson->getTableName(), $personToCenter);
$parameters[] = $center->getId();
// begin loop for scopes
$where .= ' AND (';
$scopesI = 0; //like scope#i
foreach ($reachablesScopesId as $scopeId) {
if (0 < $scopesI) {
$where .= ' OR ';
}
$where .= sprintf(' %s.%s = ? ', $metadataActivity->getTableName(), $activityToScope);
$parameters[] = $scopeId;
++$scopesI;
}
// close loop for scopes
$where .= ') ';
++$centersI;
}
// close loop for centers
$where .= ')';
return [$where, $parameters];
}
}

View File

@@ -1,28 +0,0 @@
<?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;
use Chill\PersonBundle\Entity\Person;
interface ActivityACLAwareRepositoryInterface
{
/**
* @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

@@ -1,30 +0,0 @@
<?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

@@ -1,30 +0,0 @@
<?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,100 +0,0 @@
<?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\Activity;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @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
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Activity::class);
}
/**
* @deprecated use @see{ActivityACLAwareRepositoryInterface::findByAccompanyingPeriod}
*
* @return 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 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');
$qb
->where($qb->expr()->in('a.scope', ':scopes'))
->setParameter('scopes', $scopes)
->andWhere(
$qb->expr()->orX(
$qb->expr()->eq('a.person', ':person'),
':person MEMBER OF a.persons'
)
)
->setParameter('person', $person);
foreach ($orderBy as $k => $dir) {
$qb->addOrderBy('a.' . $k, $dir);
}
$qb->setMaxResults($limit)->setFirstResult($offset);
return $qb->getQuery()->getResult();
}
}

View File

@@ -1,30 +0,0 @@
<?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

@@ -1,30 +0,0 @@
<?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

@@ -0,0 +1,10 @@
@import '~ChillMainSass/custom/config/colors';
@import '~ChillMainSass/custom/mixins/entity';
.chill-entity.chill-entity__activity-reason {
@include entity($chill-pink, white);
}
.activity {
color: $chill-green;
}

View File

@@ -1,87 +0,0 @@
// Access to Bootstrap variables and mixins
@import '~ChillMainAssets/module/bootstrap/shared';
//// ACTIVITY CREATION
// first step: select type page
div.new-activity-select-type {
div.activity-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
div.bloc {
width: 200px;
align-self: flex-end;
height: 140px;
display: flex;
justify-content: center;
align-items: center;
}
}
}
//// ACTIVITY LIST PAGE
// 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 {
ul.list-content {
font-size: 70%;
list-style-type: none;
padding-left: 0;
margin: 0;
li {
margin-bottom: 0.2em;
// exception: change bg color for action badges above dashboard
.bg-light {
background-color: $chill-light-gray !important;
}
}
}
}
//// ACTIVITY SHOW AND FORM PAGES
// Exceptions for flex-bloc in concerned-groups
div.flex-bloc.concerned-groups {
margin-top: 1em;
div.item-bloc {
flex-grow: 0; flex-shrink: 0; flex-basis: 25%; //4 blocs
ul.list-content {
list-style-type: none;
padding-left: 0;
li {
margin-bottom: 0.2em;
a {
color: $white;
cursor: pointer;
&:hover {
color: #ffffffab;
}
}
}
}
}
&.person div.item-bloc {
flex-basis: 33%; //3 blocs
}
}
/// CHILL ENTITY RENDER BOX
.chill-entity {
/// ACTIVITY-REASON
&.entity-activity-reason {
margin-right: 0.3em;
font-size: 120%;
}
}

View File

@@ -1 +0,0 @@
require('./chillactivity.scss');

View File

@@ -0,0 +1 @@
require('./activity/activity.scss');

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