Compare commits

...

154 Commits

Author SHA1 Message Date
94d6b5eff8 Merge branch 'upgrade-sf5' of gitlab.com:Chill-Projet/chill-bundles into upgrade-sf5 2024-08-26 18:15:13 +02:00
d87f380f16 Update chill bundles to version 3.0.0 2024-08-26 18:14:48 +02:00
58bf722fae remove the "v" prefix for file, to create release with tags starting with "v". 2024-08-26 14:21:43 +00:00
50fb79ebbf Remove enforcement of https scheme within controller method 2024-08-26 15:42:28 +02:00
58912f1d98 Add changie for fixes to CollectionType js 2024-08-22 14:24:58 +02:00
9604ba5f4b Merge branch 'person_resource_abstract_class' into 'upgrade-sf5'
Create abstract class for person resource and corrections made to CollectionType js

See merge request Chill-Projet/chill-bundles!716
2024-08-22 12:22:52 +00:00
b689a51a48 Changie added for fix connection to azure 2024-08-21 16:56:05 +02:00
8c0d2f58ba Add missing [ ] for schemes option 2024-08-21 16:53:26 +02:00
212230448b Add changie for fix of delete action accompanying periods 2024-08-21 15:14:48 +02:00
2bfb8fe387 Fix delete action for accompanying periods, missed in previous correction 2024-08-21 15:13:18 +02:00
6362b98a00 Specifiy https scheme for the connectAzureCheck controller method 2024-08-21 14:20:37 +02:00
6e2a08cae8 Resolve multiple entries not being saved in collectiontype 2024-08-21 10:22:13 +02:00
305105faae Fix CalendarContextTest after faulty php cs fix 2024-08-14 15:27:00 +02:00
85811cc6ae Run php-cs-fixer and rector 2024-08-14 14:45:59 +02:00
7eee995627 Merge branch 'upgrade-sf5' into person_resource_abstract_class 2024-08-14 14:26:24 +02:00
c0c448fb39 Remove dump from code 2024-08-14 14:26:04 +02:00
6445342136 Fix remove button not showing in CollectionType forms with allow_delete option 2024-08-14 14:23:03 +02:00
d52e54fd2a Make loadDynamicPicker available within windows where dynamicPicker tags are added 2024-08-14 13:38:58 +02:00
547a9d1369 Downgrade bundles to v2.23.0 fix in v2.23.1 undone 2024-08-08 11:01:21 +02:00
288a02f5b7 Undo wrong fix in export filter parcours having activity between dates
Problem reported by users, wrong analysis and fix made. Behavior was in fact
correct.
2024-08-05 15:56:40 +02:00
2f9884072c Add missing use statement for Groups annotation 2024-07-31 15:12:36 +02:00
ee45ff61a6 Reorganize person resource code to create an abstract class 2024-07-31 14:55:35 +02:00
5dfd8daf3a Remove dump causing export to fail 2024-07-30 14:58:48 +02:00
a46e987f81 Upgrade version of chill-bundles to v2.23.1 2024-07-25 16:56:43 +02:00
81220b5b22 Correct phpstan error: ParsingException is never thrown
The typing of the exception had to be changed, because \Datetime throws an \Exception
instead of a ParsingException.
2024-07-25 11:30:33 +02:00
5b0019cde7 Fix query in filter: period having activity between dates filter
Adding statement to ensure that subquery only considers records that are identical to the
ones being processed in the main query.
2024-07-25 11:23:36 +02:00
b42473b01d Upgrade bundles version with new features and fix 2024-07-23 16:54:08 +02:00
be19d09bad Fix typing error in ListActivityHelper: string expected got int 2024-07-23 16:43:54 +02:00
c82991674e Rector changes: repositories become final readonly classes 2024-07-18 09:49:05 +02:00
3fc3f32c5f Pipeline fixes 2024-07-17 16:21:19 +02:00
20af766cdf Fix the query to return the count of events
Left join was causing events to counted multiple times (once per participation). Using DISTINCT fixes this.
2024-07-17 15:38:55 +02:00
681f637d13 Merge branch 'add-module-emploi' into upgrade-sf5 2024-07-17 14:21:10 +02:00
fb8a6d960e Remove duplicate edit method in events controller 2024-07-17 14:20:15 +02:00
a2310a662f Fix delete request for event status controller 2024-07-17 14:17:19 +02:00
dd7d126bec Fix edit request for event bundle 2024-07-17 14:15:48 +02:00
29f6a43288 Fix translation in event bundle using new pluralization syntax 2024-07-17 13:35:47 +02:00
74be6460d4 Fix delete request for all entities 2024-07-17 13:33:05 +02:00
c8e87ced35 Remove duplicate edit method in events controller 2024-07-17 13:29:14 +02:00
b8002d56ec merge latest changes from upgrade-sf5 branch 2024-07-17 13:17:59 +02:00
a80b36bb31 Fix EntityPersonCRUDController.php add 'person' key 2024-07-17 13:15:22 +02:00
116fe35ad2 Fix delete request for event status controller 2024-07-17 13:11:09 +02:00
5b95336bac Fix event and participation voters 2024-07-17 13:10:51 +02:00
f9d5ba7778 Fix edit request for event bundle 2024-07-17 13:10:34 +02:00
f76379551c Change namespace event voters for consistency 2024-07-17 13:08:48 +02:00
15094d5a91 Fix delete request for all entities 2024-07-17 13:05:14 +02:00
1079c7e394 Fix translation in event bundle using new pluralization syntax 2024-07-16 07:07:08 +02:00
bc2dfd159c merge upgrade-sf5 branch 2024-07-15 15:16:27 +02:00
b100792a34 Fix request to France Travail Api to accomodate for new request limit
A new limit was set to allow a maximum amount of request in a certain timeframe. We need to wait 1 sec to make next request.
2024-07-15 15:11:34 +02:00
00ceee1fd5 Remove "document" from document name 2024-07-15 15:09:45 +02:00
724b98e8c5 Improve naming of downloaded documents in job bundle
When downloaded the title of the document was\ always set to 'Document', which gave little\ indication as to what the document was about.\
Now documents are titled with the name of the\ person and the type of document (CV, permis,...)
2024-07-04 13:28:54 +02:00
ead1abb825 fix code style for symfony 5 2024-07-03 13:29:22 +02:00
54d045f261 Merge remote-tracking branch 'origin/master' into upgrade-sf5 2024-07-03 13:19:41 +02:00
702a5a27d2 Update version of bundles to 2.22.2 2024-07-03 12:22:53 +02:00
nobohan
41dd4d89f7 Revert "#271 Account for acp closing date inn action filters (export)"
This reverts commit 3a7ed7ef8f.
2024-07-02 16:24:45 +02:00
nobohan
3a7ed7ef8f #271 Account for acp closing date inn action filters (export) 2024-07-02 16:22:07 +02:00
2c99ea17d4 Update new version of chill + more obvious generate jwt keypair command 2024-07-01 09:55:38 +00:00
18df08e8c3 Do not require scope for event participation stats 2024-07-01 11:14:02 +02:00
db3961275b git release 2.22.1 2024-07-01 09:53:41 +02:00
cd488d7576 Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2024-07-01 09:19:29 +02:00
436661d952 Remove debug word from code 2024-07-01 09:19:22 +02:00
c3b8d42047 Merge branch 'import_lux_addresses_command' into 'master'
DX import Luxembourg address command

See merge request Chill-Projet/chill-bundles!704
2024-06-28 09:07:32 +00:00
nobohan
9c28df25a1 DX: Improve Lux adress import command + register in config 2024-06-28 10:45:58 +02:00
d88b5a0098 Remove dump 2024-06-28 10:38:54 +02:00
nobohan
c5a24e8ac5 DX: Improve Lux address import command - WIP 2024-06-28 09:27:45 +02:00
nobohan
d9c50cffb7 DX import Luxembourg address command - phpstan 2024-06-27 10:34:41 +02:00
nobohan
25ccb16308 DX import Luxembourg address command - csfixer 2024-06-27 10:17:08 +02:00
nobohan
ba25c181f5 DX import Luxembourg address command 2024-06-27 10:07:24 +02:00
e38d47ec5e fix pipeline rector and cs 2024-06-26 09:38:13 +02:00
36f2275a56 Delete overriding of generateTemplateParameters method
This method was requiring a person_id to be set, which was\
not the case here so it threw an error. Using the method\
already available in CRUDController works fine, seems to be\
no need to override it.
2024-06-26 09:04:57 +02:00
9a34064b23 Implement chill_document_button_group to allow download of document
At first another download button was used, but not working.\ Elsewhere in Chill the chill_document_button_group seems to\
be used so more coherent to use it here too.
2024-06-26 09:04:41 +02:00
4c3bfc90b5 Import debug-bundle instead of var-dumper 2024-06-25 15:59:56 +00:00
1812e84c92 Merge branch 'testing-2024-03' into add-module-emploi 2024-06-25 15:54:54 +02:00
dfa7de4f38 merge upgrade-sf5 into branch for latest fixes 2024-06-25 15:54:22 +02:00
145419a76b CHANGELOG entry added for exports in event bundle 2024-06-25 13:29:24 +02:00
b1ba5cc608 Merge branch '216-exports-event-bundle' into 'master'
Add eventBundle exports

Closes #216

See merge request Chill-Projet/chill-bundles!688
2024-06-25 11:24:57 +00:00
87a6757e5e Add eventBundle exports 2024-06-25 11:24:57 +00:00
bd41308bbd remove deps to gelf-graylog, which is a dependency of the app 2024-06-24 15:11:00 +02:00
f8fa96d836 Fix download report URL in ChillMainBundle export
An extra "?" was erroneously appended to the download report URL in ChillMainBundle's export feature. This update removes the extraneous character to ensure the function works as expected with the correct URL format.
2024-06-24 14:23:47 +02:00
635b1ee537 merge upgrade-sf5 branch to have latest fixes 2024-06-06 13:01:12 +02:00
3fd6e52e9d remove unnecessary constraints from 3party properties acronym and nameCompany 2024-05-29 11:37:42 +02:00
f60a595ab6 Update vue toast version and implementation 2024-05-29 11:37:04 +02:00
436ef33dbc final fix for appellation selector: define metier when appellation already exists 2024-05-23 14:05:14 +02:00
405aad7333 fix rome appellation selector and admin 2024-05-23 13:47:33 +02:00
a5c2576124 Fix the appellation selection for projet professional 2024-05-23 12:46:12 +02:00
33a6f9996e remove dumps 2024-05-22 21:02:17 +02:00
b96cbc5594 rector fixes 2024-05-22 17:13:27 +02:00
854d72fa42 php cs fixes 2024-05-22 17:12:49 +02:00
cd6fd091dc resolve merge conflicts 2024-05-22 16:57:28 +02:00
cb5ade3d14 add changie for module emploi 2024-05-22 16:49:45 +02:00
dddb6d66bc php cs fixes after merge 2024-05-22 16:44:02 +02:00
d34f9450b8 Merge branch 'add-module-emploi' of gitlab.com:Chill-Projet/chill-bundles into add-module-emploi 2024-05-22 16:43:02 +02:00
9ce1788a14 phpstan en rector fixes 2024-05-22 16:42:47 +02:00
2895638f3b php cs fixes 2024-05-22 15:26:23 +02:00
2708bafb1f Export for list person with cs_person columns fixed 2024-05-22 15:24:39 +02:00
7d309136b1 minor last fixes for immersion and remove of dumps 2024-05-22 08:53:56 +02:00
82d3ec4d6f Merge branch 'add-module-emploi' of gitlab.com:Chill-Projet/chill-bundles into add-module-emploi 2024-05-22 08:40:46 +02:00
cad2dea148 Wip: add jsonb fields to export 2024-05-16 09:26:40 +02:00
bff14aa700 minor last fixes for immersion and remove of dumps 2024-05-15 16:02:14 +02:00
66570cd430 merge add-module-emploi into testing 2024-05-15 15:19:24 +02:00
53df2ec9ba Merge branch 'master' into testing-2024-03 2024-05-15 15:17:49 +02:00
068503a830 Merge branch 'master' into add-module-emploi 2024-05-15 14:36:36 +02:00
c07a728f1d Wip: add jsonb fields to export 2024-05-15 14:35:51 +02:00
b7e61c6747 merge master into module emploi branch 2024-05-15 14:26:51 +02:00
97846a5877 add basic fields csperson to list person export 2024-05-14 11:01:45 +02:00
4ed9d3d8e2 Fix API call 2024-05-14 07:49:48 +02:00
d82d534a4c Try to fix API: adjust to new urls, but still receiving error code 400 2024-05-07 11:15:10 +02:00
684f28291a reinstate exports 2024-05-07 11:14:39 +02:00
526882a5b6 phpstan, rector and cs fixes 2024-04-29 15:39:05 +02:00
422b6b99eb Change translation for the group of voter rights 2024-04-29 15:28:13 +02:00
02b150b0a5 fix delete of reports in crud config + template 2024-04-29 15:25:15 +02:00
20c27c100c Name change from CSConnecte to Job 2024-04-29 15:20:23 +02:00
12a22bcc13 template + form + property fixes for emploi reports 2024-04-29 14:13:37 +02:00
cba8a342d5 more template fixes 2024-04-24 18:02:06 +02:00
6f55ba15d6 Split crud controllers for each report entity 2024-04-24 17:40:31 +02:00
454ab73303 WIP fix emploi reports 2024-04-24 17:11:26 +02:00
800942bc92 Add missing columns to report tables 2024-04-24 17:10:30 +02:00
c4e7683e48 fix templates for personal situation 2024-04-24 15:02:24 +02:00
28c986fddf controller with crud logic + templates fixed for dispositif 2024-04-24 14:52:19 +02:00
adca4f0d6a php cs fixes 2024-04-24 12:28:55 +02:00
7b25c8e390 New migration to take care of everything needed for ChillJobBundle 2024-04-24 12:27:49 +02:00
0b5be0419b Move old migrations to directory 'old' just in case 2024-04-24 12:27:23 +02:00
650e85c481 Fix crudconfig 2024-04-24 11:44:14 +02:00
56d5d08ed3 Run rector on ListCSPerson file 2024-04-24 11:34:06 +02:00
d3390ca334 Phpstan error for unused parameter fixed 2024-04-24 10:49:19 +02:00
511c0af5fa Last php cs fix 2024-04-24 10:40:09 +02:00
4c354c47c9 Fix construct method for ListCSPerson 2024-04-24 10:39:46 +02:00
d8b6cef7b4 rector fixes 2024-04-24 10:18:07 +02:00
e312929d86 php cs fixes 2024-04-24 10:16:54 +02:00
20b38af812 Create phpstan baseline for level 5 taking into account new bundles 2024-04-24 10:16:16 +02:00
2f07be0843 Revert "adjust phpstan baselines"
This reverts commit a71573136a.
2024-04-24 10:11:02 +02:00
11c069a2ff Revert "phpstan baseline 5 updated"
This reverts commit deaab80270.
2024-04-24 10:10:55 +02:00
3929602f59 Revert "php style fixes"
This reverts commit 38fcccfd83.
2024-04-24 10:09:25 +02:00
38fcccfd83 php style fixes 2024-04-23 21:22:29 +02:00
deaab80270 phpstan baseline 5 updated 2024-04-23 21:22:19 +02:00
a71573136a adjust phpstan baselines 2024-04-23 21:12:57 +02:00
b1082f6a55 fix phpstan errors level 3 2024-04-23 21:03:42 +02:00
dcc285e976 fix phpstan errors level 2 2024-04-23 20:52:22 +02:00
ed3e0f889e Rector changes + namespace changes 2024-04-23 17:43:23 +02:00
63fe8070c4 Rector passed again on JobBundle entities 2024-04-19 11:30:47 +02:00
8e3322f578 rector rules for upgrade to php 8.2 and symfony 5.4 applied + php cs fixer 2024-04-19 10:56:49 +02:00
00756a3bde Move migrations directory to src 2024-04-19 10:33:40 +02:00
2c68224e9c Add jobBundle and FranceTravailApiBundle 2024-04-19 10:21:17 +02:00
166d7fe0b0 Merge branch 'master' into testing-2024-03 2024-03-26 22:14:51 +01:00
ab9d5439c1 Release 2.18.1 2024-03-26 22:08:30 +01:00
0737838dd6 Fix layout in admin document generation
A layout issue in the admin document generation has been fixed, particularly in the ChillDocGeneratorBundle. Unnecessary elements such as table headers and multiple entity data rows in DocGeneratorTemplate have been removed, simplifying the view page and improving its performance.
2024-03-26 22:08:01 +01:00
88bac5b5d8 Merge remote-tracking branch 'origin/145-permettre-de-visualiser-les-documents-dans-libreoffice-en-utilisant-webdav' into testing-2024-03 2024-03-26 21:34:15 +01:00
cf1df462dc optional parameter after the required one 2024-01-15 21:18:51 +01:00
a0328b9d68 Apply new CS rules on the webdav feature 2024-01-15 20:38:03 +01:00
813a80d6f9 Dav: add UI to edit document 2024-01-15 20:22:14 +01:00
ab95bb157e Dav: add some documentation on classes 2024-01-15 20:19:03 +01:00
18fd1dbc4a Dav: Introduce access control inside de dav controller 2024-01-15 20:19:03 +01:00
a35f7656cb Dav: refactor WebdavController 2024-01-15 20:19:03 +01:00
ff05f9f48a Dav: implements JWT extraction from the URL, and add the access_token in dav urls 2024-01-15 20:19:02 +01:00
482c494034 Webdav: fully implements the controller and response
The controller is tested from real request scraped from apache mod_dav implementation. The requests were scraped using a wireshark-like tool. Those requests have been adapted to suit to our xml.
2024-01-15 20:19:02 +01:00
271 changed files with 14649 additions and 711 deletions

View File

@@ -1,5 +0,0 @@
kind: Feature
body: '[DX] move async-upload-bundle features into chill-bundles'
time: 2023-12-12T15:48:41.954970271+01:00
custom:
Issue: "221"

View File

@@ -1,6 +0,0 @@
kind: Feature
body: |
Upgrade import of address list to the last version of compiled addresses of belgian-best-address
time: 2024-05-30T16:00:03.440767606+02:00
custom:
Issue: ""

View File

@@ -1,6 +0,0 @@
kind: Feature
body: |
Upgrade CKEditor and refactor configuration with use of typescript
time: 2024-05-31T19:02:42.776662753+02:00
custom:
Issue: ""

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Fix resolving of centers for an household, which will fix in turn the access
control
time: 2024-04-10T10:37:36.462484988+02:00
custom:
Issue: ""

6
.changes/v2.22.0.md Normal file
View File

@@ -0,0 +1,6 @@
## v2.22.0 - 2024-06-25
### Feature
* ([#216](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/216)) [event bundle] exports added for the event module
### Traduction francophone
* Exports sont ajoutés pour la module événement.

5
.changes/v2.22.1.md Normal file
View File

@@ -0,0 +1,5 @@
## v2.22.1 - 2024-07-01
### Fixed
* Remove debug word
### DX
* Add a command for reading official address DB from Luxembourg and update chill addresses

3
.changes/v2.22.2.md Normal file
View File

@@ -0,0 +1,3 @@
## v2.22.2 - 2024-07-03
### Fixed
* Remove scope required for event participation stats

11
.changes/v2.23.0.md Normal file
View File

@@ -0,0 +1,11 @@
## v2.23.0 - 2024-07-23
### Feature
* ([#221](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/221)) [DX] move async-upload-bundle features into chill-bundles
* Add job bundle (module emploi)
* Upgrade import of address list to the last version of compiled addresses of belgian-best-address
* Upgrade CKEditor and refactor configuration with use of typescript
### Fixed
* Fix resolving of centers for an household, which will fix in turn the access control
* Resolved type hinting error in activity list export

5
.changes/v3.0.0.md Normal file
View File

@@ -0,0 +1,5 @@
## v3.0.0 - 2024-08-26
### Fixed
* Fix delete action for accompanying periods in draft state
* Fix connection to azure when making an calendar event in chill
* CollectionType js fixes for remove button and adding multiple entries

View File

@@ -138,4 +138,4 @@ release:
- echo "running release_job"
release:
tag_name: '$CI_COMMIT_TAG'
description: "./.changes/v$CI_COMMIT_TAG.md"
description: "./.changes/$CI_COMMIT_TAG.md"

View File

@@ -6,6 +6,41 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
## v3.0.0 - 2024-08-26
### Fixed
* Fix delete action for accompanying periods in draft state
* Fix connection to azure when making an calendar event in chill
* CollectionType js fixes for remove button and adding multiple entries
## v2.23.0 - 2024-07-23
### Feature
* ([#221](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/221)) [DX] move async-upload-bundle features into chill-bundles
* Add job bundle (module emploi)
* Upgrade import of address list to the last version of compiled addresses of belgian-best-address
* Upgrade CKEditor and refactor configuration with use of typescript
### Fixed
* Fix resolving of centers for an household, which will fix in turn the access control
* Resolved type hinting error in activity list export
## v2.22.2 - 2024-07-03
### Fixed
* Remove scope required for event participation stats
## v2.22.1 - 2024-07-01
### Fixed
* Remove debug word
### DX
* Add a command for reading official address DB from Luxembourg and update chill addresses
## v2.22.0 - 2024-06-25
### Feature
* ([#216](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/216)) [event bundle] exports added for the event module
### Traduction francophone
* Exports sont ajoutés pour la module événement.
## v2.21.0 - 2024-06-18
### Feature
* Add flash menu buttons in search results, to open directly a new calendar, or a new activity in an accompanying period

View File

@@ -19,7 +19,6 @@
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.13.0",
"erusev/parsedown": "^1.7",
"graylog2/gelf-php": "^1.5",
"knplabs/knp-menu-bundle": "^3.0",
"knplabs/knp-time-bundle": "^1.12",
"knpuniversity/oauth2-client-bundle": "^2.10",
@@ -115,6 +114,8 @@
"Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle",
"Chill\\DocStoreBundle\\": "src/Bundle/ChillDocStoreBundle",
"Chill\\EventBundle\\": "src/Bundle/ChillEventBundle",
"Chill\\FranceTravailApiBundle\\": "src/Bundle/ChillFranceTravailApiBundle/src",
"Chill\\JobBundle\\": "src/Bundle/ChillJobBundle/src",
"Chill\\MainBundle\\": "src/Bundle/ChillMainBundle",
"Chill\\PersonBundle\\": "src/Bundle/ChillPersonBundle",
"Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle",

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
class BirthdateFilter implements ExportElementValidatedInterface, FilterInterface
{
// add specific role for this filter
public function addRole()
public function addRole(): ?string
{
// we do not need any new role for this filter, so we return null
return null;

View File

@@ -56,7 +56,7 @@ We strongly encourage you to initialize a git repository at this step, to track
cat <<< "$(jq '.extra.symfony += {"endpoint": ["flex://defaults", "https://gitlab.com/api/v4/projects/57371968/repository/files/index.json/raw?ref=main"]}' composer.json)" > composer.json
# install chill and some dependencies
# TODO fix the suffix "alpha1" and replace by ^3.0.0 when version 3.0.0 will be released
symfony composer require chill-project/chill-bundles v3.0.0-alpha1 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev
symfony composer require chill-project/chill-bundles v3.0.0-RC3 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev
We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database.
You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually).
@@ -110,15 +110,14 @@ you can either:
.. code-block:: env
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
# note: if you copy-paste the line above, the password will be "admin".
- add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env,
not the password in clear text).
- set up the jwt authentication bundle
Some environment variables are available for the JWT authentication bundle in the :code:`.env` file. You must also run the command
:code:`symfony console lexik:jwt:generate-keypair` to generate some keys that will be stored in the paths set up in the :code:`JWT_SECRET_KEY`
and the :code:`JWT_PUBLIC_KEY` env variables. This is only required for using the stored documents in Chill.
Some environment variables are available for the JWT authentication bundle in the :code:`.env` file.
Prepare migrations and other tools
**********************************
@@ -136,6 +135,8 @@ To continue the installation process, you will have to run migrations:
symfony console messenger:setup-transports
# prepare some views
symfony console chill:db:sync-views
# generate jwt token, required for some api features (webdav access, ...)
symfony console lexik:jwt:generate-keypair
.. warning::
@@ -170,7 +171,7 @@ can rely on the whole chill framework, meaning there is no need to add them to t
You will require some bundles to have the following development tools:
- add fixtures
- add profiler and var-dumper to debug
- add profiler and debug bundle
Install fixtures
****************
@@ -198,7 +199,7 @@ Add web profiler and debugger
.. code-block:: bash
symfony composer require --dev symfony/web-profiler-bundle symfony/var-dumper
symfony composer require --dev symfony/web-profiler-bundle symfony/debug-bundle
Working on chill bundles
************************

View File

@@ -27,7 +27,7 @@
"popper.js": "^1.16.1",
"postcss-loader": "^7.0.2",
"raw-loader": "^4.0.2",
"sass-loader": "^13.0.0",
"sass-loader": "^14.0.0",
"select2": "^4.0.13",
"select2-bootstrap-theme": "0.1.0-beta.10",
"style-loader": "^3.3.1",
@@ -58,7 +58,7 @@
"vue": "^3.2.37",
"vue-i18n": "^9.1.6",
"vue-multiselect": "3.0.0-alpha.2",
"vue-toast-notification": "^2.0",
"vue-toast-notification": "^3.1.2",
"vuex": "^4.0.0"
},
"browserslist": [

View File

@@ -1,34 +1,29 @@
parameters:
ignoreErrors:
-
message: "#^Foreach overwrites \\$key with its key variable\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
message: "#^Property Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomField\\:\\:\\$required \\(false\\) does not accept bool\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Form/PersonType.php
path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Templating/ChillTwigRoutingHelper.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomFieldsGroup.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
message: "#^Parameter \\#1 \\$user of method Chill\\\\DocStoreBundle\\\\Entity\\\\Document\\:\\:setUser\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User\\|null, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#"
count: 2
path: src/Bundle/ChillMainBundle/Repository/NotificationRepository.php
path: src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php
-
message: "#^Foreach overwrites \\$key with its key variable\\.$#"
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php
message: "#^Parameter \\#1 \\$user of method Chill\\\\DocStoreBundle\\\\Entity\\\\Document\\:\\:setUser\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User\\|null, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#"
count: 2
path: src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php
-
message: "#^Variable \\$participation might not be defined\\.$#"
@@ -40,6 +35,106 @@ parameters:
count: 1
path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php
-
message: "#^Comparison operation \"\\>\" between \\(bool\\|int\\|Redis\\) and 0 results in an error\\.$#"
count: 1
path: src/Bundle/ChillFranceTravailApiBundle/src/ApiHelper/ApiWrapper.php
-
message: "#^Variable \\$response might not be defined\\.$#"
count: 1
path: src/Bundle/ChillFranceTravailApiBundle/src/ApiHelper/ApiWrapper.php
-
message: "#^Function GuzzleHttp\\\\Psr7\\\\get not found\\.$#"
count: 1
path: src/Bundle/ChillFranceTravailApiBundle/src/ApiHelper/PartenaireRomeAppellation.php
-
message: "#^Function GuzzleHttp\\\\Psr7\\\\str not found\\.$#"
count: 2
path: src/Bundle/ChillFranceTravailApiBundle/src/ApiHelper/PartenaireRomeAppellation.php
-
message: "#^Parameter \\#1 \\$seconds of function sleep expects int, string given\\.$#"
count: 1
path: src/Bundle/ChillFranceTravailApiBundle/src/ApiHelper/PartenaireRomeAppellation.php
-
message: "#^Unreachable statement \\- code above always terminates\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Controller/CSPersonController.php
-
message: "#^Parameter \\#1 \\$interval of method DateTimeImmutable\\:\\:add\\(\\) expects DateInterval, string\\|null given\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Entity/Immersion.php
-
message: "#^Parameter \\#1 \\$object of static method DateTimeImmutable\\:\\:createFromMutable\\(\\) expects DateTime, DateTimeInterface given\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Entity/Immersion.php
-
message: "#^Property Chill\\\\JobBundle\\\\Entity\\\\Rome\\\\Metier\\:\\:\\$appellations is never read, only written\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Entity/Rome/Metier.php
-
message: "#^Method Chill\\\\JobBundle\\\\Export\\\\ListCSPerson\\:\\:splitArrayToColumns\\(\\) never returns Closure so it can be removed from the return type\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Export/ListCSPerson.php
-
message: "#^Variable \\$f might not be defined\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Export/ListCSPerson.php
-
message: "#^Method Chill\\\\JobBundle\\\\Export\\\\ListFrein\\:\\:splitArrayToColumns\\(\\) never returns Closure so it can be removed from the return type\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Export/ListFrein.php
-
message: "#^Method Chill\\\\JobBundle\\\\Export\\\\ListProjetProfessionnel\\:\\:splitArrayToColumns\\(\\) never returns Closure so it can be removed from the return type\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php
-
message: "#^Property Chill\\\\JobBundle\\\\Form\\\\ChoiceLoader\\\\RomeAppellationChoiceLoader\\:\\:\\$appellationRepository \\(Chill\\\\JobBundle\\\\Repository\\\\Rome\\\\AppellationRepository\\) does not accept Doctrine\\\\ORM\\\\EntityRepository\\<Chill\\\\JobBundle\\\\Entity\\\\Rome\\\\Appellation\\>\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Form/ChoiceLoader/RomeAppellationChoiceLoader.php
-
message: "#^Result of && is always false\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Form/ChoiceLoader/RomeAppellationChoiceLoader.php
-
message: "#^Strict comparison using \\=\\=\\= between array\\{\\} and Symfony\\\\Component\\\\Validator\\\\ConstraintViolationListInterface will always evaluate to false\\.$#"
count: 2
path: src/Bundle/ChillJobBundle/src/Form/ChoiceLoader/RomeAppellationChoiceLoader.php
-
message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Form/ChoiceLoader/RomeAppellationChoiceLoader.php
-
message: "#^Variable \\$metier might not be defined\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Form/ChoiceLoader/RomeAppellationChoiceLoader.php
-
message: "#^Parameter \\#1 \\$interval of method DateTimeImmutable\\:\\:add\\(\\) expects DateInterval, string\\|null given\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Security/Authorization/CSConnectesVoter.php
-
message: "#^Parameter \\#1 \\$object of static method DateTimeImmutable\\:\\:createFromMutable\\(\\) expects DateTime, DateTimeInterface given\\.$#"
count: 1
path: src/Bundle/ChillJobBundle/src/Security/Authorization/CSConnectesVoter.php
-
message: "#^Cannot unset offset '_token' on array\\{formatter\\: mixed, export\\: mixed, centers\\: mixed, alias\\: string\\}\\.$#"
count: 1
@@ -65,11 +160,31 @@ parameters:
count: 1
path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2
path: src/Bundle/ChillMainBundle/Repository/NotificationRepository.php
-
message: "#^Parameter \\#1 \\$user of method Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:\\:userHasAccessForCenter\\(\\) expects Chill\\\\MainBundle\\\\Entity\\\\User, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Templating/ChillTwigRoutingHelper.php
-
message: "#^Foreach overwrites \\$value with its value variable\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Form/PersonType.php
-
message: "#^Foreach overwrites \\$value with its value variable\\.$#"
count: 1

View File

@@ -28,6 +28,9 @@ return static function (RectorConfig $rectorConfig): void {
// register a single rule
$rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
$rectorConfig->rule(Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromPropertyTypeRector::class);
$rectorConfig->rule(Rector\TypeDeclaration\Rector\Class_\MergeDateTimePropertyTypeDeclarationRector::class);
$rectorConfig->rule(Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeDeclarationBasedOnParentClassMethodRector::class);
// part of the symfony 54 rules
$rectorConfig->rule(\Rector\Symfony\Symfony53\Rector\StaticPropertyFetch\KernelTestCaseContainerPropertyDeprecationRector::class);
@@ -36,14 +39,14 @@ return static function (RectorConfig $rectorConfig): void {
//define sets of rules
$rectorConfig->sets([
\Rector\Symfony\Set\SymfonySetList::SYMFONY_50,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_50_TYPES,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_51,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_52,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_53,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_54,
LevelSetList::UP_TO_PHP_82,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_40,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_41,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_42,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_43,
\Rector\Symfony\Set\SymfonySetList::SYMFONY_44,
\Rector\Doctrine\Set\DoctrineSetList::DOCTRINE_CODE_QUALITY,
\Rector\Doctrine\Set\DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
\Rector\PHPUnit\Set\PHPUnitSetList::PHPUNIT_90,
]);
$rectorConfig->ruleWithConfiguration(\Rector\Php80\Rector\Class_\AnnotationToAttributeRector::class, [

View File

@@ -99,10 +99,10 @@ final class ActivityController extends AbstractController
$form = $this->createDeleteForm($activity->getId(), $person, $accompanyingPeriod);
if (Request::METHOD_DELETE === $request->getMethod()) {
if (Request::METHOD_POST === $request->getMethod()) {
$form->handleRequest($request);
if ($form->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
$this->logger->notice('An activity has been removed', [
'by_user' => $this->getUser()->getUsername(),
'activity_id' => $activity->getId(),
@@ -640,7 +640,6 @@ final class ActivityController extends AbstractController
return $this->createFormBuilder()
->setAction($this->generateUrl('chill_activity_activity_delete', $params))
->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}

View File

@@ -79,11 +79,9 @@ class ActivityReason
/**
* Set active.
*
* @param bool $active
*
* @return ActivityReason
*/
public function setActive($active)
public function setActive(bool $active)
{
$this->active = $active;
@@ -110,11 +108,9 @@ class ActivityReason
/**
* Set name.
*
* @param array $name
*
* @return ActivityReason
*/
public function setName($name)
public function setName(array $name)
{
$this->name = $name;

View File

@@ -152,7 +152,7 @@ class ListActivityHelper
return '';
}
return $this->translator->trans($value);
return $this->translator->trans((string) $value);
},
};
}

View File

@@ -73,7 +73,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt
$qb->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.Activity::class." {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = acp"
'SELECT 1 FROM '.Activity::class." {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = activity.accompanyingPeriod"
)
);

View File

@@ -15,9 +15,9 @@ use Chill\ActivityBundle\Entity\ActivityType;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
final class ActivityTypeRepository implements ActivityTypeRepositoryInterface
final readonly class ActivityTypeRepository implements ActivityTypeRepositoryInterface
{
private readonly EntityRepository $repository;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $em)
{

View File

@@ -87,7 +87,6 @@
<li>
{% if bloc.type == 'user' %}
<span class="badge-user">
hello
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }}
</span>
{% else %}

View File

@@ -54,7 +54,7 @@ abstract class AbstractElementController extends AbstractController
$indexPage = 'chill_budget_elements_household_index';
}
if (Request::METHOD_DELETE === $request->getMethod()) {
if (Request::METHOD_POST === $request->getMethod()) {
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
@@ -198,10 +198,9 @@ abstract class AbstractElementController extends AbstractController
/**
* Creates a form to delete a help request entity by id.
*/
private function createDeleteForm(): Form
private function createDeleteForm(): \Symfony\Component\Form\FormInterface
{
return $this->createFormBuilder()
->setMethod(Request::METHOD_DELETE)
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}

View File

@@ -100,7 +100,7 @@ class Charge extends AbstractElement implements HasCentersInterface
return $this;
}
public function setHelp($help)
public function setHelp(?string $help)
{
$this->help = $help;

View File

@@ -15,9 +15,9 @@ use Chill\BudgetBundle\Entity\ChargeKind;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
final class ChargeKindRepository implements ChargeKindRepositoryInterface
final readonly class ChargeKindRepository implements ChargeKindRepositoryInterface
{
private readonly EntityRepository $repository;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{

View File

@@ -15,9 +15,9 @@ use Chill\BudgetBundle\Entity\ResourceKind;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
final class ResourceKindRepository implements ResourceKindRepositoryInterface
final readonly class ResourceKindRepository implements ResourceKindRepositoryInterface
{
private readonly EntityRepository $repository;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{

View File

@@ -84,7 +84,7 @@ class CalendarController extends AbstractController
$form = $this->createDeleteForm($entity);
if (Request::METHOD_DELETE === $request->getMethod()) {
if (Request::METHOD_POST === $request->getMethod()) {
$form->handleRequest($request);
if ($form->isValid()) {
@@ -512,7 +512,6 @@ class CalendarController extends AbstractController
{
return $this->createFormBuilder()
->setAction($this->generateUrl('chill_calendar_calendar_delete', ['id' => $calendar->getId()]))
->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}

View File

@@ -47,7 +47,7 @@ final class CalendarContextTest extends TestCase
{
$expected =
[
'track_datetime' => true,
'trackDatetime' => true,
'askMainPerson' => true,
'mainPersonLabel' => 'docgen.calendar.Destinee',
'askThirdParty' => false,
@@ -61,7 +61,7 @@ final class CalendarContextTest extends TestCase
{
$expected =
[
'track_datetime' => true,
'trackDatetime' => true,
'askMainPerson' => true,
'mainPersonLabel' => 'docgen.calendar.Destinee',
'askThirdParty' => false,

View File

@@ -172,11 +172,9 @@ class CustomField
/**
* Set active.
*
* @param bool $active
*
* @return CustomField
*/
public function setActive($active)
public function setActive(bool $active)
{
$this->active = $active;
@@ -224,18 +222,16 @@ class CustomField
/**
* Set order.
*
* @param float $order
*
* @return CustomField
*/
public function setOrdering($order)
public function setOrdering(?float $order)
{
$this->ordering = $order;
return $this;
}
public function setRequired($required)
public function setRequired(bool $required)
{
$this->required = $required;
@@ -245,7 +241,7 @@ class CustomField
/**
* @return $this
*/
public function setSlug($slug)
public function setSlug(?string $slug)
{
$this->slug = $slug;
@@ -255,11 +251,9 @@ class CustomField
/**
* Set type.
*
* @param string $type
*
* @return CustomField
*/
public function setType($type)
public function setType(?string $type)
{
$this->type = $type;

View File

@@ -129,7 +129,7 @@ class Option
/**
* @return $this
*/
public function setActive($active)
public function setActive(bool $active)
{
$this->active = $active;
@@ -139,7 +139,7 @@ class Option
/**
* @return $this
*/
public function setInternalKey($internal_key)
public function setInternalKey(string $internal_key)
{
$this->internalKey = $internal_key;
@@ -149,7 +149,7 @@ class Option
/**
* @return $this
*/
public function setKey($key)
public function setKey(?string $key)
{
$this->key = $key;

View File

@@ -69,7 +69,7 @@ class CustomFieldsDefaultGroup
*
* @return CustomFieldsDefaultGroup
*/
public function setCustomFieldsGroup($customFieldsGroup)
public function setCustomFieldsGroup(?CustomFieldsGroup $customFieldsGroup)
{
$this->customFieldsGroup = $customFieldsGroup;
@@ -79,11 +79,9 @@ class CustomFieldsDefaultGroup
/**
* Set entity.
*
* @param string $entity
*
* @return CustomFieldsDefaultGroup
*/
public function setEntity($entity)
public function setEntity(?string $entity)
{
$this->entity = $entity;

View File

@@ -165,11 +165,9 @@ class CustomFieldsGroup
/**
* Set entity.
*
* @param string $entity
*
* @return CustomFieldsGroup
*/
public function setEntity($entity)
public function setEntity(?string $entity)
{
$this->entity = $entity;

View File

@@ -21,14 +21,14 @@ use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class RelatorioDriver implements DriverInterface
final readonly class RelatorioDriver implements DriverInterface
{
private readonly string $url;
private string $url;
public function __construct(
private readonly HttpClientInterface $client,
private HttpClientInterface $client,
ParameterBagInterface $parameterBag,
private readonly LoggerInterface $logger
private LoggerInterface $logger
) {
$this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url'];
}

View File

@@ -16,11 +16,11 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\HttpFoundation\RequestStack;
final class DocGeneratorTemplateRepository implements DocGeneratorTemplateRepositoryInterface
final readonly class DocGeneratorTemplateRepository implements DocGeneratorTemplateRepositoryInterface
{
private readonly EntityRepository $repository;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager, private readonly RequestStack $requestStack)
public function __construct(EntityManagerInterface $entityManager, private RequestStack $requestStack)
{
$this->repository = $entityManager->getRepository(DocGeneratorTemplate::class);
}

View File

@@ -129,7 +129,7 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
return $this;
}
public function setUser($user): self
public function setUser(?\Chill\MainBundle\Entity\User $user): self
{
$this->user = $user;

View File

@@ -86,7 +86,7 @@ class DocumentCategory
return $this;
}
public function setDocumentClass($documentClass): self
public function setDocumentClass(?string $documentClass): self
{
$this->documentClass = $documentClass;

View File

@@ -55,14 +55,14 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt
return $this->scope;
}
public function setPerson($person): self
public function setPerson(Person $person): self
{
$this->person = $person;
return $this;
}
public function setScope($scope): self
public function setScope(?Scope $scope): self
{
$this->scope = $scope;

View File

@@ -15,7 +15,7 @@ use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Form\EventType;
use Chill\EventBundle\Form\Type\PickEventType;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory;
@@ -61,7 +61,7 @@ final class EventController extends AbstractController
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry
) {}
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/delete', name: 'chill_event__event_delete', requirements: ['event_id' => '\d+'], methods: ['GET', 'DELETE'])]
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/delete', name: 'chill_event__event_delete', requirements: ['event_id' => '\d+'], methods: ['GET', 'POST', 'DELETE'])]
public function deleteAction($event_id, Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|Response
{
$em = $this->managerRegistry->getManager();
@@ -78,10 +78,10 @@ final class EventController extends AbstractController
$form = $this->createDeleteForm($event_id);
if (Request::METHOD_DELETE === $request->getMethod()) {
if (Request::METHOD_POST === $request->getMethod()) {
$form->handleRequest($request);
if ($form->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
foreach ($participations as $participation) {
$em->remove($participation);
}
@@ -108,28 +108,6 @@ final class EventController extends AbstractController
]);
}
/**
* Displays a form to edit an existing Event entity.
*/
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/edit', name: 'chill_event__event_edit')]
public function editAction($event_id): Response
{
$em = $this->managerRegistry->getManager();
$entity = $em->getRepository(Event::class)->find($event_id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Event entity.');
}
$editForm = $this->createEditForm($entity);
return $this->render('@ChillEvent/Event/edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
}
/**
* List events subscriptions for a person.
*
@@ -313,7 +291,7 @@ final class EventController extends AbstractController
/**
* Edits an existing Event entity.
*/
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/update', name: 'chill_event__event_update', methods: ['POST', 'PUT'])]
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/edit', name: 'chill_event__event_edit', methods: ['GET', 'POST', 'PUT'])]
public function updateAction(Request $request, $event_id): \Symfony\Component\HttpFoundation\RedirectResponse|Response
{
$em = $this->managerRegistry->getManager();
@@ -324,14 +302,20 @@ final class EventController extends AbstractController
throw $this->createNotFoundException('Unable to find Event entity.');
}
$editForm = $this->createEditForm($entity);
$editForm = $this->createForm(EventType::class, $entity, [
'center' => $entity->getCenter(),
'role' => EventVoter::UPDATE,
]);
$editForm->add('submit', SubmitType::class, ['label' => 'Update']);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
if ($editForm->isSubmitted() && $editForm->isValid()) {
$em->persist($entity);
$em->flush();
$this->addFlash('success', $this->translator
->trans('The event was updated'));
$this->addFlash('success', $this->translator->trans('The event was updated'));
return $this->redirectToRoute('chill_event__event_show', ['event_id' => $event_id]);
}
@@ -418,7 +402,6 @@ final class EventController extends AbstractController
$builder->add('event_id', HiddenType::class, [
'data' => $event->getId(),
]);
dump($event->getId());
return $builder->getForm();
}
@@ -600,29 +583,7 @@ final class EventController extends AbstractController
->setAction($this->generateUrl('chill_event__event_delete', [
'event_id' => $event_id,
]))
->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}
/**
* Creates a form to edit a Event entity.
*
* @return \Symfony\Component\Form\FormInterface
*/
private function createEditForm(Event $entity)
{
$form = $this->createForm(EventType::class, $entity, [
'action' => $this->generateUrl('chill_event__event_update', ['event_id' => $entity->getId()]),
'method' => 'PUT',
'center' => $entity->getCenter(),
'role' => 'CHILL_EVENT_CREATE',
]);
$form->remove('center');
$form->add('submit', SubmitType::class, ['label' => 'Update']);
return $form;
}
}

View File

@@ -201,7 +201,7 @@ class EventTypeController extends AbstractController
/**
* Creates a form to delete a EventType entity by id.
*
* @return \Symfony\Component\Form\Form The form
* @return \Symfony\Component\Form\FormInterface The form
*/
private function createDeleteForm(mixed $id)
{
@@ -210,7 +210,6 @@ class EventTypeController extends AbstractController
'chill_eventtype_admin_delete',
['id' => $id]
))
->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}

View File

@@ -15,7 +15,7 @@ use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Form\ParticipationType;
use Chill\EventBundle\Repository\EventRepository;
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
use Chill\EventBundle\Security\ParticipationVoter;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\Common\Collections\Collection;
@@ -259,10 +259,10 @@ final class ParticipationController extends AbstractController
$form = $this->createDeleteForm($participation_id);
if (Request::METHOD_DELETE === $request->getMethod()) {
if (Request::METHOD_POST === $request->getMethod()) {
$form->handleRequest($request);
if ($form->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
$em->remove($participation);
$em->flush();
@@ -753,7 +753,6 @@ final class ParticipationController extends AbstractController
->setAction($this->generateUrl('chill_event_participation_delete', [
'participation_id' => $participation_id,
]))
->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}

View File

@@ -201,7 +201,7 @@ class RoleController extends AbstractController
/**
* Creates a form to delete a Role entity by id.
*
* @return \Symfony\Component\Form\Form The form
* @return \Symfony\Component\Form\FormInterface The form
*/
private function createDeleteForm(mixed $id)
{

View File

@@ -201,13 +201,12 @@ class StatusController extends AbstractController
/**
* Creates a form to delete a Status entity by id.
*
* @return \Symfony\Component\Form\Form The form
* @return \Symfony\Component\Form\FormInterface The form
*/
private function createDeleteForm(mixed $id)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('chill_event_admin_status_delete', ['id' => $id]))
->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}

View File

@@ -11,8 +11,8 @@ declare(strict_types=1);
namespace Chill\EventBundle\DependencyInjection;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
use Chill\EventBundle\Security\EventVoter;
use Chill\EventBundle\Security\ParticipationVoter;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
@@ -33,12 +33,13 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml');
$loader->load('services/authorization.yaml');
$loader->load('services/security.yaml');
$loader->load('services/fixtures.yaml');
$loader->load('services/forms.yaml');
$loader->load('services/repositories.yaml');
$loader->load('services/search.yaml');
$loader->load('services/timeline.yaml');
$loader->load('services/export.yaml');
}
/** (non-PHPdoc).

View File

@@ -47,7 +47,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
private ?Scope $circle = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE)]
private ?\DateTime $date;
private ?\DateTime $date = null;
#[ORM\Id]
#[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
@@ -265,11 +265,9 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
/**
* Set label.
*
* @param string $label
*
* @return Event
*/
public function setName($label)
public function setName(?string $label)
{
$this->name = $label;

View File

@@ -146,11 +146,9 @@ class EventType
/**
* Set active.
*
* @param bool $active
*
* @return EventType
*/
public function setActive($active)
public function setActive(bool $active)
{
$this->active = $active;

View File

@@ -81,11 +81,9 @@ class Role
/**
* Set active.
*
* @param bool $active
*
* @return Role
*/
public function setActive($active)
public function setActive(bool $active)
{
$this->active = $active;

View File

@@ -81,11 +81,9 @@ class Status
/**
* Set active.
*
* @param bool $active
*
* @return Status
*/
public function setActive($active)
public function setActive(bool $active)
{
$this->active = $active;

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export\Aggregator;
use Chill\EventBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
class EventDateAggregator implements AggregatorInterface
{
private const CHOICES = [
'by month' => 'month',
'by week' => 'week',
'by year' => 'year',
];
private const DEFAULT_CHOICE = 'year';
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$order = null;
switch ($data['frequency']) {
case 'month':
$fmt = 'YYYY-MM';
break;
case 'week':
$fmt = 'YYYY-IW';
break;
case 'year':
$fmt = 'YYYY';
$order = 'DESC';
break;
default:
throw new \RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency']));
}
$qb->addSelect(sprintf("TO_CHAR(event.date, '%s') AS date_aggregator", $fmt));
$qb->addGroupBy('date_aggregator');
$qb->addOrderBy('date_aggregator', $order);
}
public function applyOn(): string
{
return Declarations::EVENT;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('frequency', ChoiceType::class, [
'choices' => self::CHOICES,
'multiple' => false,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return ['frequency' => self::DEFAULT_CHOICE];
}
public function getLabels($key, array $values, $data)
{
return static function ($value) use ($data): string {
if ('_header' === $value) {
return 'by '.$data['frequency'];
}
if (null === $value) {
return '';
}
return match ($data['frequency']) {
default => $value,
};
};
}
public function getQueryKeys($data): array
{
return ['date_aggregator'];
}
public function getTitle(): string
{
return 'Group event by date';
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export\Aggregator;
use Chill\EventBundle\Export\Declarations;
use Chill\EventBundle\Repository\EventTypeRepository;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class EventTypeAggregator implements AggregatorInterface
{
final public const KEY = 'event_type_aggregator';
public function __construct(protected EventTypeRepository $eventTypeRepository, protected TranslatableStringHelperInterface $translatableStringHelper) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!\in_array('eventtype', $qb->getAllAliases(), true)) {
$qb->leftJoin('event.type', 'eventtype');
}
$qb->addSelect(sprintf('IDENTITY(event.type) AS %s', self::KEY));
$qb->addGroupBy(self::KEY);
}
public function applyOn(): string
{
return Declarations::EVENT;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form required for this aggregator
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): \Closure
{
return function (int|string|null $value): string {
if ('_header' === $value) {
return 'Event type';
}
if (null === $value || '' === $value || null === $t = $this->eventTypeRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($t->getName());
};
}
public function getQueryKeys($data): array
{
return [self::KEY];
}
public function getTitle()
{
return 'Group by event type';
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export\Aggregator;
use Chill\EventBundle\Export\Declarations;
use Chill\EventBundle\Repository\RoleRepository;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class RoleAggregator implements AggregatorInterface
{
final public const KEY = 'part_role_aggregator';
public function __construct(protected RoleRepository $roleRepository, protected TranslatableStringHelperInterface $translatableStringHelper) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!\in_array('event_part', $qb->getAllAliases(), true)) {
$qb->leftJoin('event_part.role', 'role');
}
$qb->addSelect(sprintf('IDENTITY(event_part.role) AS %s', self::KEY));
$qb->addGroupBy(self::KEY);
}
public function applyOn(): string
{
return Declarations::EVENT_PARTICIPANTS;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form required for this aggregator
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): \Closure
{
return function (int|string|null $value): string {
if ('_header' === $value) {
return 'Participant role';
}
if (null === $value || '' === $value || null === $r = $this->roleRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($r->getName());
};
}
public function getQueryKeys($data): array
{
return [self::KEY];
}
public function getTitle()
{
return 'Group by participant role';
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export;
/**
* This class declare constants used for the export framework.
*/
abstract class Declarations
{
final public const EVENT = 'event';
final public const EVENT_PARTICIPANTS = 'event_participants';
}

View File

@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export\Export;
use Chill\EventBundle\Export\Declarations;
use Chill\EventBundle\Repository\ParticipationRepository;
use Chill\EventBundle\Security\ParticipationVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
readonly class CountEventParticipations implements ExportInterface, GroupedExportInterface
{
private bool $filterStatsByCenters;
public function __construct(
private ParticipationRepository $participationRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription()
{
return 'Count participants to an event by various parameters.';
}
public function getGroup(): string
{
return 'Exports of events';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_event_participants' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Count event participants' : $value;
}
public function getQueryKeys($data)
{
return ['export_count_event_participants'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle()
{
return 'Count event participants';
}
public function getType(): string
{
return Declarations::EVENT_PARTICIPANTS;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->participationRepository
->createQueryBuilder('event_part')
->join('event_part.person', 'person');
$qb->select('COUNT(event_part.id) as export_count_event_participants');
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.PersonCenterHistory::class.' acl_count_person_history WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
}
return $qb;
}
public function requiredRole(): string
{
return ParticipationVoter::STATS;
}
public function supportsModifiers()
{
return [
Declarations::EVENT_PARTICIPANTS,
PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export\Export;
use Chill\EventBundle\Repository\EventRepository;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\EventBundle\Export\Declarations;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
readonly class CountEvents implements ExportInterface, GroupedExportInterface
{
private bool $filterStatsByCenters;
public function __construct(
private EventRepository $eventRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription()
{
return 'Count events by various parameters.';
}
public function getGroup(): string
{
return 'Exports of events';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_event' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Number of events' : $value;
}
public function getQueryKeys($data)
{
return ['export_count_event'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle()
{
return 'Count events';
}
public function getType(): string
{
return Declarations::EVENT;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->eventRepository
->createQueryBuilder('event')
->leftJoin('event.participations', 'epart')
->leftJoin('epart.person', 'person');
$qb->select('COUNT(DISTINCT event.id) as export_count_event');
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.PersonCenterHistory::class.' acl_count_person_history WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
}
return $qb;
}
public function requiredRole(): string
{
return EventVoter::STATS;
}
public function supportsModifiers()
{
return [
Declarations::EVENT,
PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export\Filter;
use Chill\EventBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class EventDateFilter implements FilterInterface
{
public function __construct(protected TranslatorInterface $translator, private readonly RollingDateConverterInterface $rollingDateConverter) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->between(
'event.date',
':date_from',
':date_to'
);
if ($where instanceof Expr\Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter(
'date_from',
$this->rollingDateConverter->convert($data['date_from'])
);
$qb->setParameter(
'date_to',
$this->rollingDateConverter->convert($data['date_to'])
);
}
public function applyOn(): string
{
return Declarations::EVENT;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('date_from', PickRollingDateType::class, [
'label' => 'Events after this date',
])
->add('date_to', PickRollingDateType::class, [
'label' => 'Events before this date',
]);
}
public function getFormDefaultData(): array
{
return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
}
public function describeAction($data, $format = 'string')
{
return [
'Filtered by date of event: only between %date_from% and %date_to%',
[
'%date_from%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'),
'%date_to%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'),
],
];
}
public function getTitle()
{
return 'Filtered by event date';
}
}

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export\Filter;
use Chill\EventBundle\Entity\EventType;
use Chill\EventBundle\Export\Declarations;
use Chill\EventBundle\Repository\EventTypeRepository;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class EventTypeFilter implements ExportElementValidatedInterface, FilterInterface
{
public function __construct(
protected TranslatableStringHelperInterface $translatableStringHelper,
protected EventTypeRepository $eventTypeRepository
) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$clause = $qb->expr()->in('event.type', ':selected_event_types');
$qb->andWhere($clause);
$qb->setParameter('selected_event_types', $data['types']);
}
public function applyOn(): string
{
return Declarations::EVENT;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('types', EntityType::class, [
'choices' => $this->eventTypeRepository->findAllActive(),
'class' => EventType::class,
'choice_label' => fn (EventType $ety) => $this->translatableStringHelper->localize($ety->getName()),
'multiple' => true,
'expanded' => false,
'attr' => [
'class' => 'select2',
],
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string')
{
$typeNames = array_map(
fn (EventType $t): string => $this->translatableStringHelper->localize($t->getName()),
$this->eventTypeRepository->findBy(['id' => $data['types'] instanceof \Doctrine\Common\Collections\Collection ? $data['types']->toArray() : $data['types']])
);
return ['Filtered by event type: only %list%', [
'%list%' => implode(', ', $typeNames),
]];
}
public function getTitle()
{
return 'Filtered by event type';
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['types'] || 0 === \count($data['types'])) {
$context
->buildViolation('At least one type must be chosen')
->addViolation();
}
}
}

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Export\Filter;
use Chill\EventBundle\Entity\Role;
use Chill\EventBundle\Export\Declarations;
use Chill\EventBundle\Repository\RoleRepository;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class RoleFilter implements ExportElementValidatedInterface, FilterInterface
{
public function __construct(
protected TranslatableStringHelperInterface $translatableStringHelper,
protected RoleRepository $roleRepository
) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$clause = $qb->expr()->in('event_part.role', ':selected_part_roles');
$qb->andWhere($clause);
$qb->setParameter('selected_part_roles', $data['part_roles']);
}
public function applyOn(): string
{
return Declarations::EVENT_PARTICIPANTS;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('part_roles', EntityType::class, [
'choices' => $this->roleRepository->findAllActive(),
'class' => Role::class,
'choice_label' => fn (Role $r) => $this->translatableStringHelper->localize($r->getName()),
'multiple' => true,
'expanded' => false,
'attr' => [
'class' => 'select2',
],
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string')
{
$roleNames = array_map(
fn (Role $r): string => $this->translatableStringHelper->localize($r->getName()),
$this->roleRepository->findBy(['id' => $data['part_roles'] instanceof \Doctrine\Common\Collections\Collection ? $data['part_roles']->toArray() : $data['part_roles']])
);
return ['Filtered by participant roles: only %list%', [
'%list%' => implode(', ', $roleNames),
]];
}
public function getTitle()
{
return 'Filter by participant roles';
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['part_roles'] || 0 === \count($data['part_roles'])) {
$context
->buildViolation('At least one role must be chosen')
->addViolation();
}
}
}

View File

@@ -13,6 +13,7 @@ namespace Chill\EventBundle\Form;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Form\Type\PickEventTypeType;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Form\Type\ChillCollectionType;
@@ -23,6 +24,7 @@ use Chill\MainBundle\Form\Type\PickUserLocationType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -31,7 +33,9 @@ class EventType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('name', TextType::class, [
'required' => true,
])
->add('date', ChillDateTimeType::class, [
'required' => true,
])
@@ -75,7 +79,7 @@ class EventType extends AbstractType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => \Chill\EventBundle\Entity\Event::class,
'data_class' => Event::class,
]);
$resolver
->setRequired(['center', 'role'])

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\EventBundle\Menu;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\EventBundle\Menu;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;

View File

@@ -13,7 +13,7 @@ namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\PersonBundle\Entity\Person;

View File

@@ -12,13 +12,57 @@ declare(strict_types=1);
namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\Role;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
class RoleRepository extends ServiceEntityRepository
readonly class RoleRepository implements ObjectRepository
{
public function __construct(ManagerRegistry $registry)
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager, private TranslatableStringHelper $translatableStringHelper)
{
parent::__construct($registry, Role::class);
$this->repository = $entityManager->getRepository(Role::class);
}
public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder
{
return $this->repository->createQueryBuilder($alias, $indexBy);
}
public function find($id)
{
return $this->repository->find($id);
}
public function findAll(): array
{
return $this->repository->findAll();
}
public function findAllActive(): array
{
$roles = $this->repository->findBy(['active' => true]);
usort($roles, fn (Role $a, Role $b) => $this->translatableStringHelper->localize($a->getName()) <=> $this->translatableStringHelper->localize($b->getName()));
return $roles;
}
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findOneBy(array $criteria)
{
return $this->repository->findOneBy($criteria);
}
public function getClassName(): string
{
return Role::class;
}
}

View File

@@ -1,7 +1,7 @@
{% import '@ChillPerson/Person/macro.html.twig' as person_macro %}
{% if ignored_participations|length > 0 %}
<p>{% transchoice ignored_participations|length %}The following people have been ignored because they are already participating on the event{% endtranschoice %}&nbsp;:</p>
<p>{{ 'ignored_participations'|trans({'count': ignored_participations|length}) }}:</p>
<ul>
{% for p in ignored_participations %}
<li>{{ person_macro.render(p.person) }}</li>

View File

@@ -9,18 +9,19 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Security\Authorization;
namespace Chill\EventBundle\Security;
use Chill\EventBundle\Entity\Event;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
/**
* Description of EventVoter.
@@ -42,61 +43,46 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
final public const UPDATE = 'CHILL_EVENT_UPDATE';
/**
* @var AccessDecisionManagerInterface
*/
protected $accessDecisionManager;
final public const STATS = 'CHILL_EVENT_STATS';
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/**
* @var LoggerInterface
*/
protected $logger;
private readonly VoterHelperInterface $voterHelper;
public function __construct(
AccessDecisionManagerInterface $accessDecisionManager,
AuthorizationHelper $authorizationHelper,
LoggerInterface $logger
private readonly AuthorizationHelper $authorizationHelper,
private readonly LoggerInterface $logger,
VoterHelperFactoryInterface $voterHelperFactory
) {
$this->accessDecisionManager = $accessDecisionManager;
$this->authorizationHelper = $authorizationHelper;
$this->logger = $logger;
$this->voterHelper = $voterHelperFactory
->generate(self::class)
->addCheckFor(null, [self::SEE])
->addCheckFor(Event::class, [...self::ROLES])
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->addCheckFor(Center::class, [self::STATS])
->build();
}
public function getRoles(): array
{
return self::ROLES;
return [...self::ROLES, self::STATS];
}
public function getRolesWithHierarchy(): array
{
return [
'Event' => self::ROLES,
'Event' => $this->getRoles(),
];
}
public function getRolesWithoutScope(): array
{
return [];
return [self::ROLES, self::STATS];
}
public function supports($attribute, $subject)
{
return ($subject instanceof Event && \in_array($attribute, self::ROLES, true))
|| ($subject instanceof Person && \in_array($attribute, [self::CREATE, self::SEE], true))
|| (null === $subject && self::SEE === $attribute);
return $this->voterHelper->supports($attribute, $subject);
}
/**
* @param string $attribute
* @param Event $subject
*
* @return bool
*/
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$this->logger->debug(sprintf('Voting from %s class', self::class));
@@ -118,15 +104,5 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
->getReachableCenters($token->getUser(), $attribute);
return \count($centers) > 0;
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
return false;
}
return $this->authorizationHelper->userHasAccess(
$token->getUser(),
$subject,
$attribute
);
}
}

View File

@@ -9,18 +9,19 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Security\Authorization;
namespace Chill\EventBundle\Security;
use Chill\EventBundle\Entity\Participation;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
@@ -39,58 +40,48 @@ class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierar
final public const UPDATE = 'CHILL_EVENT_PARTICIPATION_UPDATE';
/**
* @var AccessDecisionManagerInterface
*/
protected $accessDecisionManager;
final public const STATS = 'CHILL_EVENT_PARTICIPATION_STATS';
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/**
* @var LoggerInterface
*/
protected $logger;
private readonly VoterHelperInterface $voterHelper;
public function __construct(
AccessDecisionManagerInterface $accessDecisionManager,
AuthorizationHelper $authorizationHelper,
LoggerInterface $logger
private readonly AuthorizationHelper $authorizationHelper,
private readonly LoggerInterface $logger,
VoterHelperFactoryInterface $voterHelperFactory
) {
$this->accessDecisionManager = $accessDecisionManager;
$this->authorizationHelper = $authorizationHelper;
$this->logger = $logger;
$this->voterHelper = $voterHelperFactory
->generate(self::class)
->addCheckFor(null, [self::SEE])
->addCheckFor(Participation::class, [...self::ROLES])
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->addCheckFor(Center::class, [self::STATS])
->build();
}
public function getRoles(): array
{
return self::ROLES;
return [...self::ROLES, self::STATS];
}
public function getRolesWithHierarchy(): array
{
return [
'Event' => self::ROLES,
'Participation' => $this->getRoles(),
];
}
public function getRolesWithoutScope(): array
{
return [];
return [self::ROLES, self::STATS];
}
public function supports($attribute, $subject)
{
return ($subject instanceof Participation && \in_array($attribute, self::ROLES, true))
|| ($subject instanceof Person && \in_array($attribute, [self::CREATE, self::SEE], true))
|| (null === $subject && self::SEE === $attribute);
return $this->voterHelper->supports($attribute, $subject);
}
/**
* @param string $attribute
* @param Participation $subject
* @param string $attribute
*
* @return bool
*/
@@ -115,15 +106,5 @@ class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierar
->getReachableCenters($token->getUser(), $attribute);
return \count($centers) > 0;
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
return false;
}
return $this->authorizationHelper->userHasAccess(
$token->getUser(),
$subject,
$attribute
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Tests\Export;
use Chill\EventBundle\Export\Export\CountEventParticipations;
use Doctrine\ORM\AbstractQuery;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
*
* @coversNothing
*/
class CountEventParticipationsTest extends KernelTestCase
{
private CountEventParticipations $countEventParticipations;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->countEventParticipations = self::getContainer()->get(CountEventParticipations::class);
}
public function testExecuteQuery(): void
{
$qb = $this->countEventParticipations->initiateQuery([], [], [])
->setMaxResults(1);
$results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
self::assertIsArray($results, 'smoke test: test that the result is an array');
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\EventBundle\Tests\Export;
use Chill\EventBundle\Export\Export\CountEvents;
use Doctrine\ORM\AbstractQuery;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
*
* @coversNothing
*/
class CountEventTest extends KernelTestCase
{
private CountEvents $countEvents;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->countEvents = self::getContainer()->get(CountEvents::class);
}
public function testExecuteQuery(): void
{
$qb = $this->countEvents->initiateQuery([], [], [])
->setMaxResults(1);
$results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
self::assertIsArray($results, 'smoke test: test that the result is an array');
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Export\aggregators;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Export\Aggregator\EventDateAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class EventDateAggregatorTest extends AbstractAggregatorTest
{
private $aggregator;
protected function setUp(): void
{
self::bootKernel();
$this->aggregator = self::getContainer()->get(EventDateAggregator::class);
}
public function getAggregator()
{
return $this->aggregator;
}
public function getFormData(): array|\Generator
{
yield ['frequency' => 'YYYY'];
yield ['frequency' => 'YYYY-MM'];
yield ['frequency' => 'YYYY-IV'];
}
public function getQueryBuilders(): array
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('event.id')
->from(Event::class, 'event'),
];
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Export\aggregators;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Export\Aggregator\EventTypeAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class EventTypeAggregatorTest extends AbstractAggregatorTest
{
private $aggregator;
protected function setUp(): void
{
self::bootKernel();
$this->aggregator = self::getContainer()->get(EventTypeAggregator::class);
}
public function getAggregator()
{
return $this->aggregator;
}
public function getFormData(): array
{
return [
[],
];
}
public function getQueryBuilders(): array
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('event.id')
->from(Event::class, 'event'),
];
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Export\aggregators;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Export\Aggregator\RoleAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class RoleAggregatorTest extends AbstractAggregatorTest
{
private $aggregator;
protected function setUp(): void
{
self::bootKernel();
$this->aggregator = self::getContainer()->get(RoleAggregator::class);
}
public function getAggregator()
{
return $this->aggregator;
}
public function getFormData(): array
{
return [
[],
];
}
public function getQueryBuilders(): array
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('event.id')
->from(Event::class, 'event'),
$em->createQueryBuilder()
->select('event_part')
->from(Participation::class, 'event_part'),
];
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Export\filters;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Export\Filter\EventDateFilter;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class EventDateFilterTest extends AbstractFilterTest
{
private RollingDateConverterInterface $rollingDateConverter;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->rollingDateConverter = self::getContainer()->get(RollingDateConverterInterface::class);
}
public function getFilter()
{
return new EventDateFilter($this->rollingDateConverter);
}
public function getFormData()
{
return [
[
'date_from' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
'date_to' => new RollingDate(RollingDate::T_TODAY),
],
];
}
public function getQueryBuilders(): array
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('event.id')
->from(Event::class, 'event'),
];
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Export\filters;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\EventType;
use Chill\EventBundle\Export\Filter\EventTypeFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class EventTypeFilterTest extends AbstractFilterTest
{
private EventTypeFilter $filter;
protected function setUp(): void
{
self::bootKernel();
$this->filter = self::getContainer()->get(EventTypeFilter::class);
}
public function getFilter(): EventTypeFilter|\Chill\MainBundle\Export\FilterInterface
{
return $this->filter;
}
public function getFormData()
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
$array = $em->createQueryBuilder()
->from(EventType::class, 'et')
->select('et')
->getQuery()
->getResult();
$data = [];
foreach ($array as $a) {
$data[] = [
'types' => new ArrayCollection([$a]),
];
}
return $data;
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('event.id')
->from(Event::class, 'event'),
];
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Export\filters;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Entity\Role;
use Chill\EventBundle\Export\Filter\RoleFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class RoleFilterTest extends AbstractFilterTest
{
private RoleFilter $filter;
protected function setUp(): void
{
self::bootKernel();
$this->filter = self::getContainer()->get(RoleFilter::class);
}
public function getFilter()
{
return $this->filter;
}
public function getFormData(): array
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
$array = $em->createQueryBuilder()
->from(Role::class, 'r')
->select('r')
->getQuery()
->setMaxResults(1)
->getResult();
$data = [];
foreach ($array as $a) {
$data[] = [
'roles' => new ArrayCollection([$a]),
];
}
return $data;
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('event.id')
->from(Event::class, 'event'),
$em->createQueryBuilder()
->select('event_part')
->from(Participation::class, 'event_part'),
];
}
}

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\EventBundle\Tests\Repository;
use Chill\EventBundle\Repository\EventACLAwareRepository;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;

View File

@@ -1,18 +0,0 @@
services:
chill_event.event_voter:
class: Chill\EventBundle\Security\Authorization\EventVoter
arguments:
- "@security.access.decision_manager"
- "@chill.main.security.authorization.helper"
- "@logger"
tags:
- { name: security.voter }
chill_event.event_participation:
class: Chill\EventBundle\Security\Authorization\ParticipationVoter
arguments:
- "@security.access.decision_manager"
- "@chill.main.security.authorization.helper"
- "@logger"
tags:
- { name: security.voter }

View File

@@ -0,0 +1,41 @@
services:
_defaults:
autowire: true
autoconfigure: true
# indicators
Chill\EventBundle\Export\Export\CountEvents:
tags:
- { name: chill.export, alias: 'count_events' }
Chill\EventBundle\Export\Export\CountEventParticipations:
tags:
- { name: chill.export, alias: 'count_event_participants' }
# filters
Chill\EventBundle\Export\Filter\EventDateFilter:
tags:
- { name: chill.export_filter, alias: 'event_date_filter' }
Chill\EventBundle\Export\Filter\EventTypeFilter:
tags:
- { name: chill.export_filter, alias: 'event_type_filter' }
Chill\EventBundle\Export\Filter\RoleFilter:
tags:
- { name: chill.export_filter, alias: 'role_filter' }
# aggregators
Chill\EventBundle\Export\Aggregator\EventTypeAggregator:
tags:
- { name: chill.export_aggregator, alias: event_type_aggregator }
Chill\EventBundle\Export\Aggregator\EventDateAggregator:
tags:
- { name: chill.export_aggregator, alias: event_date_aggregator }
Chill\EventBundle\Export\Aggregator\RoleAggregator:
tags:
- { name: chill.export_aggregator, alias: role_aggregator }

View File

@@ -0,0 +1,14 @@
services:
Chill\EventBundle\Security\EventVoter:
autowire: true
autoconfigure: true
tags:
- { name: security.voter }
- { name: chill.role }
Chill\EventBundle\Security\ParticipationVoter:
autowire: true
autoconfigure: true
tags:
- { name: security.voter }
- { name: chill.role }

View File

@@ -19,3 +19,9 @@ events:
one {et un autre participant}
other {et # autres participants}
}
ignored_participations: >-
{ count, plural,
one {La personne suivante a été ignorée parce qu''elle participe déjà à l''événement}
other {Les personnes suivantes ont été ignorées parce qu''elles participent déjà à l'événement}
}

View File

@@ -41,7 +41,6 @@ Back to the event: Retour à l'événement
The participation was created: La participation a été créée
The participation was updated: La participation a été mise à jour
'None of the requested people may participate the event: they are maybe already participating.': 'Aucune des personnes indiquées ne peut être ajoutée à l''événement: elles sont peut-être déjà inscrites comme participantes.'
'The following people have been ignored because they are already participating on the event': '{1} La personne suivante a été ignorée parce qu''elle participe déjà à l''événement | ]1,Inf] Les personnes suivantes ont été ignorées parce qu''elles participent déjà à l''événement'
There are no participation to edit for this event: Il n'y a pas de participation pour cet événement
The participations have been successfully updated.: Les participations ont été mises à jour.
The participation has been sucessfully removed: La participation a été correctement supprimée.
@@ -81,9 +80,31 @@ Pick an event: Choisir un événement
Pick a type of event: Choisir un type d'événement
Pick a moderator: Choisir un animateur
# exports
Select a format: Choisir un format
Export: Exporter
Count events: Nombre d'événements
Count events by various parameters.: Compte le nombre d'événements selon divers critères
Exports of events: Exports d'événements
Filtered by event date: Filtrer par date d'événement
'Filtered by date of event: only between %date_from% and %date_to%': "Filtré par date d'événement: uniquement entre le %date_from% et le %date_to%"
Events after this date: Événements après cette date
Events before this date: Événements avant cette date
Filtered by event type: Filtrer par type d'événement
'Filtered by event type: only %list%': "Filtré par type: uniquement %list%"
Group event by date: Grouper par date d'événement
Group by event type: Grouper par type d'événement
Count event participants: Nombre de participations
Count participants to an event by various parameters.: Compte le nombre de participations selon divers critères
Exports of event participants: Exports de participations
'Filtered by participant roles: only %list%': "Filtré par rôles de participation: uniquement %list%"
Filter by participant roles: Filtrer par rôles de participation
Part roles: Rôles de participation
Group by participant role: Grouper par rôle de participation
Events configuration: Configuration des événements
Events configuration menu: Menu des événements

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle\ApiHelper;
use Chill\MainBundle\Redis\ChillRedis;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
/**
* Wraps the pole emploi api.
*/
class ApiWrapper
{
/**
* @var Client
*/
private $client;
/**
* key for the bearer for the api pole emploi.
*
* This bearer is shared across users
*/
public const UNPERSONAL_BEARER = 'api_pemploi_bear_';
public function __construct(private $clientId, private $clientSecret, private readonly ChillRedis $redis)
{
$this->client = new Client([
'base_uri' => 'https://entreprise.francetravail.fr/connexion/oauth2/access_token',
]);
}
public function getPublicBearer($scopes): string
{
$cacheKey = $this->getCacheKey($scopes);
if ($this->redis->exists($cacheKey) > 0) {
$data = \unserialize($this->redis->get($cacheKey));
return $data->access_token;
}
try {
$response = $this->client->post('', [
'query' => ['realm' => '/partenaire'],
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
],
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'scope' => \implode(' ', \array_merge($scopes, ['application_'.$this->clientId])),
],
]);
} catch (ClientException $e) {
dump($e->getResponse());
}
$data = \json_decode((string) $response->getBody());
// set the key with an expiry time
$this->redis->setex(
$cacheKey,
$data->expires_in - 2,
\serialize($data)
);
return $data->access_token;
}
protected function getCacheKey($scopes)
{
return self::UNPERSONAL_BEARER.implode('', $scopes);
}
}

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle\ApiHelper;
use GuzzleHttp\Client;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
/**
* Queries for ROME partenaires api.
*/
class PartenaireRomeAppellation
{
use ProcessRequestTrait;
/**
* @var ApiWrapper
*/
protected $wrapper;
/**
* @var Client
*/
protected $client;
/**
* @var LoggerInterface
*/
protected $logger;
private const BASE = 'https://api.pole-emploi.io/partenaire/rome-metiers/v1/metiers/';
public function __construct(
ApiWrapper $wrapper,
LoggerInterface $logger,
private \Symfony\Contracts\HttpClient\HttpClientInterface $httpClient,
) {
$this->wrapper = $wrapper;
$this->logger = $logger;
$this->client = new Client([
'base_uri' => 'https://api.pole-emploi.io/partenaire/rome-metiers/v1/metiers/',
]);
}
private function getBearer()
{
return $this->wrapper->getPublicBearer([
'api_rome-metiersv1',
'nomenclatureRome',
]);
}
public function getListeAppellation(string $search): array
{
$bearer = $this->getBearer();
try {
$response = $this->httpClient->request(
'GET',
self::BASE.'appellation/requete',
[
'headers' => [
'Authorization' => 'Bearer '.$bearer,
'Accept' => 'application/json',
],
'query' => [
'q' => $search,
],
]
);
return $response->toArray()['resultats'];
} catch (HttpExceptionInterface $exception) {
throw $exception;
}
}
public function getAppellation(string $code): array
{
$bearer = $this->getBearer();
while (true) {
try {
$response = $this->httpClient->request('GET', sprintf(self::BASE.'appellation/%s', $code), [
'headers' => [
'Authorization' => 'Bearer '.$bearer,
'Accept' => 'application/json',
],
'query' => [
'champs' => 'code,libelle,metier(code,libelle)',
],
]);
return $response->toArray();
} catch (HttpExceptionInterface $exception) {
if (429 === $exception->getResponse()->getStatusCode()) {
$retryAfter = $exception->getResponse()->getHeaders(false)['retry-after'][0] ?? 1;
sleep((int) $retryAfter);
} else {
throw $exception;
}
}
}
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle\ApiHelper;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7;
use Psr\Log\LoggerInterface;
/**
* Methods to process request against the api, and handle the
* Exceptions.
*/
trait ProcessRequestTrait
{
/**
* Handle a request and 429 errors.
*
* @param Request $request the request
* @param array $parameters the requests parameters
*/
protected function handleRequest(
Request $request,
array $parameters,
Client $client,
LoggerInterface $logger
) {
return $this->handleRequestRecursive(
$request,
$parameters,
$client,
$logger
);
}
/**
* internal method to handle recursive requests.
*
* @throws BadResponseException
*/
private function handleRequestRecursive(
Request $request,
array $parameters,
Client $client,
LoggerInterface $logger,
$counter = 0
) {
try {
return $client->send($request, $parameters);
} catch (BadResponseException $e) {
if (
// get 429 exceptions
$e instanceof ClientException
&& 429 === $e->getResponse()->getStatusCode()
&& count($e->getResponse()->getHeader('Retry-After')) > 0) {
if ($counter > 5) {
$logger->error('too much 429 response', [
'request' => Psr7\get($e->getRequest()),
]);
throw $e;
}
$delays = $e->getResponse()->getHeader('Retry-After');
$delay = \end($delays);
sleep($delay);
return $this->handleRequestRecursive(
$request,
$parameters,
$client,
$logger,
$counter + 1
);
}
// handling other errors
$logger->error('Error while querying ROME api', [
'status_code' => $e->getResponse()->getStatusCode(),
'part' => 'appellation',
'request' => $e->getRequest()->getBody()->getContents(),
'response' => $e->getResponse()->getBody()->getContents(),
]);
throw $e;
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillFranceTravailApiBundle extends Bundle {}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Chill\FranceTravailApiBundle\ApiHelper\PartenaireRomeAppellation;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
class RomeController extends AbstractController
{
/**
* @var PartenaireRomeAppellation
*/
protected $apiAppellation;
public function __construct(PartenaireRomeAppellation $apiAppellation)
{
$this->apiAppellation = $apiAppellation;
}
#[Route(path: '/{_locale}/france-travail/appellation/search.{_format}', name: 'chill_france_travail_api_appellation_search')]
public function appellationSearchAction(Request $request)
{
if (false === $request->query->has('q')) {
return new JsonResponse([]);
}
$appellations = $this->apiAppellation
->getListeAppellation($request->query->get('q'));
$results = [];
foreach ($appellations as $appellation) {
$appellation['id'] = 'original-'.$appellation['code'];
$appellation['text'] = $appellation['libelle'];
$results[] = $appellation;
}
$computed = new \stdClass();
$computed->pagination = (new \stdClass());
$computed->pagination->more = false;
$computed->results = $results;
return new JsonResponse($computed);
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration.
*
* @see http://symfony.com/doc/current/cookbook/bundles/extension.html
*/
class ChillFranceTravailApiExtension extends Extension implements PrependExtensionInterface
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
public function prepend(ContainerBuilder $container): void
{
$this->prependRoute($container);
}
protected function prependRoute(ContainerBuilder $container): void
{
// declare routes for france travail api bundle
$container->prependExtensionConfig('chill_main', [
'routing' => [
'resources' => [
'@ChillFranceTravailApiBundle/Resources/config/routing.yml',
],
],
]);
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
*/
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('chill_france_travail_api');
$rootNode = $treeBuilder->getRootNode();
// Here you should define the parameters that are allowed to
// configure your bundle. See the documentation linked above for
// more information on that topic.
return $treeBuilder;
}
}

View File

@@ -0,0 +1,3 @@
chill_france_travail_api_controllers:
resource: "@ChillFranceTravailApiBundle/Controller"
type: annotation

View File

@@ -0,0 +1,16 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\FranceTravailApiBundle\ApiHelper\ApiWrapper:
$clientId: '%env(FRANCE_TRAVAIL_CLIENT_ID)%'
$clientSecret: '%env(FRANCE_TRAVAIL_CLIENT_SECRET)%'
$redis: '@Chill\MainBundle\Redis\ChillRedis'
Chill\FranceTravailApiBundle\ApiHelper\PartenaireRomeAppellation: ~
Chill\FranceTravailApiBundle\Controller\RomeController:
autowire: true
autoconfigure: true
tags: ['controller.service_arguments']

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle\Tests\ApiHelper;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Chill\FranceTravailApiBundle\ApiHelper\PartenaireRomeAppellation;
/**
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*
* @internal
*
* @coversNothing
*/
class PartenaireRomeAppellationTest extends KernelTestCase
{
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
}
public function testGetListeMetiersSimple()
{
/** @var PartenaireRomeAppellation $appellations */
$appellations = self::$kernel
->getContainer()
->get(PartenaireRomeAppellation::class)
;
$data = $appellations->getListeAppellation('arb');
$this->assertTrue(\is_array($data));
$this->assertNotNull($data[0]->libelle);
$this->assertNotNull($data[0]->code);
}
public function testGetListeMetiersTooMuchRequests()
{
/** @var PartenaireRomeMetier $appellations */
$appellations = self::$kernel
->getContainer()
->get(PartenaireRomeAppellation::class)
;
$appellations->getListeAppellation('arb');
$appellations->getListeAppellation('ing');
$appellations->getListeAppellation('rob');
$appellations->getListeAppellation('chori');
$data = $appellations->getListeAppellation('camion');
$this->assertTrue(
$data[0] instanceof \stdClass,
'assert that first index of data is an instance of stdClass'
);
}
public function testGetAppellation()
{
/** @var PartenaireRomeMetier $appellations */
$appellations = self::$kernel
->getContainer()
->get(PartenaireRomeAppellation::class)
;
$a = $appellations->getListeAppellation('arb');
$full = $appellations->getAppellation($a[0]->code);
$this->assertNotNull(
$full->libelle,
'assert that libelle is not null'
);
$this->assertTrue(
$full->metier instanceof \stdClass,
'assert that metier is returned'
);
$this->assertNotNull(
$full->metier->libelle,
'assert that metier->libelle is not null'
);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\FranceTravailApiBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* @internal
*
* @coversNothing
*/
class RomeControllerTest extends WebTestCase
{
public function testAppellationsearch()
{
$client = static::createClient();
$crawler = $client->request('GET', '/{_locale}/pole-emploi/appellation/search');
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillJobBundle extends Bundle {}

View File

@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle\Controller;
use Chill\PersonBundle\CRUD\Controller\EntityPersonCRUDController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Chill\JobBundle\Entity\Immersion;
use Symfony\Component\HttpFoundation\Response;
/**
* CRUD Controller for reports (Frein, ...).
*/
class CSCrudReportController extends EntityPersonCRUDController
{
protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request): ?Response
{
$next = $request->request->get('submit', 'save-and-close');
return match ($next) {
'save-and-close', 'delete-and-close' => $this->redirectToRoute('chill_job_report_index', [
'person' => $entity->getPerson()->getId(),
]),
default => parent::onBeforeRedirectAfterSubmission($action, $entity, $form, $request),
};
}
protected function duplicateEntity(string $action, Request $request)
{
if ('cscv' === $this->getCrudName()) {
$id = $request->query->get('duplicate_id', 0);
/** @var \Chill\JobBundle\Entity\CV $cv */
$cv = $this->getEntity($action, $id, $request);
$em = $this->managerRegistry->getManager();
$em->detach($cv);
foreach ($cv->getExperiences() as $experience) {
$cv->removeExperience($experience);
$em->detach($experience);
$cv->addExperience($experience);
}
foreach ($cv->getFormations() as $formation) {
$cv->removeFormation($formation);
$em->detach($formation);
$cv->addFormation($formation);
}
return $cv;
}
if ('projet_prof' === $this->getCrudName()) {
$id = $request->query->get('duplicate_id', 0);
/** @var \Chill\JobBundle\Entity\ProjetProfessionnel $original */
$original = $this->getEntity($action, $id, $request);
$new = parent::duplicateEntity($action, $request);
foreach ($original->getSouhait() as $s) {
$new->addSouhait($s);
}
foreach ($original->getValide() as $s) {
$new->addValide($s);
}
return $new;
}
return parent::duplicateEntity($action, $request);
}
protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface
{
if ($entity instanceof Immersion) {
if ('edit' === $action || 'new' === $action) {
return parent::createFormFor($action, $entity, $formClass, [
'center' => $entity->getPerson()->getCenter(),
]);
}
if ('bilan' === $action) {
return parent::createFormFor($action, $entity, $formClass, [
'center' => $entity->getPerson()->getCenter(),
'step' => 'bilan',
]);
}
if ('delete' === $action) {
return parent::createFormFor($action, $entity, $formClass, $formOptions);
}
throw new \LogicException("this step {$action} is not supported");
}
return parent::createFormFor($action, $entity, $formClass, $formOptions);
}
protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request)
{
// for immersion / edit-bilan action
if ('bilan' === $action) {
/* @var $entity Immersion */
$entity->setIsBilanFullfilled(true);
}
parent::onPreFlush($action, $entity, $form, $request);
}
/**
* Edit immersion bilan.
*
* @param int $id
*/
public function editBilan(Request $request, $id): Response
{
return $this->formEditAction('bilan', $request, $id);
}
}

View File

@@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle\Controller;
use Chill\PersonBundle\CRUD\Controller\OneToOneEntityPersonCRUDController;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Chill\JobBundle\Form\CSPersonPersonalSituationType;
use Chill\JobBundle\Form\CSPersonDispositifsType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class CSPersonController extends OneToOneEntityPersonCRUDController
{
#[Route(path: '{_locale}/person/job/personal_situation/{id}/edit', name: 'chill_crud_job_personal_situation_edit')]
public function personalSituationEdit(Request $request, $id): Response
{
return $this->formEditAction(
'ps_situation_edit',
$request,
$id,
CSPersonPersonalSituationType::class
);
}
#[Route(path: '{_locale}/person/job/dispositifs/{id}/edit', name: 'chill_crud_job_dispositifs_edit')]
public function dispositifsEdit(Request $request, $id)
{
return $this->formEditAction(
'dispositifs_edit',
$request,
$id,
CSPersonDispositifsType::class
);
}
#[Route(path: '{_locale}/person/job/{person}/personal_situation', name: 'chill_crud_job_personal_situation_view')]
public function personalSituationView(Request $request, $person): Response
{
return $this->viewAction('ps_situation_view', $request, $person);
}
#[Route(path: '{_locale}/person/job/{person}/dispositifs', name: 'chill_crud_job_dispositifs_view')]
public function dispositifsView(Request $request, $person): Response
{
return $this->viewAction('dispositifs_view', $request, $person);
}
protected function generateRedirectOnCreateRoute($action, Request $request, $entity): string
{
$route = '';
switch ($action) {
case 'ps_situation_view':
$route = 'chill_crud_job_personal_situation_edit';
break;
case 'dispositifs_view':
$route = 'chill_crud_job_dispositifs_edit';
break;
default:
parent::generateRedirectOnCreateRoute($action, $request, $entity);
}
return $this->generateUrl($route, ['id' => $entity->getPerson()->getId()]);
}
protected function checkACL($action, $entity): void
{
match ($action) {
'ps_situation_edit', 'dispositifs_edit' => $this->denyAccessUnlessGranted(
PersonVoter::UPDATE,
$entity->getPerson()
),
'ps_situation_view', 'dispositifs_view' => $this->denyAccessUnlessGranted(
PersonVoter::SEE,
$entity->getPerson()
),
default => parent::checkACL($action, $entity),
};
}
protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request): ?Response
{
return match ($action) {
'ps_situation_edit' => $this->redirectToRoute(
'chill_crud_'.$this->getCrudName().'_personal_situation_view',
['person' => $entity->getId()]
),
'dispositifs_edit' => $this->redirectToRoute(
'chill_crud_'.$this->getCrudName().'_dispositifs_view',
['person' => $entity->getId()]
),
default => null,
};
}
protected function getTemplateFor($action, $entity, Request $request): string
{
return match ($action) {
'ps_situation_edit' => '@ChillJob/CSPerson/personal_situation_edit.html.twig',
'dispositifs_edit' => '@ChillJob/CSPerson/dispositifs_edit.html.twig',
'ps_situation_view' => '@ChillJob/CSPerson/personal_situation_view.html.twig',
'dispositifs_view' => '@ChillJob/CSPerson/dispositifs_view.html.twig',
default => parent::getTemplateFor($action, $entity, $request),
};
}
protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface
{
switch ($action) {
case 'ps_situation_edit':
case 'dispositifs_edit':
$form = $this->createForm($formClass, $entity, \array_merge(
$formOptions,
['center' => $entity->getPerson()->getCenter()]
));
$this->customizeForm($action, $form);
return $form;
default:
return parent::createFormFor($action, $entity, $formClass, $formOptions);
}
}
protected function generateLabelForButton($action, $formName, $form): string
{
switch ($action) {
case 'ps_situation_edit':
case 'dispositifs_edit':
if ('submit' === $formName) {
return 'Enregistrer';
}
throw new \LogicException("this formName is not supported: {$formName}");
break;
default:
return 'Enregistrer';
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\HttpFoundation\Response;
use Chill\JobBundle\Entity\Frein;
use Chill\JobBundle\Entity\CV;
use Chill\JobBundle\Entity\Immersion;
use Chill\JobBundle\Entity\ProjetProfessionnel;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\JobBundle\Security\Authorization\JobVoter;
class CSReportController extends AbstractController
{
public function __construct(private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry) {}
#[Route(path: '{_locale}/person/job/{person}/report', name: 'chill_job_report_index')]
public function index(Person $person): Response
{
$this->denyAccessUnlessGranted(PersonVoter::SEE, $person, 'The access to '
.'person is denied');
$reports = $this->getReports($person);
return $this->render('@ChillJob/Report/index.html.twig', \array_merge([
'person' => $person,
], $reports));
}
protected function getReports(Person $person): array
{
$results = [];
$kinds = [];
if ($this->isGranted(JobVoter::REPORT_CV, $person)) {
$kinds['cvs'] = CV::class;
}
if ($this->isGranted(JobVoter::REPORT_NEW, $person)) {
$kinds = \array_merge($kinds, [
'cvs' => CV::class,
'freins' => Frein::class,
'immersions' => Immersion::class,
'projet_professionnels' => ProjetProfessionnel::class,
]);
}
foreach ($kinds as $key => $className) {
$ordering = match ($key) {
'immersions' => ['debutDate' => 'DESC'],
default => ['reportDate' => 'DESC'],
};
$results[$key] = $this->managerRegistry->getManager()
->getRepository($className)
->findBy(['person' => $person], $ordering);
}
return $results;
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle\Controller;
use Chill\PersonBundle\CRUD\Controller\EntityPersonCRUDController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* CRUD Controller for reports (Frein, ...).
*/
class CVCrudController extends EntityPersonCRUDController
{
protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request): ?Response
{
$next = $request->request->get('submit', 'save-and-close');
return match ($next) {
'save-and-close', 'delete-and-close' => $this->redirectToRoute('chill_job_report_index', [
'person' => $entity->getPerson()->getId(),
]),
default => parent::onBeforeRedirectAfterSubmission($action, $entity, $form, $request),
};
}
protected function duplicateEntity(string $action, Request $request)
{
if ('cv' === $this->getCrudName()) {
$id = $request->query->get('duplicate_id', 0);
/** @var \Chill\JobBundle\Entity\CV $cv */
$cv = $this->getEntity($action, $id, $request);
$em = $this->managerRegistry->getManager();
$em->detach($cv);
foreach ($cv->getExperiences() as $experience) {
$cv->removeExperience($experience);
$em->detach($experience);
$cv->addExperience($experience);
}
foreach ($cv->getFormations() as $formation) {
$cv->removeFormation($formation);
$em->detach($formation);
$cv->addFormation($formation);
}
return $cv;
}
return parent::duplicateEntity($action, $request);
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle\Controller;
use Chill\PersonBundle\CRUD\Controller\EntityPersonCRUDController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* CRUD Controller for reports (Frein, ...).
*/
class FreinCrudController extends EntityPersonCRUDController
{
protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request): ?Response
{
$next = $request->request->get('submit', 'save-and-close');
return match ($next) {
'save-and-close', 'delete-and-close' => $this->redirectToRoute('chill_job_report_index', [
'person' => $entity->getPerson()->getId(),
]),
default => parent::onBeforeRedirectAfterSubmission($action, $entity, $form, $request),
};
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle\Controller;
use Chill\PersonBundle\CRUD\Controller\EntityPersonCRUDController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Chill\JobBundle\Entity\Immersion;
use Symfony\Component\HttpFoundation\Response;
/**
* CRUD Controller for reports (Frein, ...).
*/
class ImmersionCrudController extends EntityPersonCRUDController
{
protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request): ?Response
{
$next = $request->request->get('submit', 'save-and-close');
return match ($next) {
'save-and-close', 'delete-and-close' => $this->redirectToRoute('chill_job_report_index', [
'person' => $entity->getPerson()->getId(),
]),
default => parent::onBeforeRedirectAfterSubmission($action, $entity, $form, $request),
};
}
protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface
{
if ($entity instanceof Immersion) {
if ('edit' === $action || 'new' === $action) {
return parent::createFormFor($action, $entity, $formClass, [
'center' => $entity->getPerson()->getCenter(),
]);
}
if ('bilan' === $action) {
return parent::createFormFor($action, $entity, $formClass, [
'center' => $entity->getPerson()->getCenter(),
'step' => 'bilan',
]);
}
if ('delete' === $action) {
return parent::createFormFor($action, $entity, $formClass, $formOptions);
}
throw new \LogicException("this step {$action} is not supported");
}
return parent::createFormFor($action, $entity, $formClass, $formOptions);
}
protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request)
{
// for immersion / edit-bilan action
if ('bilan' === $action) {
/* @var $entity Immersion */
$entity->setIsBilanFullfilled(true);
}
parent::onPreFlush($action, $entity, $form, $request);
}
/**
* Edit immersion bilan.
*
* @param int $id
*/
public function bilan(Request $request, $id): Response
{
return $this->formEditAction('bilan', $request, $id);
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle\Controller;
use Chill\PersonBundle\CRUD\Controller\EntityPersonCRUDController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* CRUD Controller for reports (Frein, ...).
*/
class ProjetProfessionnelCrudController extends EntityPersonCRUDController
{
protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request): ?Response
{
$next = $request->request->get('submit', 'save-and-close');
return match ($next) {
'save-and-close', 'delete-and-close' => $this->redirectToRoute('chill_job_report_index', [
'person' => $entity->getPerson()->getId(),
]),
default => parent::onBeforeRedirectAfterSubmission($action, $entity, $form, $request),
};
}
protected function duplicateEntity(string $action, Request $request)
{
if ('projet_prof' === $this->getCrudName()) {
$id = $request->query->get('duplicate_id', 0);
/** @var \Chill\JobBundle\Entity\ProjetProfessionnel $original */
$original = $this->getEntity($action, $id, $request);
$new = parent::duplicateEntity($action, $request);
foreach ($original->getSouhait() as $s) {
$new->addSouhait($s);
}
foreach ($original->getValide() as $s) {
$new->addValide($s);
}
return $new;
}
return parent::duplicateEntity($action, $request);
}
}

View File

@@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\JobBundle\DependencyInjection;
use Chill\JobBundle\Controller\CSPersonController;
use Chill\JobBundle\Controller\CVCrudController;
use Chill\JobBundle\Controller\FreinCrudController;
use Chill\JobBundle\Controller\ImmersionCrudController;
use Chill\JobBundle\Controller\ProjetProfessionnelCrudController;
use Chill\JobBundle\Entity\CSPerson;
use Chill\JobBundle\Entity\CV;
use Chill\JobBundle\Entity\Frein;
use Chill\JobBundle\Entity\Immersion;
use Chill\JobBundle\Entity\ProjetProfessionnel;
use Chill\JobBundle\Form\CVType;
use Chill\JobBundle\Form\FreinType;
use Chill\JobBundle\Form\ImmersionType;
use Chill\JobBundle\Form\ProjetProfessionnelType;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration.
*
* @see http://symfony.com/doc/current/cookbook/bundles/extension.html
*/
class ChillJobExtension extends Extension implements PrependExtensionInterface
{
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$loader->load('services/3party_type.yml');
$loader->load('services/controller.yml');
$loader->load('services/form.yml');
$loader->load('services/export.yml');
$loader->load('services/menu.yml');
$loader->load('services/security.yml');
}
public function prepend(ContainerBuilder $container): void
{
$this->prependRoute($container);
$this->prependCruds($container);
}
protected function prependCruds(ContainerBuilder $container)
{
$container->prependExtensionConfig('chill_main', [
'cruds' => [
[
'class' => CSPerson::class,
'controller' => CSPersonController::class,
'name' => 'job',
'base_role' => 'ROLE_USER',
'base_path' => '/person/job/',
],
[
'class' => CV::class,
'controller' => CVCrudController::class,
'name' => 'cscv',
'base_role' => 'ROLE_USER',
'base_path' => '/person/report/cv',
'form_class' => CVType::class,
'actions' => [
'view' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/CV/view.html.twig',
],
'new' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/CV/new.html.twig',
],
'edit' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/CV/edit.html.twig',
],
'delete' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Report/delete.html.twig',
],
],
],
[
'class' => ProjetProfessionnel::class,
'controller' => ProjetProfessionnelCrudController::class,
'name' => 'projet_prof',
'base_role' => 'ROLE_USER',
'base_path' => '/person/report/projet-professionnel',
'form_class' => ProjetProfessionnelType::class,
'actions' => [
'view' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/ProjetProfessionnel/view.html.twig',
],
'new' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/ProjetProfessionnel/new.html.twig',
],
'edit' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/ProjetProfessionnel/edit.html.twig',
],
'delete' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Report/delete.html.twig',
],
],
],
[
'class' => Frein::class,
'controller' => FreinCrudController::class,
'name' => 'csfrein',
'base_role' => 'ROLE_USER',
'base_path' => '/person/report/frein',
'form_class' => FreinType::class,
'actions' => [
'view' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Frein/view.html.twig',
],
'new' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Frein/new.html.twig',
],
'edit' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Frein/edit.html.twig',
],
'delete' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Report/delete.html.twig',
],
],
],
[
'class' => Immersion::class,
'controller' => ImmersionCrudController::class,
'name' => 'immersion',
'base_role' => 'ROLE_USER',
'base_path' => '/person/report/immersion',
'form_class' => ImmersionType::class,
'actions' => [
'view' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Immersion/view.html.twig',
],
'new' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Immersion/new.html.twig',
],
'bilan' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Immersion/edit-bilan.html.twig',
],
'edit' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Immersion/edit.html.twig',
],
'delete' => [
'role' => 'ROLE_USER',
'template' => '@ChillJob/Report/delete.html.twig',
],
],
],
],
]);
}
protected function prependRoute(ContainerBuilder $container): void
{
// declare routes for job bundle
$container->prependExtensionConfig('chill_main', [
'routing' => [
'resources' => [
'@ChillJobBundle/Resources/config/routing.yml',
],
],
]);
}
}

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