mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-25 16:14:59 +00:00
Compare commits
9 Commits
v3.0.0-RC1
...
signature-
Author | SHA1 | Date | |
---|---|---|---|
7aa63a25a7
|
|||
610239930b
|
|||
b65e2c62c4 | |||
89f5231649
|
|||
99818c211d
|
|||
a9f0059743
|
|||
5bc542a567
|
|||
c8ccce83fd
|
|||
4a229ebf6b |
5
.changes/unreleased/Feature-20231212-154841.yaml
Normal file
5
.changes/unreleased/Feature-20231212-154841.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: '[DX] move async-upload-bundle features into chill-bundles'
|
||||||
|
time: 2023-12-12T15:48:41.954970271+01:00
|
||||||
|
custom:
|
||||||
|
Issue: "221"
|
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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: ""
|
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: |
|
||||||
|
Upgrade CKEditor and refactor configuration with use of typescript
|
||||||
|
time: 2024-05-31T19:02:42.776662753+02:00
|
||||||
|
custom:
|
||||||
|
Issue: ""
|
8
.changes/unreleased/Feature-20240614-153236.yaml
Normal file
8
.changes/unreleased/Feature-20240614-153236.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: |-
|
||||||
|
Electronic signature
|
||||||
|
|
||||||
|
Implementation of the electronic signature for documents within chill.
|
||||||
|
time: 2024-06-14T15:32:36.875891692+02:00
|
||||||
|
custom:
|
||||||
|
Issue: ""
|
6
.changes/unreleased/Fixed-20240410-103736.yaml
Normal file
6
.changes/unreleased/Fixed-20240410-103736.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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: ""
|
@@ -1,21 +0,0 @@
|
|||||||
## v2.20.0 - 2024-06-05
|
|
||||||
### Fixed
|
|
||||||
* ([#170](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/170)) Display agents traitants instead of accompanying period referrer in export list social actions.
|
|
||||||
* Added translations for choices of durations (> 5 hours)
|
|
||||||
### Feature
|
|
||||||
* ([#145](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/145)) Allow to open documents in LibreOffice locally (need configuration within security);
|
|
||||||
|
|
||||||
This endpoint should be added to make the endpoint works properly:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
security:
|
|
||||||
firewalls:
|
|
||||||
dav:
|
|
||||||
pattern: ^/dav
|
|
||||||
provider: chain_provider
|
|
||||||
stateless: true
|
|
||||||
guard:
|
|
||||||
authenticators:
|
|
||||||
- Chill\DocStoreBundle\Security\Guard\JWTOnDavUrlAuthenticator
|
|
||||||
|
|
||||||
```
|
|
@@ -1,3 +0,0 @@
|
|||||||
## v2.20.1 - 2024-06-05
|
|
||||||
### Fixed
|
|
||||||
* Do not allow StoredObjectCreated for edit and convert buttons
|
|
@@ -1,31 +0,0 @@
|
|||||||
## 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
|
|
||||||
* ([#122](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/122)) Improve the list of calendar in the search results: make all calendar clicable, and display a list of calendars
|
|
||||||
* ([#282](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/282)) [export] add start date and end date on filters "filter course by referrer job" and "filter course by referrer scope"
|
|
||||||
* ([#282](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/282)) [export] the aggregator "Group by referrer" now accept a date range.
|
|
||||||
* ([#282](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/282)) [export] add date range on "group course by referrer's scope"
|
|
||||||
* ([#282](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/282)) [export] add date range on "group course by referrer's jobs"
|
|
||||||
* ([#168](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/168) In the UX, display user job and service at the time when he performs an action:
|
|
||||||
now, the job and service is shown:
|
|
||||||
* at the activity's date,
|
|
||||||
* at the appointment's date,
|
|
||||||
* when the user is marked as referrer for an accompanying period work,
|
|
||||||
* when the user apply a transition in a workflow,
|
|
||||||
* when the user updates or creates "something" ("created/updated by ... at ..."),
|
|
||||||
* or when he wrote a comment,
|
|
||||||
* …
|
|
||||||
|
|
||||||
### Traduction francophone
|
|
||||||
* Ajout d'un menu "flash" dans les résultats de recherche, pour créer un rendez-vous ou un échange dans un parcours depuis les résultats de recherche;
|
|
||||||
* Améliore la liste des rendez-vous dans les résultats de recherche: les rendez-vous sont cliquables;
|
|
||||||
* [exports] Ajout d'intervalles de dates pour des filtres et regroupements des parcours par référent, métier du référent, service du référent;
|
|
||||||
* Affiche le métier et le service des utilisateurs à la date à laquelle il a exécuté une action. Le métier et le service est affiché:
|
|
||||||
* à la date d'un échange,
|
|
||||||
* au jour d'un rendez-vous,
|
|
||||||
* quand l'utilisateur est devenu référent d'un parcours d'accompagnement,
|
|
||||||
* quand il a appliqué une transition sur un workflow,
|
|
||||||
* quand il a mise à jour ou créé une fiche, dans les mentions "créé / mise à jour par ..., le ...",
|
|
||||||
* quand il a mis à jour un commentaire,
|
|
||||||
* …
|
|
||||||
|
|
@@ -1,6 +0,0 @@
|
|||||||
## 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.
|
|
@@ -1,5 +0,0 @@
|
|||||||
## 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
|
|
@@ -1,3 +0,0 @@
|
|||||||
## v2.22.2 - 2024-07-03
|
|
||||||
### Fixed
|
|
||||||
* Remove scope required for event participation stats
|
|
@@ -1,11 +0,0 @@
|
|||||||
## 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
|
|
87
CHANGELOG.md
87
CHANGELOG.md
@@ -6,93 +6,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
|||||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||||
|
|
||||||
|
|
||||||
## 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
|
|
||||||
* ([#122](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/122)) Improve the list of calendar in the search results: make all calendar clicable, and display a list of calendars
|
|
||||||
* ([#282](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/282)) [export] add start date and end date on filters "filter course by referrer job" and "filter course by referrer scope"
|
|
||||||
* ([#282](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/282)) [export] the aggregator "Group by referrer" now accept a date range.
|
|
||||||
* ([#282](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/282)) [export] add date range on "group course by referrer's scope"
|
|
||||||
* ([#282](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/282)) [export] add date range on "group course by referrer's jobs"
|
|
||||||
* ([#168](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/168) In the UX, display user job and service at the time when he performs an action:
|
|
||||||
now, the job and service is shown:
|
|
||||||
* at the activity's date,
|
|
||||||
* at the appointment's date,
|
|
||||||
* when the user is marked as referrer for an accompanying period work,
|
|
||||||
* when the user apply a transition in a workflow,
|
|
||||||
* when the user updates or creates "something" ("created/updated by ... at ..."),
|
|
||||||
* or when he wrote a comment,
|
|
||||||
* …
|
|
||||||
|
|
||||||
### Traduction francophone
|
|
||||||
* Ajout d'un menu "flash" dans les résultats de recherche, pour créer un rendez-vous ou un échange dans un parcours depuis les résultats de recherche;
|
|
||||||
* Améliore la liste des rendez-vous dans les résultats de recherche: les rendez-vous sont cliquables;
|
|
||||||
* [exports] Ajout d'intervalles de dates pour des filtres et regroupements des parcours par référent, métier du référent, service du référent;
|
|
||||||
* Affiche le métier et le service des utilisateurs à la date à laquelle il a exécuté une action. Le métier et le service est affiché:
|
|
||||||
* à la date d'un échange,
|
|
||||||
* au jour d'un rendez-vous,
|
|
||||||
* quand l'utilisateur est devenu référent d'un parcours d'accompagnement,
|
|
||||||
* quand il a appliqué une transition sur un workflow,
|
|
||||||
* quand il a mise à jour ou créé une fiche, dans les mentions "créé / mise à jour par ..., le ...",
|
|
||||||
* quand il a mis à jour un commentaire,
|
|
||||||
* …
|
|
||||||
|
|
||||||
|
|
||||||
## v2.20.1 - 2024-06-05
|
|
||||||
### Fixed
|
|
||||||
* Do not allow StoredObjectCreated for edit and convert buttons
|
|
||||||
|
|
||||||
## v2.20.0 - 2024-06-05
|
|
||||||
### Fixed
|
|
||||||
* ([#170](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/170)) Display agents traitants instead of accompanying period referrer in export list social actions.
|
|
||||||
* Added translations for choices of durations (> 5 hours)
|
|
||||||
### Feature
|
|
||||||
* ([#145](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/145)) Allow to open documents in LibreOffice locally (need configuration within security);
|
|
||||||
|
|
||||||
This endpoint should be added to make the endpoint works properly:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
security:
|
|
||||||
firewalls:
|
|
||||||
dav:
|
|
||||||
pattern: ^/dav
|
|
||||||
provider: chain_provider
|
|
||||||
stateless: true
|
|
||||||
guard:
|
|
||||||
authenticators:
|
|
||||||
- Chill\DocStoreBundle\Security\Guard\JWTOnDavUrlAuthenticator
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## v2.19.0 - 2024-05-14
|
## v2.19.0 - 2024-05-14
|
||||||
### Feature
|
### Feature
|
||||||
* ([#197](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/197)) Make the script which subscribe to microsoft calendars changes more tolerant to errors or missing configuration on the microsoft side
|
* ([#197](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/197)) Make the script which subscribe to microsoft calendars changes more tolerant to errors or missing configuration on the microsoft side
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||||
"doctrine/orm": "^2.13.0",
|
"doctrine/orm": "^2.13.0",
|
||||||
"erusev/parsedown": "^1.7",
|
"erusev/parsedown": "^1.7",
|
||||||
|
"graylog2/gelf-php": "^1.5",
|
||||||
"knplabs/knp-menu-bundle": "^3.0",
|
"knplabs/knp-menu-bundle": "^3.0",
|
||||||
"knplabs/knp-time-bundle": "^1.12",
|
"knplabs/knp-time-bundle": "^1.12",
|
||||||
"knpuniversity/oauth2-client-bundle": "^2.10",
|
"knpuniversity/oauth2-client-bundle": "^2.10",
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
"phpoffice/phpspreadsheet": "^1.16",
|
"phpoffice/phpspreadsheet": "^1.16",
|
||||||
"ramsey/uuid-doctrine": "^1.7",
|
"ramsey/uuid-doctrine": "^1.7",
|
||||||
"sensio/framework-extra-bundle": "^5.5",
|
"sensio/framework-extra-bundle": "^5.5",
|
||||||
|
"smalot/pdfparser": "^2.10",
|
||||||
"spomky-labs/base64url": "^2.0",
|
"spomky-labs/base64url": "^2.0",
|
||||||
"symfony/asset": "^5.4",
|
"symfony/asset": "^5.4",
|
||||||
"symfony/browser-kit": "^5.4",
|
"symfony/browser-kit": "^5.4",
|
||||||
@@ -91,12 +93,11 @@
|
|||||||
"phpstan/phpstan": "^1.9",
|
"phpstan/phpstan": "^1.9",
|
||||||
"phpstan/phpstan-deprecation-rules": "^1.1",
|
"phpstan/phpstan-deprecation-rules": "^1.1",
|
||||||
"phpstan/phpstan-strict-rules": "^1.0",
|
"phpstan/phpstan-strict-rules": "^1.0",
|
||||||
"phpunit/phpunit": "^10.5.24",
|
"phpunit/phpunit": ">= 7.5",
|
||||||
"rector/rector": "^1.1.0",
|
"rector/rector": "^1.1.0",
|
||||||
"symfony/debug-bundle": "^5.4",
|
"symfony/debug-bundle": "^5.4",
|
||||||
"symfony/dotenv": "^5.4",
|
"symfony/dotenv": "^5.4",
|
||||||
"symfony/maker-bundle": "^1.20",
|
"symfony/maker-bundle": "^1.20",
|
||||||
"symfony/phpunit-bridge": "^7.1",
|
|
||||||
"symfony/runtime": "^5.4",
|
"symfony/runtime": "^5.4",
|
||||||
"symfony/stopwatch": "^5.4",
|
"symfony/stopwatch": "^5.4",
|
||||||
"symfony/var-dumper": "^5.4"
|
"symfony/var-dumper": "^5.4"
|
||||||
@@ -114,8 +115,6 @@
|
|||||||
"Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle",
|
"Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle",
|
||||||
"Chill\\DocStoreBundle\\": "src/Bundle/ChillDocStoreBundle",
|
"Chill\\DocStoreBundle\\": "src/Bundle/ChillDocStoreBundle",
|
||||||
"Chill\\EventBundle\\": "src/Bundle/ChillEventBundle",
|
"Chill\\EventBundle\\": "src/Bundle/ChillEventBundle",
|
||||||
"Chill\\FranceTravailApiBundle\\": "src/Bundle/ChillFranceTravailApiBundle/src",
|
|
||||||
"Chill\\JobBundle\\": "src/Bundle/ChillJobBundle/src",
|
|
||||||
"Chill\\MainBundle\\": "src/Bundle/ChillMainBundle",
|
"Chill\\MainBundle\\": "src/Bundle/ChillMainBundle",
|
||||||
"Chill\\PersonBundle\\": "src/Bundle/ChillPersonBundle",
|
"Chill\\PersonBundle\\": "src/Bundle/ChillPersonBundle",
|
||||||
"Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle",
|
"Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle",
|
||||||
|
@@ -21,7 +21,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||||||
class BirthdateFilter implements ExportElementValidatedInterface, FilterInterface
|
class BirthdateFilter implements ExportElementValidatedInterface, FilterInterface
|
||||||
{
|
{
|
||||||
// add specific role for this filter
|
// add specific role for this filter
|
||||||
public function addRole(): ?string
|
public function addRole()
|
||||||
{
|
{
|
||||||
// we do not need any new role for this filter, so we return null
|
// we do not need any new role for this filter, so we return null
|
||||||
return null;
|
return null;
|
||||||
|
@@ -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
|
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
|
# install chill and some dependencies
|
||||||
# TODO fix the suffix "alpha1" and replace by ^3.0.0 when version 3.0.0 will be released
|
# 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-RC3 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev
|
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
|
||||||
|
|
||||||
We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database.
|
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).
|
You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually).
|
||||||
@@ -95,7 +95,7 @@ custom developments. But most of the time, this should be fine.
|
|||||||
|
|
||||||
You have to configure some local variables, which are described in the :code:`.env` file. The secrets should not be stored
|
You have to configure some local variables, which are described in the :code:`.env` file. The secrets should not be stored
|
||||||
in this :code:`.env` file, but instead using the `secrets management tool <https://symfony.com/doc/current/configuration/secrets.html>`_
|
in this :code:`.env` file, but instead using the `secrets management tool <https://symfony.com/doc/current/configuration/secrets.html>`_
|
||||||
or in the :code:`.env.local` file, which should not be committed to the git repository.
|
or in the :code:`.env.local` file, which should not be commited to the git repository.
|
||||||
|
|
||||||
You do not need to set variables for the smtp server, redis server and relatorio server, as they are generated automatically
|
You do not need to set variables for the smtp server, redis server and relatorio server, as they are generated automatically
|
||||||
by the symfony server, from the docker compose services.
|
by the symfony server, from the docker compose services.
|
||||||
@@ -110,15 +110,10 @@ you can either:
|
|||||||
.. code-block:: env
|
.. code-block:: env
|
||||||
|
|
||||||
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
|
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,
|
- 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).
|
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.
|
|
||||||
|
|
||||||
Prepare migrations and other tools
|
Prepare migrations and other tools
|
||||||
**********************************
|
**********************************
|
||||||
|
|
||||||
@@ -135,8 +130,6 @@ To continue the installation process, you will have to run migrations:
|
|||||||
symfony console messenger:setup-transports
|
symfony console messenger:setup-transports
|
||||||
# prepare some views
|
# prepare some views
|
||||||
symfony console chill:db:sync-views
|
symfony console chill:db:sync-views
|
||||||
# generate jwt token, required for some api features (webdav access, ...)
|
|
||||||
symfony console lexik:jwt:generate-keypair
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
@@ -171,7 +164,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:
|
You will require some bundles to have the following development tools:
|
||||||
|
|
||||||
- add fixtures
|
- add fixtures
|
||||||
- add profiler and debug bundle
|
- add profiler and var-dumper to debug
|
||||||
|
|
||||||
Install fixtures
|
Install fixtures
|
||||||
****************
|
****************
|
||||||
@@ -186,7 +179,7 @@ Install fixtures
|
|||||||
This will generate user accounts, centers, and some basic configuration.
|
This will generate user accounts, centers, and some basic configuration.
|
||||||
|
|
||||||
The accounts created are: :code:`center a_social`, :code:`center b_social`, :code:`center a_direction`, ... The full list is
|
The accounts created are: :code:`center a_social`, :code:`center b_social`, :code:`center a_direction`, ... The full list is
|
||||||
visible in the "users" table: :code:`docker compose exec database psql -U app -c "SELECT username FROM users"`.
|
visibile in the "users" table: :code:`docker compose exec database psql -U app -c "SELECT username FROM users"`.
|
||||||
|
|
||||||
The password is always :code:`password`.
|
The password is always :code:`password`.
|
||||||
|
|
||||||
@@ -199,7 +192,7 @@ Add web profiler and debugger
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
symfony composer require --dev symfony/web-profiler-bundle symfony/debug-bundle
|
symfony composer require --dev symfony/web-profiler-bundle symfony/var-dumper
|
||||||
|
|
||||||
Working on chill bundles
|
Working on chill bundles
|
||||||
************************
|
************************
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"postcss-loader": "^7.0.2",
|
"postcss-loader": "^7.0.2",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"sass-loader": "^14.0.0",
|
"sass-loader": "^13.0.0",
|
||||||
"select2": "^4.0.13",
|
"select2": "^4.0.13",
|
||||||
"select2-bootstrap-theme": "0.1.0-beta.10",
|
"select2-bootstrap-theme": "0.1.0-beta.10",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
@@ -46,11 +46,9 @@
|
|||||||
"@fullcalendar/vue3": "^6.1.4",
|
"@fullcalendar/vue3": "^6.1.4",
|
||||||
"@popperjs/core": "^2.9.2",
|
"@popperjs/core": "^2.9.2",
|
||||||
"@types/leaflet": "^1.9.3",
|
"@types/leaflet": "^1.9.3",
|
||||||
"@types/dompurify": "^3.0.5",
|
|
||||||
"dropzone": "^5.7.6",
|
"dropzone": "^5.7.6",
|
||||||
"es6-promise": "^4.2.8",
|
"es6-promise": "^4.2.8",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"marked": "^12.0.2",
|
|
||||||
"masonry-layout": "^4.2.2",
|
"masonry-layout": "^4.2.2",
|
||||||
"mime": "^4.0.0",
|
"mime": "^4.0.0",
|
||||||
"swagger-ui": "^4.15.5",
|
"swagger-ui": "^4.15.5",
|
||||||
@@ -58,7 +56,7 @@
|
|||||||
"vue": "^3.2.37",
|
"vue": "^3.2.37",
|
||||||
"vue-i18n": "^9.1.6",
|
"vue-i18n": "^9.1.6",
|
||||||
"vue-multiselect": "3.0.0-alpha.2",
|
"vue-multiselect": "3.0.0-alpha.2",
|
||||||
"vue-toast-notification": "^3.1.2",
|
"vue-toast-notification": "^2.0",
|
||||||
"vuex": "^4.0.0"
|
"vuex": "^4.0.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
@@ -1,29 +1,34 @@
|
|||||||
parameters:
|
parameters:
|
||||||
ignoreErrors:
|
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\\.$#"
|
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php
|
path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Property Chill\\\\CustomFieldsBundle\\\\Entity\\\\CustomField\\:\\:\\$required \\(false\\) does not accept bool\\.$#"
|
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillCustomFieldsBundle/Entity/CustomField.php
|
path: src/Bundle/ChillPersonBundle/Form/PersonType.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\\.$#"
|
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
|
||||||
count: 2
|
count: 1
|
||||||
path: src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php
|
path: src/Bundle/ChillMainBundle/Templating/ChillTwigRoutingHelper.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\\.$#"
|
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\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
path: src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php
|
path: src/Bundle/ChillMainBundle/Repository/NotificationRepository.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: "#^Foreach overwrites \\$key with its key variable\\.$#"
|
||||||
|
count: 1
|
||||||
|
path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Variable \\$participation might not be defined\\.$#"
|
message: "#^Variable \\$participation might not be defined\\.$#"
|
||||||
@@ -35,106 +40,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillEventBundle/Form/ChoiceLoader/EventChoiceLoader.php
|
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\\}\\.$#"
|
message: "#^Cannot unset offset '_token' on array\\{formatter\\: mixed, export\\: mixed, centers\\: mixed, alias\\: string\\}\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -160,31 +65,11 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillMainBundle/Form/ChoiceLoader/PostalCodeChoiceLoader.php
|
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\\.$#"
|
message: "#^Foreach overwrites \\$value with its value variable\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php
|
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\\.$#"
|
message: "#^Foreach overwrites \\$value with its value variable\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
17
rector.php
17
rector.php
@@ -28,9 +28,6 @@ return static function (RectorConfig $rectorConfig): void {
|
|||||||
|
|
||||||
// register a single rule
|
// register a single rule
|
||||||
$rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
|
$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
|
// part of the symfony 54 rules
|
||||||
$rectorConfig->rule(\Rector\Symfony\Symfony53\Rector\StaticPropertyFetch\KernelTestCaseContainerPropertyDeprecationRector::class);
|
$rectorConfig->rule(\Rector\Symfony\Symfony53\Rector\StaticPropertyFetch\KernelTestCaseContainerPropertyDeprecationRector::class);
|
||||||
@@ -39,14 +36,14 @@ return static function (RectorConfig $rectorConfig): void {
|
|||||||
|
|
||||||
//define sets of rules
|
//define sets of rules
|
||||||
$rectorConfig->sets([
|
$rectorConfig->sets([
|
||||||
LevelSetList::UP_TO_PHP_82,
|
\Rector\Symfony\Set\SymfonySetList::SYMFONY_50,
|
||||||
\Rector\Symfony\Set\SymfonySetList::SYMFONY_40,
|
\Rector\Symfony\Set\SymfonySetList::SYMFONY_50_TYPES,
|
||||||
\Rector\Symfony\Set\SymfonySetList::SYMFONY_41,
|
\Rector\Symfony\Set\SymfonySetList::SYMFONY_51,
|
||||||
\Rector\Symfony\Set\SymfonySetList::SYMFONY_42,
|
\Rector\Symfony\Set\SymfonySetList::SYMFONY_52,
|
||||||
\Rector\Symfony\Set\SymfonySetList::SYMFONY_43,
|
\Rector\Symfony\Set\SymfonySetList::SYMFONY_53,
|
||||||
\Rector\Symfony\Set\SymfonySetList::SYMFONY_44,
|
\Rector\Symfony\Set\SymfonySetList::SYMFONY_54,
|
||||||
\Rector\Doctrine\Set\DoctrineSetList::DOCTRINE_CODE_QUALITY,
|
\Rector\Doctrine\Set\DoctrineSetList::DOCTRINE_CODE_QUALITY,
|
||||||
\Rector\PHPUnit\Set\PHPUnitSetList::PHPUNIT_90,
|
\Rector\Doctrine\Set\DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$rectorConfig->ruleWithConfiguration(\Rector\Php80\Rector\Class_\AnnotationToAttributeRector::class, [
|
$rectorConfig->ruleWithConfiguration(\Rector\Php80\Rector\Class_\AnnotationToAttributeRector::class, [
|
||||||
|
@@ -99,10 +99,10 @@ final class ActivityController extends AbstractController
|
|||||||
|
|
||||||
$form = $this->createDeleteForm($activity->getId(), $person, $accompanyingPeriod);
|
$form = $this->createDeleteForm($activity->getId(), $person, $accompanyingPeriod);
|
||||||
|
|
||||||
if (Request::METHOD_POST === $request->getMethod()) {
|
if (Request::METHOD_DELETE === $request->getMethod()) {
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$this->logger->notice('An activity has been removed', [
|
$this->logger->notice('An activity has been removed', [
|
||||||
'by_user' => $this->getUser()->getUsername(),
|
'by_user' => $this->getUser()->getUsername(),
|
||||||
'activity_id' => $activity->getId(),
|
'activity_id' => $activity->getId(),
|
||||||
@@ -640,6 +640,7 @@ final class ActivityController extends AbstractController
|
|||||||
|
|
||||||
return $this->createFormBuilder()
|
return $this->createFormBuilder()
|
||||||
->setAction($this->generateUrl('chill_activity_activity_delete', $params))
|
->setAction($this->generateUrl('chill_activity_activity_delete', $params))
|
||||||
|
->setMethod('DELETE')
|
||||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||||
->getForm();
|
->getForm();
|
||||||
}
|
}
|
||||||
|
@@ -79,9 +79,11 @@ class ActivityReason
|
|||||||
/**
|
/**
|
||||||
* Set active.
|
* Set active.
|
||||||
*
|
*
|
||||||
|
* @param bool $active
|
||||||
|
*
|
||||||
* @return ActivityReason
|
* @return ActivityReason
|
||||||
*/
|
*/
|
||||||
public function setActive(bool $active)
|
public function setActive($active)
|
||||||
{
|
{
|
||||||
$this->active = $active;
|
$this->active = $active;
|
||||||
|
|
||||||
@@ -108,9 +110,11 @@ class ActivityReason
|
|||||||
/**
|
/**
|
||||||
* Set name.
|
* Set name.
|
||||||
*
|
*
|
||||||
|
* @param array $name
|
||||||
|
*
|
||||||
* @return ActivityReason
|
* @return ActivityReason
|
||||||
*/
|
*/
|
||||||
public function setName(array $name)
|
public function setName($name)
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
|
|
||||||
|
@@ -152,7 +152,7 @@ class ListActivityHelper
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->translator->trans((string) $value);
|
return $this->translator->trans($value);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -73,7 +73,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt
|
|||||||
|
|
||||||
$qb->andWhere(
|
$qb->andWhere(
|
||||||
$qb->expr()->exists(
|
$qb->expr()->exists(
|
||||||
'SELECT 1 FROM '.Activity::class." {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = activity.accompanyingPeriod"
|
'SELECT 1 FROM '.Activity::class." {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = acp"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -1,49 +0,0 @@
|
|||||||
<?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\ActivityBundle\Menu;
|
|
||||||
|
|
||||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
|
||||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
|
||||||
use Knp\Menu\MenuItem;
|
|
||||||
use Symfony\Component\Security\Core\Security;
|
|
||||||
|
|
||||||
final readonly class AccompanyingCourseQuickMenuBuilder implements LocalMenuBuilderInterface
|
|
||||||
{
|
|
||||||
public function __construct(private Security $security) {}
|
|
||||||
|
|
||||||
public static function getMenuIds(): array
|
|
||||||
{
|
|
||||||
return ['accompanying_course_quick_menu'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
|
||||||
{
|
|
||||||
/** @var \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingCourse */
|
|
||||||
$accompanyingCourse = $parameters['accompanying-course'];
|
|
||||||
|
|
||||||
if ($this->security->isGranted(ActivityVoter::CREATE, $accompanyingCourse)) {
|
|
||||||
$menu
|
|
||||||
->addChild('Create a new activity in accompanying course', [
|
|
||||||
'route' => 'chill_activity_activity_new',
|
|
||||||
'routeParameters' => [
|
|
||||||
// 'activityType_id' => '',
|
|
||||||
'accompanying_period_id' => $accompanyingCourse->getId(),
|
|
||||||
],
|
|
||||||
])
|
|
||||||
->setExtras([
|
|
||||||
'order' => 10,
|
|
||||||
'icon' => 'plus',
|
|
||||||
])
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -15,9 +15,9 @@ use Chill\ActivityBundle\Entity\ActivityType;
|
|||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
final readonly class ActivityTypeRepository implements ActivityTypeRepositoryInterface
|
final class ActivityTypeRepository implements ActivityTypeRepositoryInterface
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private readonly EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $em)
|
public function __construct(EntityManagerInterface $em)
|
||||||
{
|
{
|
||||||
|
@@ -68,7 +68,7 @@
|
|||||||
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
|
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
|
||||||
<div class="wl-col list">
|
<div class="wl-col list">
|
||||||
<p class="wl-item">
|
<p class="wl-item">
|
||||||
<span class="badge-user">{{ activity.user|chill_entity_render_box({'at_date': activity.date}) }}</span>
|
<span class="badge-user">{{ activity.user|chill_entity_render_box }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -87,7 +87,7 @@
|
|||||||
<li>
|
<li>
|
||||||
{% if bloc.type == 'user' %}
|
{% if bloc.type == 'user' %}
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }}
|
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ _self.insert_onthefly(bloc.type, item) }}
|
{{ _self.insert_onthefly(bloc.type, item) }}
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
<li>
|
<li>
|
||||||
{% if bloc.type == 'user' %}
|
{% if bloc.type == 'user' %}
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }}
|
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ _self.insert_onthefly(bloc.type, item) }}
|
{{ _self.insert_onthefly(bloc.type, item) }}
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
<span class="wl-item">
|
<span class="wl-item">
|
||||||
{% if bloc.type == 'user' %}
|
{% if bloc.type == 'user' %}
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }}
|
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
|
||||||
{%- if context == 'calendar_accompanyingCourse' or context == 'calendar_person' %}
|
{%- if context == 'calendar_accompanyingCourse' or context == 'calendar_person' %}
|
||||||
{% set invite = entity.inviteForUser(item) %}
|
{% set invite = entity.inviteForUser(item) %}
|
||||||
{% if invite is not null %}
|
{% if invite is not null %}
|
||||||
|
@@ -41,7 +41,7 @@
|
|||||||
{% if activity.user and t.userVisible %}
|
{% if activity.user and t.userVisible %}
|
||||||
<li>
|
<li>
|
||||||
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
||||||
<span class="badge-user">{{ activity.user|chill_entity_render_box({'at_date': activity.date}) }}</span>
|
<span class="badge-user">{{ activity.user|chill_entity_render_box }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@@ -37,7 +37,7 @@
|
|||||||
{%- if entity.user is not null %}
|
{%- if entity.user is not null %}
|
||||||
<dt class="inline">{{ 'Referrer'|trans|capitalize }}</dt>
|
<dt class="inline">{{ 'Referrer'|trans|capitalize }}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<span class="badge-user">{{ entity.user|chill_entity_render_box({'at_date': entity.date}) }}</span>
|
<span class="badge-user">{{ entity.user|chill_entity_render_box }}</span>
|
||||||
</dd>
|
</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@@ -145,7 +145,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
|
|||||||
throw new \RuntimeException('Could not determine context of activity.');
|
throw new \RuntimeException('Could not determine context of activity.');
|
||||||
}
|
}
|
||||||
} elseif ($subject instanceof AccompanyingPeriod) {
|
} elseif ($subject instanceof AccompanyingPeriod) {
|
||||||
if (AccompanyingPeriod::STEP_CLOSED === $subject->getStep() || AccompanyingPeriod::STEP_DRAFT === $subject->getStep()) {
|
if (AccompanyingPeriod::STEP_CLOSED === $subject->getStep()) {
|
||||||
if (\in_array($attribute, [self::UPDATE, self::CREATE, self::DELETE], true)) {
|
if (\in_array($attribute, [self::UPDATE, self::CREATE, self::DELETE], true)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@@ -77,18 +77,6 @@ Choose a type: Choisir un type
|
|||||||
4 hours: 4 heures
|
4 hours: 4 heures
|
||||||
4 hours 30: 4 heures 30
|
4 hours 30: 4 heures 30
|
||||||
5 hours: 5 heures
|
5 hours: 5 heures
|
||||||
5 hours 30: 5 heure 30
|
|
||||||
6 hours: 6 heures
|
|
||||||
6 hours 30: 6 heure 30
|
|
||||||
7 hours: 7 heures
|
|
||||||
7 hours 30: 7 heure 30
|
|
||||||
8 hours: 8 heures
|
|
||||||
8 hours 30: 8 heure 30
|
|
||||||
9 hours: 9 heures
|
|
||||||
9 hours 30: 9 heure 30
|
|
||||||
10 hours: 10 heures
|
|
||||||
11 hours: 11 heures
|
|
||||||
12 hours: 12 heures
|
|
||||||
Concerned groups: Parties concernées par l'échange
|
Concerned groups: Parties concernées par l'échange
|
||||||
Persons in accompanying course: Usagers du parcours
|
Persons in accompanying course: Usagers du parcours
|
||||||
Third persons: Tiers non-pro.
|
Third persons: Tiers non-pro.
|
||||||
@@ -222,7 +210,6 @@ Documents label: Libellé du champ Documents
|
|||||||
# activity type category admin
|
# activity type category admin
|
||||||
ActivityTypeCategory list: Liste des catégories des types d'échange
|
ActivityTypeCategory list: Liste des catégories des types d'échange
|
||||||
Create a new activity type category: Créer une nouvelle catégorie de type d'échange
|
Create a new activity type category: Créer une nouvelle catégorie de type d'échange
|
||||||
Create a new activity in accompanying course: Créer un échange dans le parcours
|
|
||||||
|
|
||||||
# activity delete
|
# activity delete
|
||||||
Remove activity: Supprimer un échange
|
Remove activity: Supprimer un échange
|
||||||
|
@@ -49,13 +49,13 @@
|
|||||||
<li>
|
<li>
|
||||||
<span>
|
<span>
|
||||||
<abbr class="referrer" title={{ 'Created by'|trans }}>{{ 'By'|trans }}:</abbr>
|
<abbr class="referrer" title={{ 'Created by'|trans }}>{{ 'By'|trans }}:</abbr>
|
||||||
<b>{{ entity.createdBy|chill_entity_render_box({'at_date': entity.date}) }}</b>
|
<b>{{ entity.createdBy|chill_entity_render_box }}</b>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span>
|
<span>
|
||||||
<abbr class="referrer" title={{ 'Created for'|trans }}>{{ 'For'|trans }}:</abbr>
|
<abbr class="referrer" title={{ 'Created for'|trans }}>{{ 'For'|trans }}:</abbr>
|
||||||
<b>{{ entity.agent|chill_entity_render_box({'at_date': entity.date}) }}</b>
|
<b>{{ entity.agent|chill_entity_render_box }}</b>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -18,10 +18,10 @@
|
|||||||
<dd>{{ entity.type|chill_entity_render_box }}</dd>
|
<dd>{{ entity.type|chill_entity_render_box }}</dd>
|
||||||
|
|
||||||
<dt class="inline">{{ 'Created by'|trans }}</dt>
|
<dt class="inline">{{ 'Created by'|trans }}</dt>
|
||||||
<dd>{{ entity.createdBy|chill_entity_render_box({'at_date': entity.date}) }}</dd>
|
<dd>{{ entity.createdBy }}</dd>
|
||||||
|
|
||||||
<dt class="inline">{{ 'Created for'|trans }}</dt>
|
<dt class="inline">{{ 'Created for'|trans }}</dt>
|
||||||
<dd>{{ entity.agent|chill_entity_render_box({'at_date': entity.date}) }}</dd>
|
<dd>{{ entity.agent }}</dd>
|
||||||
|
|
||||||
<dt class="inline">{{ 'Asideactivity location'|trans }}</dt>
|
<dt class="inline">{{ 'Asideactivity location'|trans }}</dt>
|
||||||
{%- if entity.location.name is defined -%}
|
{%- if entity.location.name is defined -%}
|
||||||
|
@@ -72,21 +72,21 @@ days: jours
|
|||||||
1 hour 30: 1 heure 30
|
1 hour 30: 1 heure 30
|
||||||
1 hour 45: 1 heure 45
|
1 hour 45: 1 heure 45
|
||||||
2 hours: 2 heures
|
2 hours: 2 heures
|
||||||
2 hours 30: 2 heures 30
|
2 hours 30: 2 heure 30
|
||||||
3 hours: 3 heures
|
3 hours: 3 heures
|
||||||
3 hours 30: 3 heures 30
|
3 hours 30: 3 heure 30
|
||||||
4 hours: 4 heures
|
4 hours: 4 heures
|
||||||
4 hours 30: 4 heures 30
|
4 hours 30: 4 heure 30
|
||||||
5 hours: 5 heures
|
5 hours: 5 heures
|
||||||
5 hours 30: 5 heures 30
|
5 hours 30: 5 heure 30
|
||||||
6 hours: 6 heures
|
6 hours: 6 heures
|
||||||
6 hours 30: 6 heures 30
|
6 hours 30: 6 heure 30
|
||||||
7 hours: 7 heures
|
7 hours: 7 heures
|
||||||
7 hours 30: 7 heures 30
|
7 hours 30: 7 heure 30
|
||||||
8 hours: 8 heures
|
8 hours: 8 heures
|
||||||
8 hours 30: 8 heures 30
|
8 hours 30: 8 heure 30
|
||||||
9 hours: 9 heures
|
9 hours: 9 heures
|
||||||
9 hours 30: 9 heures 30
|
9 hours 30: 9 heure 30
|
||||||
10 hours: 10 heures
|
10 hours: 10 heures
|
||||||
1/2 day: 1/2 jour
|
1/2 day: 1/2 jour
|
||||||
1 day: 1 jour
|
1 day: 1 jour
|
||||||
|
@@ -54,7 +54,7 @@ abstract class AbstractElementController extends AbstractController
|
|||||||
$indexPage = 'chill_budget_elements_household_index';
|
$indexPage = 'chill_budget_elements_household_index';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Request::METHOD_POST === $request->getMethod()) {
|
if (Request::METHOD_DELETE === $request->getMethod()) {
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
@@ -198,9 +198,10 @@ abstract class AbstractElementController extends AbstractController
|
|||||||
/**
|
/**
|
||||||
* Creates a form to delete a help request entity by id.
|
* Creates a form to delete a help request entity by id.
|
||||||
*/
|
*/
|
||||||
private function createDeleteForm(): \Symfony\Component\Form\FormInterface
|
private function createDeleteForm(): Form
|
||||||
{
|
{
|
||||||
return $this->createFormBuilder()
|
return $this->createFormBuilder()
|
||||||
|
->setMethod(Request::METHOD_DELETE)
|
||||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||||
->getForm();
|
->getForm();
|
||||||
}
|
}
|
||||||
|
@@ -100,7 +100,7 @@ class Charge extends AbstractElement implements HasCentersInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setHelp(?string $help)
|
public function setHelp($help)
|
||||||
{
|
{
|
||||||
$this->help = $help;
|
$this->help = $help;
|
||||||
|
|
||||||
|
@@ -15,9 +15,9 @@ use Chill\BudgetBundle\Entity\ChargeKind;
|
|||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
final readonly class ChargeKindRepository implements ChargeKindRepositoryInterface
|
final class ChargeKindRepository implements ChargeKindRepositoryInterface
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private readonly EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
|
@@ -15,9 +15,9 @@ use Chill\BudgetBundle\Entity\ResourceKind;
|
|||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
final readonly class ResourceKindRepository implements ResourceKindRepositoryInterface
|
final class ResourceKindRepository implements ResourceKindRepositoryInterface
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private readonly EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
|
@@ -84,7 +84,7 @@ class CalendarController extends AbstractController
|
|||||||
|
|
||||||
$form = $this->createDeleteForm($entity);
|
$form = $this->createDeleteForm($entity);
|
||||||
|
|
||||||
if (Request::METHOD_POST === $request->getMethod()) {
|
if (Request::METHOD_DELETE === $request->getMethod()) {
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isValid()) {
|
if ($form->isValid()) {
|
||||||
@@ -512,6 +512,7 @@ class CalendarController extends AbstractController
|
|||||||
{
|
{
|
||||||
return $this->createFormBuilder()
|
return $this->createFormBuilder()
|
||||||
->setAction($this->generateUrl('chill_calendar_calendar_delete', ['id' => $calendar->getId()]))
|
->setAction($this->generateUrl('chill_calendar_calendar_delete', ['id' => $calendar->getId()]))
|
||||||
|
->setMethod('DELETE')
|
||||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||||
->getForm();
|
->getForm();
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,7 @@ class RemoteCalendarConnectAzureController
|
|||||||
->redirect(['https://graph.microsoft.com/.default', 'offline_access'], []);
|
->redirect(['https://graph.microsoft.com/.default', 'offline_access'], []);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route(path: '/connect/azure/check', name: 'chill_calendar_remote_connect_azure_check', schemes: 'https')]
|
#[Route(path: '/connect/azure/check', name: 'chill_calendar_remote_connect_azure_check')]
|
||||||
public function connectAzureCheck(Request $request): Response
|
public function connectAzureCheck(Request $request): Response
|
||||||
{
|
{
|
||||||
/** @var Azure $client */
|
/** @var Azure $client */
|
||||||
|
@@ -440,16 +440,6 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente
|
|||||||
return $this->startDate;
|
return $this->startDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* get the date of the calendar.
|
|
||||||
*
|
|
||||||
* Useful for showing the date of the calendar event, required by twig in some places.
|
|
||||||
*/
|
|
||||||
public function getDate(): ?\DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->getStartDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStatus(): ?string
|
public function getStatus(): ?string
|
||||||
{
|
{
|
||||||
return $this->status;
|
return $this->status;
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
<?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\CalendarBundle\Menu;
|
|
||||||
|
|
||||||
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
|
||||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
|
||||||
use Knp\Menu\MenuItem;
|
|
||||||
use Symfony\Component\Security\Core\Security;
|
|
||||||
|
|
||||||
final readonly class AccompanyingCourseQuickMenuBuilder implements LocalMenuBuilderInterface
|
|
||||||
{
|
|
||||||
public function __construct(private Security $security) {}
|
|
||||||
|
|
||||||
public static function getMenuIds(): array
|
|
||||||
{
|
|
||||||
return ['accompanying_course_quick_menu'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
|
||||||
{
|
|
||||||
/** @var \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingCourse */
|
|
||||||
$accompanyingCourse = $parameters['accompanying-course'];
|
|
||||||
|
|
||||||
if ($this->security->isGranted(CalendarVoter::CREATE, $accompanyingCourse)) {
|
|
||||||
$menu
|
|
||||||
->addChild('Create a new calendar in accompanying course', [
|
|
||||||
'route' => 'chill_calendar_calendar_new',
|
|
||||||
'routeParameters' => [
|
|
||||||
'accompanying_period_id' => $accompanyingCourse->getId(),
|
|
||||||
],
|
|
||||||
])
|
|
||||||
->setExtras([
|
|
||||||
'order' => 20,
|
|
||||||
'icon' => 'plus',
|
|
||||||
])
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,2 +1 @@
|
|||||||
import './scss/badge.scss';
|
import './scss/badge.scss';
|
||||||
import './scss/calendar-list.scss';
|
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
ul.calendar-list {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
& > li {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
& > li:nth-child(n+2) {
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.calendar-list {
|
|
||||||
|
|
||||||
ul.calendar-list {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > a.calendar-list__global {
|
|
||||||
display: inline-block;;
|
|
||||||
padding: 0.2rem;
|
|
||||||
min-width: 2rem;
|
|
||||||
border: 1px solid var(--bs-chill-blue);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -55,7 +55,7 @@
|
|||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<ul class="list-content">
|
<ul class="list-content">
|
||||||
{% if calendar.mainUser is not empty %}
|
{% if calendar.mainUser is not empty %}
|
||||||
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
|
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
<span class="createdBy">
|
<span class="createdBy">
|
||||||
{{ 'Created by'|trans }}
|
{{ 'Created by'|trans }}
|
||||||
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
|
<b>{{ calendar.activity.createdBy|chill_entity_render_string }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
||||||
|
@@ -89,7 +89,7 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
|
|||||||
switch ($attribute) {
|
switch ($attribute) {
|
||||||
case self::SEE:
|
case self::SEE:
|
||||||
case self::CREATE:
|
case self::CREATE:
|
||||||
if (AccompanyingPeriod::STEP_DRAFT === $subject->getStep() || AccompanyingPeriod::STEP_CLOSED === $subject->getStep()) {
|
if (AccompanyingPeriod::STEP_DRAFT === $subject->getStep()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -26,7 +26,6 @@ The calendar item has been successfully removed.: Le rendez-vous a été supprim
|
|||||||
From the day: Du
|
From the day: Du
|
||||||
to the day: au
|
to the day: au
|
||||||
Transform to activity: Transformer en échange
|
Transform to activity: Transformer en échange
|
||||||
Create a new calendar in accompanying course: Créer un rendez-vous dans le parcours
|
|
||||||
Will send SMS: Un SMS de rappel sera envoyé
|
Will send SMS: Un SMS de rappel sera envoyé
|
||||||
Will not send SMS: Aucun SMS de rappel ne sera envoyé
|
Will not send SMS: Aucun SMS de rappel ne sera envoyé
|
||||||
SMS already sent: Un SMS a été envoyé
|
SMS already sent: Un SMS a été envoyé
|
||||||
|
@@ -172,9 +172,11 @@ class CustomField
|
|||||||
/**
|
/**
|
||||||
* Set active.
|
* Set active.
|
||||||
*
|
*
|
||||||
|
* @param bool $active
|
||||||
|
*
|
||||||
* @return CustomField
|
* @return CustomField
|
||||||
*/
|
*/
|
||||||
public function setActive(bool $active)
|
public function setActive($active)
|
||||||
{
|
{
|
||||||
$this->active = $active;
|
$this->active = $active;
|
||||||
|
|
||||||
@@ -222,16 +224,18 @@ class CustomField
|
|||||||
/**
|
/**
|
||||||
* Set order.
|
* Set order.
|
||||||
*
|
*
|
||||||
|
* @param float $order
|
||||||
|
*
|
||||||
* @return CustomField
|
* @return CustomField
|
||||||
*/
|
*/
|
||||||
public function setOrdering(?float $order)
|
public function setOrdering($order)
|
||||||
{
|
{
|
||||||
$this->ordering = $order;
|
$this->ordering = $order;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setRequired(bool $required)
|
public function setRequired($required)
|
||||||
{
|
{
|
||||||
$this->required = $required;
|
$this->required = $required;
|
||||||
|
|
||||||
@@ -241,7 +245,7 @@ class CustomField
|
|||||||
/**
|
/**
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setSlug(?string $slug)
|
public function setSlug($slug)
|
||||||
{
|
{
|
||||||
$this->slug = $slug;
|
$this->slug = $slug;
|
||||||
|
|
||||||
@@ -251,9 +255,11 @@ class CustomField
|
|||||||
/**
|
/**
|
||||||
* Set type.
|
* Set type.
|
||||||
*
|
*
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
* @return CustomField
|
* @return CustomField
|
||||||
*/
|
*/
|
||||||
public function setType(?string $type)
|
public function setType($type)
|
||||||
{
|
{
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
|
|
||||||
|
@@ -129,7 +129,7 @@ class Option
|
|||||||
/**
|
/**
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setActive(bool $active)
|
public function setActive($active)
|
||||||
{
|
{
|
||||||
$this->active = $active;
|
$this->active = $active;
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ class Option
|
|||||||
/**
|
/**
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setInternalKey(string $internal_key)
|
public function setInternalKey($internal_key)
|
||||||
{
|
{
|
||||||
$this->internalKey = $internal_key;
|
$this->internalKey = $internal_key;
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ class Option
|
|||||||
/**
|
/**
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setKey(?string $key)
|
public function setKey($key)
|
||||||
{
|
{
|
||||||
$this->key = $key;
|
$this->key = $key;
|
||||||
|
|
||||||
|
@@ -69,7 +69,7 @@ class CustomFieldsDefaultGroup
|
|||||||
*
|
*
|
||||||
* @return CustomFieldsDefaultGroup
|
* @return CustomFieldsDefaultGroup
|
||||||
*/
|
*/
|
||||||
public function setCustomFieldsGroup(?CustomFieldsGroup $customFieldsGroup)
|
public function setCustomFieldsGroup($customFieldsGroup)
|
||||||
{
|
{
|
||||||
$this->customFieldsGroup = $customFieldsGroup;
|
$this->customFieldsGroup = $customFieldsGroup;
|
||||||
|
|
||||||
@@ -79,9 +79,11 @@ class CustomFieldsDefaultGroup
|
|||||||
/**
|
/**
|
||||||
* Set entity.
|
* Set entity.
|
||||||
*
|
*
|
||||||
|
* @param string $entity
|
||||||
|
*
|
||||||
* @return CustomFieldsDefaultGroup
|
* @return CustomFieldsDefaultGroup
|
||||||
*/
|
*/
|
||||||
public function setEntity(?string $entity)
|
public function setEntity($entity)
|
||||||
{
|
{
|
||||||
$this->entity = $entity;
|
$this->entity = $entity;
|
||||||
|
|
||||||
|
@@ -165,9 +165,11 @@ class CustomFieldsGroup
|
|||||||
/**
|
/**
|
||||||
* Set entity.
|
* Set entity.
|
||||||
*
|
*
|
||||||
|
* @param string $entity
|
||||||
|
*
|
||||||
* @return CustomFieldsGroup
|
* @return CustomFieldsGroup
|
||||||
*/
|
*/
|
||||||
public function setEntity(?string $entity)
|
public function setEntity($entity)
|
||||||
{
|
{
|
||||||
$this->entity = $entity;
|
$this->entity = $entity;
|
||||||
|
|
||||||
|
@@ -21,14 +21,14 @@ use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
|||||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
final readonly class RelatorioDriver implements DriverInterface
|
final class RelatorioDriver implements DriverInterface
|
||||||
{
|
{
|
||||||
private string $url;
|
private readonly string $url;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private HttpClientInterface $client,
|
private readonly HttpClientInterface $client,
|
||||||
ParameterBagInterface $parameterBag,
|
ParameterBagInterface $parameterBag,
|
||||||
private LoggerInterface $logger
|
private readonly LoggerInterface $logger
|
||||||
) {
|
) {
|
||||||
$this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url'];
|
$this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url'];
|
||||||
}
|
}
|
||||||
|
@@ -16,11 +16,11 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
|
||||||
final readonly class DocGeneratorTemplateRepository implements DocGeneratorTemplateRepositoryInterface
|
final class DocGeneratorTemplateRepository implements DocGeneratorTemplateRepositoryInterface
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private readonly EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager, private RequestStack $requestStack)
|
public function __construct(EntityManagerInterface $entityManager, private readonly RequestStack $requestStack)
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(DocGeneratorTemplate::class);
|
$this->repository = $entityManager->getRepository(DocGeneratorTemplate::class);
|
||||||
}
|
}
|
||||||
|
@@ -1,65 +0,0 @@
|
|||||||
<?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\DocGeneratorBundle\Test;
|
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T of object
|
|
||||||
*/
|
|
||||||
abstract class DocGenNormalizerTestAbstract extends KernelTestCase
|
|
||||||
{
|
|
||||||
public function testNullValueHasSameKeysAsNull(): void
|
|
||||||
{
|
|
||||||
$normalizedObject = $this->getNormalizer()->normalize($this->provideNotNullObject(), 'docgen', [
|
|
||||||
AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => $this->provideDocGenExpectClass(),
|
|
||||||
]);
|
|
||||||
$nullNormalizedObject = $this->getNormalizer()->normalize(null, 'docgen', [
|
|
||||||
AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => $this->provideDocGenExpectClass(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
self::assertEqualsCanonicalizing(array_keys($normalizedObject), array_keys($nullNormalizedObject));
|
|
||||||
self::assertArrayHasKey('isNull', $nullNormalizedObject, 'each object must have an "isNull" key');
|
|
||||||
self::assertTrue($nullNormalizedObject['isNull'], 'isNull key must be true for null objects');
|
|
||||||
self::assertFalse($normalizedObject['isNull'], 'isNull key must be false for null objects');
|
|
||||||
|
|
||||||
foreach ($normalizedObject as $key => $value) {
|
|
||||||
if (in_array($key, ['isNull', 'type'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($value)) {
|
|
||||||
if (array_is_list($value)) {
|
|
||||||
self::assertEquals([], $nullNormalizedObject[$key], "list must be serialized as an empty array, in {$key}");
|
|
||||||
} else {
|
|
||||||
self::assertEqualsCanonicalizing(array_keys($value), array_keys($nullNormalizedObject[$key]), "sub-object must have the same keys, in {$key}");
|
|
||||||
}
|
|
||||||
} elseif (is_string($value)) {
|
|
||||||
self::assertEquals('', $nullNormalizedObject[$key], 'strings must be ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return T
|
|
||||||
*/
|
|
||||||
abstract public function provideNotNullObject(): object;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return class-string<T>
|
|
||||||
*/
|
|
||||||
abstract public function provideDocGenExpectClass(): string;
|
|
||||||
|
|
||||||
abstract public function getNormalizer(): NormalizerInterface;
|
|
||||||
}
|
|
@@ -0,0 +1,45 @@
|
|||||||
|
<?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\DocStoreBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner\RequestPdfSignMessage;
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFPage;
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||||
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class SignatureRequestController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private MessageBusInterface $messageBus,
|
||||||
|
private StoredObjectManagerInterface $storedObjectManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/1.0/document/workflow/{id}/signature-request', name: 'chill_docstore_signature_request')]
|
||||||
|
public function processSignature(StoredObject $storedObject): Response
|
||||||
|
{
|
||||||
|
$content = $this->storedObjectManager->read($storedObject);
|
||||||
|
|
||||||
|
$this->messageBus->dispatch(new RequestPdfSignMessage(
|
||||||
|
0,
|
||||||
|
new PDFSignatureZone(10.0, 10.0, 180.0, 180.0, new PDFPage(0, 500.0, 800.0)),
|
||||||
|
0,
|
||||||
|
'test signature',
|
||||||
|
$content
|
||||||
|
));
|
||||||
|
|
||||||
|
return new Response('<html><head><title>test</title></head><body><p>ok</p></body></html>');
|
||||||
|
}
|
||||||
|
}
|
@@ -129,7 +129,7 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUser(?\Chill\MainBundle\Entity\User $user): self
|
public function setUser($user): self
|
||||||
{
|
{
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
|
|
||||||
|
@@ -86,7 +86,7 @@ class DocumentCategory
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDocumentClass(?string $documentClass): self
|
public function setDocumentClass($documentClass): self
|
||||||
{
|
{
|
||||||
$this->documentClass = $documentClass;
|
$this->documentClass = $documentClass;
|
||||||
|
|
||||||
|
@@ -55,14 +55,14 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt
|
|||||||
return $this->scope;
|
return $this->scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPerson(Person $person): self
|
public function setPerson($person): self
|
||||||
{
|
{
|
||||||
$this->person = $person;
|
$this->person = $person;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setScope(?Scope $scope): self
|
public function setScope($scope): self
|
||||||
{
|
{
|
||||||
$this->scope = $scope;
|
$this->scope = $scope;
|
||||||
|
|
||||||
|
@@ -313,19 +313,4 @@ class StoredObject implements Document, TrackCreationInterface
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveHistory(): void
|
|
||||||
{
|
|
||||||
if ('' === $this->getFilename()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->datas['history'][] = [
|
|
||||||
'filename' => $this->getFilename(),
|
|
||||||
'iv' => $this->getIv(),
|
|
||||||
'key_infos' => $this->getKeyInfos(),
|
|
||||||
'type' => $this->getType(),
|
|
||||||
'before' => (new \DateTimeImmutable('now'))->getTimestamp(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -57,8 +57,8 @@ class StoredObjectDataMapper implements DataMapperInterface
|
|||||||
|
|
||||||
/** @var StoredObject $viewData */
|
/** @var StoredObject $viewData */
|
||||||
if ($viewData->getFilename() !== $forms['stored_object']->getData()['filename']) {
|
if ($viewData->getFilename() !== $forms['stored_object']->getData()['filename']) {
|
||||||
// we want to keep the previous history
|
// we do not want to erase the previous object
|
||||||
$viewData->saveHistory();
|
$viewData = new StoredObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
$viewData->setFilename($forms['stored_object']->getData()['filename']);
|
$viewData->setFilename($forms['stored_object']->getData()['filename']);
|
||||||
|
@@ -4,13 +4,13 @@
|
|||||||
Actions
|
Actions
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li v-if="props.canEdit && is_extension_editable(props.storedObject.type) && props.storedObject.status !== 'stored_object_created'">
|
<li v-if="props.canEdit && is_extension_editable(props.storedObject.type)">
|
||||||
<wopi-edit-button :stored-object="props.storedObject" :classes="{'dropdown-item': true}" :execute-before-leave="props.executeBeforeLeave"></wopi-edit-button>
|
<wopi-edit-button :stored-object="props.storedObject" :classes="{'dropdown-item': true}" :execute-before-leave="props.executeBeforeLeave"></wopi-edit-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="props.canEdit && is_extension_editable(props.storedObject.type) && props.davLink !== undefined && props.davLinkExpiration !== undefined">
|
<li v-if="props.canEdit && is_extension_editable(props.storedObject.type) && props.davLink !== undefined && props.davLinkExpiration !== undefined">
|
||||||
<desktop-edit-button :classes="{'dropdown-item': true}" :edit-link="props.davLink" :expiration-link="props.davLinkExpiration"></desktop-edit-button>
|
<desktop-edit-button :classes="{'dropdown-item': true}" :edit-link="props.davLink" :expiration-link="props.davLinkExpiration"></desktop-edit-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="props.storedObject.type != 'application/pdf' && is_extension_viewable(props.storedObject.type) && props.canConvertPdf && props.storedObject.status !== 'stored_object_created'">
|
<li v-if="props.storedObject.type != 'application/pdf' && is_extension_viewable(props.storedObject.type) && props.canConvertPdf">
|
||||||
<convert-button :stored-object="props.storedObject" :filename="filename" :classes="{'dropdown-item': true}"></convert-button>
|
<convert-button :stored-object="props.storedObject" :filename="filename" :classes="{'dropdown-item': true}"></convert-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="props.canDownload">
|
<li v-if="props.canDownload">
|
||||||
|
@@ -13,7 +13,7 @@ import {reactive} from "vue";
|
|||||||
import {StoredObject, StoredObjectCreated} from "../../types";
|
import {StoredObject, StoredObjectCreated} from "../../types";
|
||||||
|
|
||||||
interface ConvertButtonConfig {
|
interface ConvertButtonConfig {
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject|StoredObjectCreated,
|
||||||
classes: { [key: string]: boolean},
|
classes: { [key: string]: boolean},
|
||||||
filename?: string,
|
filename?: string,
|
||||||
};
|
};
|
||||||
|
@@ -11,7 +11,7 @@ import {build_wopi_editor_link} from "./helpers";
|
|||||||
import {StoredObject, StoredObjectCreated, WopiEditButtonExecutableBeforeLeaveFunction} from "../../types";
|
import {StoredObject, StoredObjectCreated, WopiEditButtonExecutableBeforeLeaveFunction} from "../../types";
|
||||||
|
|
||||||
interface WopiEditButtonConfig {
|
interface WopiEditButtonConfig {
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject|StoredObjectCreated,
|
||||||
returnPath?: string,
|
returnPath?: string,
|
||||||
classes: {[k: string] : boolean},
|
classes: {[k: string] : boolean},
|
||||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction,
|
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction,
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
<?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\DocStoreBundle\Service\Signature\Driver\BaseSigner;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||||
|
|
||||||
|
final readonly class RequestPdfSignMessage
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $signatureId,
|
||||||
|
public PDFSignatureZone $PDFSignatureZone,
|
||||||
|
public int $signatureZoneIndex,
|
||||||
|
public string $reason,
|
||||||
|
public string $content,
|
||||||
|
) {}
|
||||||
|
}
|
@@ -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\DocStoreBundle\Service\Signature\Driver\BaseSigner;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||||
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
|
||||||
|
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
|
||||||
|
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
final readonly class RequestPdfSignMessageSerializer implements SerializerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private NormalizerInterface $normalizer,
|
||||||
|
private DenormalizerInterface $denormalizer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function decode(array $encodedEnvelope): Envelope
|
||||||
|
{
|
||||||
|
$body = $encodedEnvelope['body'];
|
||||||
|
$headers = $encodedEnvelope['headers'];
|
||||||
|
|
||||||
|
if (RequestPdfSignMessage::class !== ($headers['Message'] ?? null)) {
|
||||||
|
throw new MessageDecodingFailedException('serializer does not support this message');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($body, true);
|
||||||
|
|
||||||
|
$zoneSignature = $this->denormalizer->denormalize($data['signatureZone'], PDFSignatureZone::class, 'json', [
|
||||||
|
AbstractNormalizer::GROUPS => ['write'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$message = new RequestPdfSignMessage(
|
||||||
|
$data['signatureId'],
|
||||||
|
$zoneSignature,
|
||||||
|
$data['signatureZoneIndex'],
|
||||||
|
$data['reason'],
|
||||||
|
base64_decode($data['content']),
|
||||||
|
);
|
||||||
|
|
||||||
|
// in case of redelivery, unserialize any stamps
|
||||||
|
$stamps = [];
|
||||||
|
if (isset($headers['stamps'])) {
|
||||||
|
$stamps = unserialize($headers['stamps']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Envelope($message, $stamps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function encode(Envelope $envelope): array
|
||||||
|
{
|
||||||
|
$message = $envelope->getMessage();
|
||||||
|
|
||||||
|
if (!$message instanceof RequestPdfSignMessage) {
|
||||||
|
throw new MessageDecodingFailedException('Message is not a RequestPdfSignMessage');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'signatureId' => $message->signatureId,
|
||||||
|
'signatureZoneIndex' => $message->signatureZoneIndex,
|
||||||
|
'signatureZone' => $this->normalizer->normalize($message->PDFSignatureZone, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||||
|
'reason' => $message->reason,
|
||||||
|
'content' => base64_encode($message->content),
|
||||||
|
];
|
||||||
|
|
||||||
|
$allStamps = [];
|
||||||
|
foreach ($envelope->all() as $stamp) {
|
||||||
|
if ($stamp instanceof NonSendableStampInterface) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$allStamps = [...$allStamps, ...$stamp];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'body' => json_encode($data, JSON_THROW_ON_ERROR, 512),
|
||||||
|
'headers' => [
|
||||||
|
'stamps' => serialize($allStamps),
|
||||||
|
'Message' => RequestPdfSignMessage::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
33
src/Bundle/ChillDocStoreBundle/Service/Signature/PDFPage.php
Normal file
33
src/Bundle/ChillDocStoreBundle/Service/Signature/PDFPage.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?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\DocStoreBundle\Service\Signature;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
|
final readonly class PDFPage
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[Groups(['read'])]
|
||||||
|
public int $index,
|
||||||
|
#[Groups(['read'])]
|
||||||
|
public float $width,
|
||||||
|
#[Groups(['read'])]
|
||||||
|
public float $height,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function equals(self $page): bool
|
||||||
|
{
|
||||||
|
return $page->index === $this->index
|
||||||
|
&& round($page->width, 2) === round($this->width, 2)
|
||||||
|
&& round($page->height, 2) === round($this->height, 2);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
<?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\DocStoreBundle\Service\Signature;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
|
final readonly class PDFSignatureZone
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[Groups(['read'])]
|
||||||
|
public float $x,
|
||||||
|
#[Groups(['read'])]
|
||||||
|
public float $y,
|
||||||
|
#[Groups(['read'])]
|
||||||
|
public float $height,
|
||||||
|
#[Groups(['read'])]
|
||||||
|
public float $width,
|
||||||
|
#[Groups(['read'])]
|
||||||
|
public PDFPage $PDFPage,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function equals(self $other): bool
|
||||||
|
{
|
||||||
|
return
|
||||||
|
$this->x == $other->x
|
||||||
|
&& $this->y == $other->y
|
||||||
|
&& $this->height == $other->height
|
||||||
|
&& $this->width == $other->width
|
||||||
|
&& $this->PDFPage->equals($other->PDFPage);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
<?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\DocStoreBundle\Service\Signature;
|
||||||
|
|
||||||
|
use Smalot\PdfParser\Parser;
|
||||||
|
|
||||||
|
class PDFSignatureZoneParser
|
||||||
|
{
|
||||||
|
public const ZONE_SIGNATURE_START = 'signature_zone';
|
||||||
|
|
||||||
|
private Parser $parser;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public float $defaultHeight = 180.0,
|
||||||
|
public float $defaultWidth = 180.0,
|
||||||
|
) {
|
||||||
|
$this->parser = new Parser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<PDFSignatureZone>
|
||||||
|
*/
|
||||||
|
public function findSignatureZones(string $fileContent): array
|
||||||
|
{
|
||||||
|
$pdf = $this->parser->parseContent($fileContent);
|
||||||
|
$zones = [];
|
||||||
|
|
||||||
|
$defaults = $pdf->getObjectsByType('Pages');
|
||||||
|
$defaultPage = reset($defaults);
|
||||||
|
$defaultPageDetails = $defaultPage->getDetails();
|
||||||
|
|
||||||
|
foreach ($pdf->getPages() as $index => $page) {
|
||||||
|
$details = $page->getDetails();
|
||||||
|
$pdfPage = new PDFPage(
|
||||||
|
$index,
|
||||||
|
(float) ($details['MediaBox'][2] ?? $defaultPageDetails['MediaBox'][2]),
|
||||||
|
(float) ($details['MediaBox'][3] ?? $defaultPageDetails['MediaBox'][3]),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($page->getDataTm() as $dataTm) {
|
||||||
|
if (str_starts_with($dataTm[1], self::ZONE_SIGNATURE_START)) {
|
||||||
|
$zones[] = new PDFSignatureZone((float) $dataTm[0][4], (float) $dataTm[0][5], $this->defaultHeight, $this->defaultWidth, $pdfPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $zones;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,53 +0,0 @@
|
|||||||
<?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\DocStoreBundle\Tests\Entity;
|
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class StoredObjectTest extends KernelTestCase
|
|
||||||
{
|
|
||||||
public function testSaveHistory(): void
|
|
||||||
{
|
|
||||||
$storedObject = new StoredObject();
|
|
||||||
$storedObject
|
|
||||||
->setFilename('test_0')
|
|
||||||
->setIv([2, 4, 6, 8])
|
|
||||||
->setKeyInfos(['key' => ['data0' => 'data0']])
|
|
||||||
->setType('text/html');
|
|
||||||
|
|
||||||
$storedObject->saveHistory();
|
|
||||||
|
|
||||||
$storedObject
|
|
||||||
->setFilename('test_1')
|
|
||||||
->setIv([8, 10, 12])
|
|
||||||
->setKeyInfos(['key' => ['data1' => 'data1']])
|
|
||||||
->setType('text/text');
|
|
||||||
|
|
||||||
$storedObject->saveHistory();
|
|
||||||
|
|
||||||
self::assertEquals('test_0', $storedObject->getDatas()['history'][0]['filename']);
|
|
||||||
self::assertEquals([2, 4, 6, 8], $storedObject->getDatas()['history'][0]['iv']);
|
|
||||||
self::assertEquals(['key' => ['data0' => 'data0']], $storedObject->getDatas()['history'][0]['key_infos']);
|
|
||||||
self::assertEquals('text/html', $storedObject->getDatas()['history'][0]['type']);
|
|
||||||
|
|
||||||
self::assertEquals('test_1', $storedObject->getDatas()['history'][1]['filename']);
|
|
||||||
self::assertEquals([8, 10, 12], $storedObject->getDatas()['history'][1]['iv']);
|
|
||||||
self::assertEquals(['key' => ['data1' => 'data1']], $storedObject->getDatas()['history'][1]['key_infos']);
|
|
||||||
self::assertEquals('text/text', $storedObject->getDatas()['history'][1]['type']);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -56,14 +56,14 @@ class StoredObjectTypeTest extends TypeTestCase
|
|||||||
{"filename":"abcdef","iv":[10, 15, 20, 30],"keyInfos":[],"type":"text/html","status":"object_store_created"}
|
{"filename":"abcdef","iv":[10, 15, 20, 30],"keyInfos":[],"type":"text/html","status":"object_store_created"}
|
||||||
JSON];
|
JSON];
|
||||||
$model = new StoredObject();
|
$model = new StoredObject();
|
||||||
$originalObjectId = spl_object_hash($model);
|
$originalObjectId = spl_object_id($model);
|
||||||
$form = $this->factory->create(StoredObjectType::class, $model, ['has_title' => true]);
|
$form = $this->factory->create(StoredObjectType::class, $model, ['has_title' => true]);
|
||||||
|
|
||||||
$form->submit($formData);
|
$form->submit($formData);
|
||||||
|
|
||||||
$this->assertTrue($form->isSynchronized());
|
$this->assertTrue($form->isSynchronized());
|
||||||
$model = $form->getData();
|
$model = $form->getData();
|
||||||
$this->assertEquals($originalObjectId, spl_object_hash($model));
|
$this->assertNotEquals($originalObjectId, spl_object_hash($model));
|
||||||
$this->assertEquals('abcdef', $model->getFilename());
|
$this->assertEquals('abcdef', $model->getFilename());
|
||||||
$this->assertEquals([10, 15, 20, 30], $model->getIv());
|
$this->assertEquals([10, 15, 20, 30], $model->getIv());
|
||||||
$this->assertEquals('text/html', $model->getType());
|
$this->assertEquals('text/html', $model->getType());
|
||||||
|
@@ -0,0 +1,132 @@
|
|||||||
|
<?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\DocStoreBundle\Tests\Service\Signature\Driver\BaseSigner;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFPage;
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner\RequestPdfSignMessage;
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner\RequestPdfSignMessageSerializer;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
use Symfony\Component\Serializer\Serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class RequestPdfSignMessageSerializerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEncode(): void
|
||||||
|
{
|
||||||
|
$serializer = $this->buildSerializer();
|
||||||
|
|
||||||
|
$envelope = new Envelope(
|
||||||
|
$request = new RequestPdfSignMessage(
|
||||||
|
0,
|
||||||
|
new PDFSignatureZone(10.0, 10.0, 180.0, 180.0, new PDFPage(0, 500.0, 800.0)),
|
||||||
|
0,
|
||||||
|
'metadata to add to the signature',
|
||||||
|
'abc'
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$actual = $serializer->encode($envelope);
|
||||||
|
$expectedBody = json_encode([
|
||||||
|
'signatureId' => $request->signatureId,
|
||||||
|
'signatureZoneIndex' => $request->signatureZoneIndex,
|
||||||
|
'signatureZone' => ['x' => 10.0],
|
||||||
|
'reason' => $request->reason,
|
||||||
|
'content' => base64_encode($request->content),
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertIsArray($actual);
|
||||||
|
self::assertArrayHasKey('body', $actual);
|
||||||
|
self::assertArrayHasKey('headers', $actual);
|
||||||
|
self::assertEquals($expectedBody, $actual['body']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecode(): void
|
||||||
|
{
|
||||||
|
$serializer = $this->buildSerializer();
|
||||||
|
|
||||||
|
$request = new RequestPdfSignMessage(
|
||||||
|
0,
|
||||||
|
new PDFSignatureZone(10.0, 10.0, 180.0, 180.0, new PDFPage(0, 500.0, 800.0)),
|
||||||
|
0,
|
||||||
|
'metadata to add to the signature',
|
||||||
|
'abc'
|
||||||
|
);
|
||||||
|
|
||||||
|
$bodyAsString = json_encode([
|
||||||
|
'signatureId' => $request->signatureId,
|
||||||
|
'signatureZoneIndex' => $request->signatureZoneIndex,
|
||||||
|
'signatureZone' => ['x' => 10.0],
|
||||||
|
'reason' => $request->reason,
|
||||||
|
'content' => base64_encode($request->content),
|
||||||
|
], JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
$actual = $serializer->decode([
|
||||||
|
'body' => $bodyAsString,
|
||||||
|
'headers' => [
|
||||||
|
'Message' => RequestPdfSignMessage::class,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertInstanceOf(RequestPdfSignMessage::class, $actual->getMessage());
|
||||||
|
self::assertEquals($request->signatureId, $actual->getMessage()->signatureId);
|
||||||
|
self::assertEquals($request->signatureZoneIndex, $actual->getMessage()->signatureZoneIndex);
|
||||||
|
self::assertEquals($request->reason, $actual->getMessage()->reason);
|
||||||
|
self::assertEquals($request->content, $actual->getMessage()->content);
|
||||||
|
self::assertNotNull($actual->getMessage()->PDFSignatureZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildSerializer(): RequestPdfSignMessageSerializer
|
||||||
|
{
|
||||||
|
$normalizer =
|
||||||
|
new class () implements NormalizerInterface {
|
||||||
|
public function normalize($object, ?string $format = null, array $context = []): array
|
||||||
|
{
|
||||||
|
if (!$object instanceof PDFSignatureZone) {
|
||||||
|
throw new UnexpectedValueException('expected RequestPdfSignMessage');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'x' => $object->x,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsNormalization($data, ?string $format = null): bool
|
||||||
|
{
|
||||||
|
return $data instanceof PDFSignatureZone;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$denormalizer = new class () implements DenormalizerInterface {
|
||||||
|
public function denormalize($data, string $type, ?string $format = null, array $context = [])
|
||||||
|
{
|
||||||
|
return new PDFSignatureZone(10.0, 10.0, 180.0, 180.0, new PDFPage(0, 500.0, 800.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsDenormalization($data, string $type, ?string $format = null)
|
||||||
|
{
|
||||||
|
return PDFSignatureZone::class === $type;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$serializer = new Serializer([$normalizer, $denormalizer]);
|
||||||
|
|
||||||
|
return new RequestPdfSignMessageSerializer($serializer, $serializer);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,77 @@
|
|||||||
|
<?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 Tests\Service\Signature;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFPage;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||||
|
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZoneParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class PDFSignatureZoneParserTest extends TestCase
|
||||||
|
{
|
||||||
|
private static PDFSignatureZoneParser $parser;
|
||||||
|
|
||||||
|
public static function setUpBeforeClass(): void
|
||||||
|
{
|
||||||
|
self::$parser = new PDFSignatureZoneParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideFiles
|
||||||
|
*
|
||||||
|
* @param list<PDFSignatureZone> $expected
|
||||||
|
*/
|
||||||
|
public function testFindSignatureZones(string $filePath, array $expected): void
|
||||||
|
{
|
||||||
|
$content = file_get_contents($filePath);
|
||||||
|
|
||||||
|
if (false === $content) {
|
||||||
|
throw new \LogicException("Unable to read file {$filePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$actual = self::$parser->findSignatureZones($content);
|
||||||
|
|
||||||
|
self::assertEquals(count($expected), count($actual));
|
||||||
|
|
||||||
|
foreach ($actual as $index => $signatureZone) {
|
||||||
|
self::assertObjectEquals($expected[$index], $signatureZone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function provideFiles(): iterable
|
||||||
|
{
|
||||||
|
yield [
|
||||||
|
__DIR__.'/data/signature_2_signature_page_1.pdf',
|
||||||
|
[
|
||||||
|
new PDFSignatureZone(
|
||||||
|
127.7,
|
||||||
|
95.289,
|
||||||
|
180.0,
|
||||||
|
180.0,
|
||||||
|
$page = new PDFPage(0, 595.30393, 841.8897)
|
||||||
|
),
|
||||||
|
new PDFSignatureZone(
|
||||||
|
269.5,
|
||||||
|
95.289,
|
||||||
|
180.0,
|
||||||
|
180.0,
|
||||||
|
$page,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@@ -15,7 +15,7 @@ use Chill\EventBundle\Entity\Event;
|
|||||||
use Chill\EventBundle\Entity\Participation;
|
use Chill\EventBundle\Entity\Participation;
|
||||||
use Chill\EventBundle\Form\EventType;
|
use Chill\EventBundle\Form\EventType;
|
||||||
use Chill\EventBundle\Form\Type\PickEventType;
|
use Chill\EventBundle\Form\Type\PickEventType;
|
||||||
use Chill\EventBundle\Security\EventVoter;
|
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||||
use Chill\MainBundle\Entity\Center;
|
use Chill\MainBundle\Entity\Center;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
@@ -61,7 +61,7 @@ final class EventController extends AbstractController
|
|||||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry
|
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', 'POST', 'DELETE'])]
|
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/delete', name: 'chill_event__event_delete', requirements: ['event_id' => '\d+'], methods: ['GET', 'DELETE'])]
|
||||||
public function deleteAction($event_id, Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
public function deleteAction($event_id, Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||||
{
|
{
|
||||||
$em = $this->managerRegistry->getManager();
|
$em = $this->managerRegistry->getManager();
|
||||||
@@ -78,10 +78,10 @@ final class EventController extends AbstractController
|
|||||||
|
|
||||||
$form = $this->createDeleteForm($event_id);
|
$form = $this->createDeleteForm($event_id);
|
||||||
|
|
||||||
if (Request::METHOD_POST === $request->getMethod()) {
|
if (Request::METHOD_DELETE === $request->getMethod()) {
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isValid()) {
|
||||||
foreach ($participations as $participation) {
|
foreach ($participations as $participation) {
|
||||||
$em->remove($participation);
|
$em->remove($participation);
|
||||||
}
|
}
|
||||||
@@ -108,6 +108,28 @@ 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.
|
* List events subscriptions for a person.
|
||||||
*
|
*
|
||||||
@@ -291,7 +313,7 @@ final class EventController extends AbstractController
|
|||||||
/**
|
/**
|
||||||
* Edits an existing Event entity.
|
* Edits an existing Event entity.
|
||||||
*/
|
*/
|
||||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/edit', name: 'chill_event__event_edit', methods: ['GET', 'POST', 'PUT'])]
|
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/update', name: 'chill_event__event_update', methods: ['POST', 'PUT'])]
|
||||||
public function updateAction(Request $request, $event_id): \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
public function updateAction(Request $request, $event_id): \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||||
{
|
{
|
||||||
$em = $this->managerRegistry->getManager();
|
$em = $this->managerRegistry->getManager();
|
||||||
@@ -302,20 +324,14 @@ final class EventController extends AbstractController
|
|||||||
throw $this->createNotFoundException('Unable to find Event entity.');
|
throw $this->createNotFoundException('Unable to find Event entity.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$editForm = $this->createForm(EventType::class, $entity, [
|
$editForm = $this->createEditForm($entity);
|
||||||
'center' => $entity->getCenter(),
|
|
||||||
'role' => EventVoter::UPDATE,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$editForm->add('submit', SubmitType::class, ['label' => 'Update']);
|
|
||||||
|
|
||||||
$editForm->handleRequest($request);
|
$editForm->handleRequest($request);
|
||||||
|
|
||||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
if ($editForm->isValid()) {
|
||||||
$em->persist($entity);
|
|
||||||
$em->flush();
|
$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]);
|
return $this->redirectToRoute('chill_event__event_show', ['event_id' => $event_id]);
|
||||||
}
|
}
|
||||||
@@ -402,6 +418,7 @@ final class EventController extends AbstractController
|
|||||||
$builder->add('event_id', HiddenType::class, [
|
$builder->add('event_id', HiddenType::class, [
|
||||||
'data' => $event->getId(),
|
'data' => $event->getId(),
|
||||||
]);
|
]);
|
||||||
|
dump($event->getId());
|
||||||
|
|
||||||
return $builder->getForm();
|
return $builder->getForm();
|
||||||
}
|
}
|
||||||
@@ -583,7 +600,29 @@ final class EventController extends AbstractController
|
|||||||
->setAction($this->generateUrl('chill_event__event_delete', [
|
->setAction($this->generateUrl('chill_event__event_delete', [
|
||||||
'event_id' => $event_id,
|
'event_id' => $event_id,
|
||||||
]))
|
]))
|
||||||
|
->setMethod('DELETE')
|
||||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||||
->getForm();
|
->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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -201,7 +201,7 @@ class EventTypeController extends AbstractController
|
|||||||
/**
|
/**
|
||||||
* Creates a form to delete a EventType entity by id.
|
* Creates a form to delete a EventType entity by id.
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\Form\FormInterface The form
|
* @return \Symfony\Component\Form\Form The form
|
||||||
*/
|
*/
|
||||||
private function createDeleteForm(mixed $id)
|
private function createDeleteForm(mixed $id)
|
||||||
{
|
{
|
||||||
@@ -210,6 +210,7 @@ class EventTypeController extends AbstractController
|
|||||||
'chill_eventtype_admin_delete',
|
'chill_eventtype_admin_delete',
|
||||||
['id' => $id]
|
['id' => $id]
|
||||||
))
|
))
|
||||||
|
->setMethod('DELETE')
|
||||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||||
->getForm();
|
->getForm();
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ use Chill\EventBundle\Entity\Event;
|
|||||||
use Chill\EventBundle\Entity\Participation;
|
use Chill\EventBundle\Entity\Participation;
|
||||||
use Chill\EventBundle\Form\ParticipationType;
|
use Chill\EventBundle\Form\ParticipationType;
|
||||||
use Chill\EventBundle\Repository\EventRepository;
|
use Chill\EventBundle\Repository\EventRepository;
|
||||||
use Chill\EventBundle\Security\ParticipationVoter;
|
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
|
||||||
use Chill\PersonBundle\Repository\PersonRepository;
|
use Chill\PersonBundle\Repository\PersonRepository;
|
||||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
@@ -259,10 +259,10 @@ final class ParticipationController extends AbstractController
|
|||||||
|
|
||||||
$form = $this->createDeleteForm($participation_id);
|
$form = $this->createDeleteForm($participation_id);
|
||||||
|
|
||||||
if (Request::METHOD_POST === $request->getMethod()) {
|
if (Request::METHOD_DELETE === $request->getMethod()) {
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$em->remove($participation);
|
$em->remove($participation);
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
@@ -753,6 +753,7 @@ final class ParticipationController extends AbstractController
|
|||||||
->setAction($this->generateUrl('chill_event_participation_delete', [
|
->setAction($this->generateUrl('chill_event_participation_delete', [
|
||||||
'participation_id' => $participation_id,
|
'participation_id' => $participation_id,
|
||||||
]))
|
]))
|
||||||
|
->setMethod('DELETE')
|
||||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||||
->getForm();
|
->getForm();
|
||||||
}
|
}
|
||||||
|
@@ -201,7 +201,7 @@ class RoleController extends AbstractController
|
|||||||
/**
|
/**
|
||||||
* Creates a form to delete a Role entity by id.
|
* Creates a form to delete a Role entity by id.
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\Form\FormInterface The form
|
* @return \Symfony\Component\Form\Form The form
|
||||||
*/
|
*/
|
||||||
private function createDeleteForm(mixed $id)
|
private function createDeleteForm(mixed $id)
|
||||||
{
|
{
|
||||||
|
@@ -201,12 +201,13 @@ class StatusController extends AbstractController
|
|||||||
/**
|
/**
|
||||||
* Creates a form to delete a Status entity by id.
|
* Creates a form to delete a Status entity by id.
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\Form\FormInterface The form
|
* @return \Symfony\Component\Form\Form The form
|
||||||
*/
|
*/
|
||||||
private function createDeleteForm(mixed $id)
|
private function createDeleteForm(mixed $id)
|
||||||
{
|
{
|
||||||
return $this->createFormBuilder()
|
return $this->createFormBuilder()
|
||||||
->setAction($this->generateUrl('chill_event_admin_status_delete', ['id' => $id]))
|
->setAction($this->generateUrl('chill_event_admin_status_delete', ['id' => $id]))
|
||||||
|
->setMethod('DELETE')
|
||||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||||
->getForm();
|
->getForm();
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\EventBundle\DependencyInjection;
|
namespace Chill\EventBundle\DependencyInjection;
|
||||||
|
|
||||||
use Chill\EventBundle\Security\EventVoter;
|
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||||
use Chill\EventBundle\Security\ParticipationVoter;
|
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
||||||
@@ -33,13 +33,12 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
|
|||||||
|
|
||||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
||||||
$loader->load('services.yaml');
|
$loader->load('services.yaml');
|
||||||
$loader->load('services/security.yaml');
|
$loader->load('services/authorization.yaml');
|
||||||
$loader->load('services/fixtures.yaml');
|
$loader->load('services/fixtures.yaml');
|
||||||
$loader->load('services/forms.yaml');
|
$loader->load('services/forms.yaml');
|
||||||
$loader->load('services/repositories.yaml');
|
$loader->load('services/repositories.yaml');
|
||||||
$loader->load('services/search.yaml');
|
$loader->load('services/search.yaml');
|
||||||
$loader->load('services/timeline.yaml');
|
$loader->load('services/timeline.yaml');
|
||||||
$loader->load('services/export.yaml');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** (non-PHPdoc).
|
/** (non-PHPdoc).
|
||||||
|
@@ -47,7 +47,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
|||||||
private ?Scope $circle = null;
|
private ?Scope $circle = null;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE)]
|
||||||
private ?\DateTime $date = null;
|
private ?\DateTime $date;
|
||||||
|
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
#[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
||||||
@@ -265,9 +265,11 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
|||||||
/**
|
/**
|
||||||
* Set label.
|
* Set label.
|
||||||
*
|
*
|
||||||
|
* @param string $label
|
||||||
|
*
|
||||||
* @return Event
|
* @return Event
|
||||||
*/
|
*/
|
||||||
public function setName(?string $label)
|
public function setName($label)
|
||||||
{
|
{
|
||||||
$this->name = $label;
|
$this->name = $label;
|
||||||
|
|
||||||
|
@@ -146,9 +146,11 @@ class EventType
|
|||||||
/**
|
/**
|
||||||
* Set active.
|
* Set active.
|
||||||
*
|
*
|
||||||
|
* @param bool $active
|
||||||
|
*
|
||||||
* @return EventType
|
* @return EventType
|
||||||
*/
|
*/
|
||||||
public function setActive(bool $active)
|
public function setActive($active)
|
||||||
{
|
{
|
||||||
$this->active = $active;
|
$this->active = $active;
|
||||||
|
|
||||||
|
@@ -81,9 +81,11 @@ class Role
|
|||||||
/**
|
/**
|
||||||
* Set active.
|
* Set active.
|
||||||
*
|
*
|
||||||
|
* @param bool $active
|
||||||
|
*
|
||||||
* @return Role
|
* @return Role
|
||||||
*/
|
*/
|
||||||
public function setActive(bool $active)
|
public function setActive($active)
|
||||||
{
|
{
|
||||||
$this->active = $active;
|
$this->active = $active;
|
||||||
|
|
||||||
|
@@ -81,9 +81,11 @@ class Status
|
|||||||
/**
|
/**
|
||||||
* Set active.
|
* Set active.
|
||||||
*
|
*
|
||||||
|
* @param bool $active
|
||||||
|
*
|
||||||
* @return Status
|
* @return Status
|
||||||
*/
|
*/
|
||||||
public function setActive(bool $active)
|
public function setActive($active)
|
||||||
{
|
{
|
||||||
$this->active = $active;
|
$this->active = $active;
|
||||||
|
|
||||||
|
@@ -1,110 +0,0 @@
|
|||||||
<?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';
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,81 +0,0 @@
|
|||||||
<?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';
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,81 +0,0 @@
|
|||||||
<?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';
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
<?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';
|
|
||||||
}
|
|
@@ -1,125 +0,0 @@
|
|||||||
<?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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,126 +0,0 @@
|
|||||||
<?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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,95 +0,0 @@
|
|||||||
<?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';
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,94 +0,0 @@
|
|||||||
<?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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,94 +0,0 @@
|
|||||||
<?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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -13,7 +13,6 @@ namespace Chill\EventBundle\Form;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\Form\StoredObjectType;
|
use Chill\DocStoreBundle\Form\StoredObjectType;
|
||||||
use Chill\EventBundle\Entity\Event;
|
|
||||||
use Chill\EventBundle\Form\Type\PickEventTypeType;
|
use Chill\EventBundle\Form\Type\PickEventTypeType;
|
||||||
use Chill\MainBundle\Entity\Center;
|
use Chill\MainBundle\Entity\Center;
|
||||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||||
@@ -24,7 +23,6 @@ use Chill\MainBundle\Form\Type\PickUserLocationType;
|
|||||||
use Chill\MainBundle\Form\Type\ScopePickerType;
|
use Chill\MainBundle\Form\Type\ScopePickerType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
@@ -33,9 +31,7 @@ class EventType extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('name', TextType::class, [
|
->add('name')
|
||||||
'required' => true,
|
|
||||||
])
|
|
||||||
->add('date', ChillDateTimeType::class, [
|
->add('date', ChillDateTimeType::class, [
|
||||||
'required' => true,
|
'required' => true,
|
||||||
])
|
])
|
||||||
@@ -79,7 +75,7 @@ class EventType extends AbstractType
|
|||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'data_class' => Event::class,
|
'data_class' => \Chill\EventBundle\Entity\Event::class,
|
||||||
]);
|
]);
|
||||||
$resolver
|
$resolver
|
||||||
->setRequired(['center', 'role'])
|
->setRequired(['center', 'role'])
|
||||||
|
@@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\EventBundle\Menu;
|
namespace Chill\EventBundle\Menu;
|
||||||
|
|
||||||
use Chill\EventBundle\Security\EventVoter;
|
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||||
use Knp\Menu\MenuItem;
|
use Knp\Menu\MenuItem;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
@@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\EventBundle\Menu;
|
namespace Chill\EventBundle\Menu;
|
||||||
|
|
||||||
use Chill\EventBundle\Security\EventVoter;
|
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||||
use Knp\Menu\MenuItem;
|
use Knp\Menu\MenuItem;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
@@ -13,7 +13,7 @@ namespace Chill\EventBundle\Repository;
|
|||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
use Chill\EventBundle\Entity\Event;
|
||||||
use Chill\EventBundle\Entity\Participation;
|
use Chill\EventBundle\Entity\Participation;
|
||||||
use Chill\EventBundle\Security\EventVoter;
|
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
@@ -12,57 +12,13 @@ declare(strict_types=1);
|
|||||||
namespace Chill\EventBundle\Repository;
|
namespace Chill\EventBundle\Repository;
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Role;
|
use Chill\EventBundle\Entity\Role;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
use Doctrine\ORM\EntityRepository;
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
|
||||||
|
|
||||||
readonly class RoleRepository implements ObjectRepository
|
class RoleRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager, private TranslatableStringHelper $translatableStringHelper)
|
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(Role::class);
|
parent::__construct($registry, 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{% import '@ChillPerson/Person/macro.html.twig' as person_macro %}
|
{% import '@ChillPerson/Person/macro.html.twig' as person_macro %}
|
||||||
|
|
||||||
{% if ignored_participations|length > 0 %}
|
{% if ignored_participations|length > 0 %}
|
||||||
<p>{{ 'ignored_participations'|trans({'count': ignored_participations|length}) }}:</p>
|
<p>{% transchoice ignored_participations|length %}The following people have been ignored because they are already participating on the event{% endtranschoice %} :</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for p in ignored_participations %}
|
{% for p in ignored_participations %}
|
||||||
<li>{{ person_macro.render(p.person) }}</li>
|
<li>{{ person_macro.render(p.person) }}</li>
|
||||||
|
@@ -9,19 +9,18 @@ declare(strict_types=1);
|
|||||||
* the LICENSE file that was distributed with this source code.
|
* the LICENSE file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Chill\EventBundle\Security;
|
namespace Chill\EventBundle\Security\Authorization;
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
use Chill\EventBundle\Entity\Event;
|
||||||
use Chill\MainBundle\Entity\Center;
|
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
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\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description of EventVoter.
|
* Description of EventVoter.
|
||||||
@@ -43,46 +42,61 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
|
|||||||
|
|
||||||
final public const UPDATE = 'CHILL_EVENT_UPDATE';
|
final public const UPDATE = 'CHILL_EVENT_UPDATE';
|
||||||
|
|
||||||
final public const STATS = 'CHILL_EVENT_STATS';
|
/**
|
||||||
|
* @var AccessDecisionManagerInterface
|
||||||
|
*/
|
||||||
|
protected $accessDecisionManager;
|
||||||
|
|
||||||
private readonly VoterHelperInterface $voterHelper;
|
/**
|
||||||
|
* @var AuthorizationHelper
|
||||||
|
*/
|
||||||
|
protected $authorizationHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly AuthorizationHelper $authorizationHelper,
|
AccessDecisionManagerInterface $accessDecisionManager,
|
||||||
private readonly LoggerInterface $logger,
|
AuthorizationHelper $authorizationHelper,
|
||||||
VoterHelperFactoryInterface $voterHelperFactory
|
LoggerInterface $logger
|
||||||
) {
|
) {
|
||||||
$this->voterHelper = $voterHelperFactory
|
$this->accessDecisionManager = $accessDecisionManager;
|
||||||
->generate(self::class)
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
->addCheckFor(null, [self::SEE])
|
$this->logger = $logger;
|
||||||
->addCheckFor(Event::class, [...self::ROLES])
|
|
||||||
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
|
||||||
->addCheckFor(Center::class, [self::STATS])
|
|
||||||
->build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRoles(): array
|
public function getRoles(): array
|
||||||
{
|
{
|
||||||
return [...self::ROLES, self::STATS];
|
return self::ROLES;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRolesWithHierarchy(): array
|
public function getRolesWithHierarchy(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'Event' => $this->getRoles(),
|
'Event' => self::ROLES,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRolesWithoutScope(): array
|
public function getRolesWithoutScope(): array
|
||||||
{
|
{
|
||||||
return [self::ROLES, self::STATS];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supports($attribute, $subject)
|
public function supports($attribute, $subject)
|
||||||
{
|
{
|
||||||
return $this->voterHelper->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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $attribute
|
||||||
|
* @param Event $subject
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||||
{
|
{
|
||||||
$this->logger->debug(sprintf('Voting from %s class', self::class));
|
$this->logger->debug(sprintf('Voting from %s class', self::class));
|
||||||
@@ -104,5 +118,15 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
|
|||||||
->getReachableCenters($token->getUser(), $attribute);
|
->getReachableCenters($token->getUser(), $attribute);
|
||||||
|
|
||||||
return \count($centers) > 0;
|
return \count($centers) > 0;
|
||||||
|
|
||||||
|
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->authorizationHelper->userHasAccess(
|
||||||
|
$token->getUser(),
|
||||||
|
$subject,
|
||||||
|
$attribute
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,19 +9,18 @@ declare(strict_types=1);
|
|||||||
* the LICENSE file that was distributed with this source code.
|
* the LICENSE file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Chill\EventBundle\Security;
|
namespace Chill\EventBundle\Security\Authorization;
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Participation;
|
use Chill\EventBundle\Entity\Participation;
|
||||||
use Chill\MainBundle\Entity\Center;
|
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
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\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||||
|
|
||||||
class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
|
class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
|
||||||
{
|
{
|
||||||
@@ -40,48 +39,58 @@ class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierar
|
|||||||
|
|
||||||
final public const UPDATE = 'CHILL_EVENT_PARTICIPATION_UPDATE';
|
final public const UPDATE = 'CHILL_EVENT_PARTICIPATION_UPDATE';
|
||||||
|
|
||||||
final public const STATS = 'CHILL_EVENT_PARTICIPATION_STATS';
|
/**
|
||||||
|
* @var AccessDecisionManagerInterface
|
||||||
|
*/
|
||||||
|
protected $accessDecisionManager;
|
||||||
|
|
||||||
private readonly VoterHelperInterface $voterHelper;
|
/**
|
||||||
|
* @var AuthorizationHelper
|
||||||
|
*/
|
||||||
|
protected $authorizationHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly AuthorizationHelper $authorizationHelper,
|
AccessDecisionManagerInterface $accessDecisionManager,
|
||||||
private readonly LoggerInterface $logger,
|
AuthorizationHelper $authorizationHelper,
|
||||||
VoterHelperFactoryInterface $voterHelperFactory
|
LoggerInterface $logger
|
||||||
) {
|
) {
|
||||||
$this->voterHelper = $voterHelperFactory
|
$this->accessDecisionManager = $accessDecisionManager;
|
||||||
->generate(self::class)
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
->addCheckFor(null, [self::SEE])
|
$this->logger = $logger;
|
||||||
->addCheckFor(Participation::class, [...self::ROLES])
|
|
||||||
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
|
||||||
->addCheckFor(Center::class, [self::STATS])
|
|
||||||
->build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRoles(): array
|
public function getRoles(): array
|
||||||
{
|
{
|
||||||
return [...self::ROLES, self::STATS];
|
return self::ROLES;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRolesWithHierarchy(): array
|
public function getRolesWithHierarchy(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'Participation' => $this->getRoles(),
|
'Event' => self::ROLES,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRolesWithoutScope(): array
|
public function getRolesWithoutScope(): array
|
||||||
{
|
{
|
||||||
return [self::ROLES, self::STATS];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supports($attribute, $subject)
|
public function supports($attribute, $subject)
|
||||||
{
|
{
|
||||||
return $this->voterHelper->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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $attribute
|
* @param string $attribute
|
||||||
|
* @param Participation $subject
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
@@ -106,5 +115,15 @@ class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierar
|
|||||||
->getReachableCenters($token->getUser(), $attribute);
|
->getReachableCenters($token->getUser(), $attribute);
|
||||||
|
|
||||||
return \count($centers) > 0;
|
return \count($centers) > 0;
|
||||||
|
|
||||||
|
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->authorizationHelper->userHasAccess(
|
||||||
|
$token->getUser(),
|
||||||
|
$subject,
|
||||||
|
$attribute
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user