mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-30 18:39:43 +00:00
Compare commits
9 Commits
v3.0.0-RC4
...
signature-
Author | SHA1 | Date | |
---|---|---|---|
7aa63a25a7
|
|||
610239930b
|
|||
b65e2c62c4 | |||
89f5231649
|
|||
99818c211d
|
|||
a9f0059743
|
|||
5bc542a567
|
|||
c8ccce83fd
|
|||
4a229ebf6b |
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: ""
|
@@ -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
|
|
75
CHANGELOG.md
75
CHANGELOG.md
@@ -6,81 +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.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"
|
||||||
|
@@ -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
|
||||||
************************
|
************************
|
||||||
|
@@ -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",
|
||||||
|
@@ -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',
|
|
||||||
])
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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
|
||||||
|
@@ -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é
|
||||||
|
@@ -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>');
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
@@ -418,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();
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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).
|
||||||
|
@@ -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(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,43 +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\Tests\Export;
|
|
||||||
|
|
||||||
use Chill\EventBundle\Export\Export\CountEventParticipations;
|
|
||||||
use Doctrine\ORM\AbstractQuery;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class CountEventParticipationsTest extends KernelTestCase
|
|
||||||
{
|
|
||||||
private CountEventParticipations $countEventParticipations;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
self::bootKernel();
|
|
||||||
$this->countEventParticipations = self::getContainer()->get(CountEventParticipations::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExecuteQuery(): void
|
|
||||||
{
|
|
||||||
$qb = $this->countEventParticipations->initiateQuery([], [], [])
|
|
||||||
->setMaxResults(1);
|
|
||||||
|
|
||||||
$results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
|
|
||||||
|
|
||||||
self::assertIsArray($results, 'smoke test: test that the result is an array');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,43 +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\Tests\Export;
|
|
||||||
|
|
||||||
use Chill\EventBundle\Export\Export\CountEvents;
|
|
||||||
use Doctrine\ORM\AbstractQuery;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class CountEventTest extends KernelTestCase
|
|
||||||
{
|
|
||||||
private CountEvents $countEvents;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
self::bootKernel();
|
|
||||||
$this->countEvents = self::getContainer()->get(CountEvents::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExecuteQuery(): void
|
|
||||||
{
|
|
||||||
$qb = $this->countEvents->initiateQuery([], [], [])
|
|
||||||
->setMaxResults(1);
|
|
||||||
|
|
||||||
$results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
|
|
||||||
|
|
||||||
self::assertIsArray($results, 'smoke test: test that the result is an array');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,59 +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 Export\aggregators;
|
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
|
||||||
use Chill\EventBundle\Export\Aggregator\EventDateAggregator;
|
|
||||||
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class EventDateAggregatorTest extends AbstractAggregatorTest
|
|
||||||
{
|
|
||||||
private $aggregator;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$this->aggregator = self::getContainer()->get(EventDateAggregator::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAggregator()
|
|
||||||
{
|
|
||||||
return $this->aggregator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFormData(): array|\Generator
|
|
||||||
{
|
|
||||||
yield ['frequency' => 'YYYY'];
|
|
||||||
yield ['frequency' => 'YYYY-MM'];
|
|
||||||
yield ['frequency' => 'YYYY-IV'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQueryBuilders(): array
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
return [
|
|
||||||
$em->createQueryBuilder()
|
|
||||||
->select('event.id')
|
|
||||||
->from(Event::class, 'event'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,59 +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 Export\aggregators;
|
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
|
||||||
use Chill\EventBundle\Export\Aggregator\EventTypeAggregator;
|
|
||||||
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class EventTypeAggregatorTest extends AbstractAggregatorTest
|
|
||||||
{
|
|
||||||
private $aggregator;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$this->aggregator = self::getContainer()->get(EventTypeAggregator::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAggregator()
|
|
||||||
{
|
|
||||||
return $this->aggregator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFormData(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQueryBuilders(): array
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
return [
|
|
||||||
$em->createQueryBuilder()
|
|
||||||
->select('event.id')
|
|
||||||
->from(Event::class, 'event'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,63 +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 Export\aggregators;
|
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
|
||||||
use Chill\EventBundle\Entity\Participation;
|
|
||||||
use Chill\EventBundle\Export\Aggregator\RoleAggregator;
|
|
||||||
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class RoleAggregatorTest extends AbstractAggregatorTest
|
|
||||||
{
|
|
||||||
private $aggregator;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$this->aggregator = self::getContainer()->get(RoleAggregator::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAggregator()
|
|
||||||
{
|
|
||||||
return $this->aggregator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFormData(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQueryBuilders(): array
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
return [
|
|
||||||
$em->createQueryBuilder()
|
|
||||||
->select('event.id')
|
|
||||||
->from(Event::class, 'event'),
|
|
||||||
$em->createQueryBuilder()
|
|
||||||
->select('event_part')
|
|
||||||
->from(Participation::class, 'event_part'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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 Export\filters;
|
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
|
||||||
use Chill\EventBundle\Export\Filter\EventDateFilter;
|
|
||||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
|
||||||
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
|
||||||
use Chill\MainBundle\Test\Export\AbstractFilterTest;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class EventDateFilterTest extends AbstractFilterTest
|
|
||||||
{
|
|
||||||
private RollingDateConverterInterface $rollingDateConverter;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$this->rollingDateConverter = self::getContainer()->get(RollingDateConverterInterface::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFilter()
|
|
||||||
{
|
|
||||||
return new EventDateFilter($this->rollingDateConverter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFormData()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'date_from' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
|
|
||||||
'date_to' => new RollingDate(RollingDate::T_TODAY),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQueryBuilders(): array
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
return [
|
|
||||||
$em->createQueryBuilder()
|
|
||||||
->select('event.id')
|
|
||||||
->from(Event::class, 'event'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,76 +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 Export\filters;
|
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
|
||||||
use Chill\EventBundle\Entity\EventType;
|
|
||||||
use Chill\EventBundle\Export\Filter\EventTypeFilter;
|
|
||||||
use Chill\MainBundle\Test\Export\AbstractFilterTest;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class EventTypeFilterTest extends AbstractFilterTest
|
|
||||||
{
|
|
||||||
private EventTypeFilter $filter;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
$this->filter = self::getContainer()->get(EventTypeFilter::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFilter(): EventTypeFilter|\Chill\MainBundle\Export\FilterInterface
|
|
||||||
{
|
|
||||||
return $this->filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFormData()
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
$array = $em->createQueryBuilder()
|
|
||||||
->from(EventType::class, 'et')
|
|
||||||
->select('et')
|
|
||||||
->getQuery()
|
|
||||||
->getResult();
|
|
||||||
|
|
||||||
$data = [];
|
|
||||||
|
|
||||||
foreach ($array as $a) {
|
|
||||||
$data[] = [
|
|
||||||
'types' => new ArrayCollection([$a]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQueryBuilders()
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
return [
|
|
||||||
$em->createQueryBuilder()
|
|
||||||
->select('event.id')
|
|
||||||
->from(Event::class, 'event'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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 Export\filters;
|
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
|
||||||
use Chill\EventBundle\Entity\Participation;
|
|
||||||
use Chill\EventBundle\Entity\Role;
|
|
||||||
use Chill\EventBundle\Export\Filter\RoleFilter;
|
|
||||||
use Chill\MainBundle\Test\Export\AbstractFilterTest;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @coversNothing
|
|
||||||
*/
|
|
||||||
class RoleFilterTest extends AbstractFilterTest
|
|
||||||
{
|
|
||||||
private RoleFilter $filter;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$this->filter = self::getContainer()->get(RoleFilter::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFilter()
|
|
||||||
{
|
|
||||||
return $this->filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFormData(): array
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
$array = $em->createQueryBuilder()
|
|
||||||
->from(Role::class, 'r')
|
|
||||||
->select('r')
|
|
||||||
->getQuery()
|
|
||||||
->setMaxResults(1)
|
|
||||||
->getResult();
|
|
||||||
|
|
||||||
$data = [];
|
|
||||||
|
|
||||||
foreach ($array as $a) {
|
|
||||||
$data[] = [
|
|
||||||
'roles' => new ArrayCollection([$a]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQueryBuilders()
|
|
||||||
{
|
|
||||||
self::bootKernel();
|
|
||||||
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
return [
|
|
||||||
$em->createQueryBuilder()
|
|
||||||
->select('event.id')
|
|
||||||
->from(Event::class, 'event'),
|
|
||||||
$em->createQueryBuilder()
|
|
||||||
->select('event_part')
|
|
||||||
->from(Participation::class, 'event_part'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -12,7 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\EventBundle\Tests\Repository;
|
namespace Chill\EventBundle\Tests\Repository;
|
||||||
|
|
||||||
use Chill\EventBundle\Repository\EventACLAwareRepository;
|
use Chill\EventBundle\Repository\EventACLAwareRepository;
|
||||||
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\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
services:
|
||||||
|
chill_event.event_voter:
|
||||||
|
class: Chill\EventBundle\Security\Authorization\EventVoter
|
||||||
|
arguments:
|
||||||
|
- "@security.access.decision_manager"
|
||||||
|
- "@chill.main.security.authorization.helper"
|
||||||
|
- "@logger"
|
||||||
|
tags:
|
||||||
|
- { name: security.voter }
|
||||||
|
|
||||||
|
chill_event.event_participation:
|
||||||
|
class: Chill\EventBundle\Security\Authorization\ParticipationVoter
|
||||||
|
arguments:
|
||||||
|
- "@security.access.decision_manager"
|
||||||
|
- "@chill.main.security.authorization.helper"
|
||||||
|
- "@logger"
|
||||||
|
tags:
|
||||||
|
- { name: security.voter }
|
@@ -1,41 +0,0 @@
|
|||||||
services:
|
|
||||||
_defaults:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
|
|
||||||
# indicators
|
|
||||||
|
|
||||||
Chill\EventBundle\Export\Export\CountEvents:
|
|
||||||
tags:
|
|
||||||
- { name: chill.export, alias: 'count_events' }
|
|
||||||
Chill\EventBundle\Export\Export\CountEventParticipations:
|
|
||||||
tags:
|
|
||||||
- { name: chill.export, alias: 'count_event_participants' }
|
|
||||||
|
|
||||||
# filters
|
|
||||||
|
|
||||||
Chill\EventBundle\Export\Filter\EventDateFilter:
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_filter, alias: 'event_date_filter' }
|
|
||||||
|
|
||||||
Chill\EventBundle\Export\Filter\EventTypeFilter:
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_filter, alias: 'event_type_filter' }
|
|
||||||
|
|
||||||
Chill\EventBundle\Export\Filter\RoleFilter:
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_filter, alias: 'role_filter' }
|
|
||||||
|
|
||||||
# aggregators
|
|
||||||
|
|
||||||
Chill\EventBundle\Export\Aggregator\EventTypeAggregator:
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_aggregator, alias: event_type_aggregator }
|
|
||||||
|
|
||||||
Chill\EventBundle\Export\Aggregator\EventDateAggregator:
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_aggregator, alias: event_date_aggregator }
|
|
||||||
|
|
||||||
Chill\EventBundle\Export\Aggregator\RoleAggregator:
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_aggregator, alias: role_aggregator }
|
|
@@ -1,14 +0,0 @@
|
|||||||
services:
|
|
||||||
Chill\EventBundle\Security\EventVoter:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
tags:
|
|
||||||
- { name: security.voter }
|
|
||||||
- { name: chill.role }
|
|
||||||
|
|
||||||
Chill\EventBundle\Security\ParticipationVoter:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
tags:
|
|
||||||
- { name: security.voter }
|
|
||||||
- { name: chill.role }
|
|
@@ -81,31 +81,9 @@ Pick an event: Choisir un événement
|
|||||||
Pick a type of event: Choisir un type d'événement
|
Pick a type of event: Choisir un type d'événement
|
||||||
Pick a moderator: Choisir un animateur
|
Pick a moderator: Choisir un animateur
|
||||||
|
|
||||||
# exports
|
|
||||||
Select a format: Choisir un format
|
Select a format: Choisir un format
|
||||||
Export: Exporter
|
Export: Exporter
|
||||||
|
|
||||||
Count events: Nombre d'événements
|
|
||||||
Count events by various parameters.: Compte le nombre d'événements selon divers critères
|
|
||||||
Exports of events: Exports d'événements
|
|
||||||
|
|
||||||
Filtered by event date: Filtrer par date d'événement
|
|
||||||
'Filtered by date of event: only between %date_from% and %date_to%': "Filtré par date d'événement: uniquement entre le %date_from% et le %date_to%"
|
|
||||||
Events after this date: Événements après cette date
|
|
||||||
Events before this date: Événements avant cette date
|
|
||||||
Filtered by event type: Filtrer par type d'événement
|
|
||||||
'Filtered by event type: only %list%': "Filtré par type: uniquement %list%"
|
|
||||||
Group event by date: Grouper par date d'événement
|
|
||||||
Group by event type: Grouper par type d'événement
|
|
||||||
|
|
||||||
Count event participants: Nombre de participations
|
|
||||||
Count participants to an event by various parameters.: Compte le nombre de participations selon divers critères
|
|
||||||
Exports of event participants: Exports de participations
|
|
||||||
'Filtered by participant roles: only %list%': "Filtré par rôles de participation: uniquement %list%"
|
|
||||||
Filter by participant roles: Filtrer par rôles de participation
|
|
||||||
Part roles: Rôles de participation
|
|
||||||
Group by participant role: Grouper par rôle de participation
|
|
||||||
|
|
||||||
|
|
||||||
Events configuration: Configuration des événements
|
Events configuration: Configuration des événements
|
||||||
Events configuration menu: Menu des événements
|
Events configuration menu: Menu des événements
|
||||||
|
@@ -1,40 +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\MainBundle\Command;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Service\Import\AddressReferenceLU;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
|
|
||||||
class LoadAddressesLUFromBDAddressCommand extends Command
|
|
||||||
{
|
|
||||||
protected static $defaultDescription = 'Import LUX addresses from BD addresses (see https://data.public.lu/fr/datasets/adresses-georeferencees-bd-adresses/)';
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
private readonly AddressReferenceLU $addressImporter,
|
|
||||||
) {
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function configure()
|
|
||||||
{
|
|
||||||
$this->setName('chill:main:address-ref-lux');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
||||||
{
|
|
||||||
$this->addressImporter->import();
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -632,7 +632,7 @@ class ExportController extends AbstractController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function rebuildRawData(?string $key): array
|
private function rebuildRawData(string $key): array
|
||||||
{
|
{
|
||||||
if (null === $key) {
|
if (null === $key) {
|
||||||
throw $this->createNotFoundException('key does not exists');
|
throw $this->createNotFoundException('key does not exists');
|
||||||
|
@@ -61,6 +61,8 @@ final class PermissionsGroupController extends AbstractController
|
|||||||
$form = $this->createAddRoleScopeForm($permissionsGroup);
|
$form = $this->createAddRoleScopeForm($permissionsGroup);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
dump($form->isSubmitted());
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$roleScope = $this->getPersistentRoleScopeBy(
|
$roleScope = $this->getPersistentRoleScopeBy(
|
||||||
$form['composed_role_scope']->getData()->getRole(),
|
$form['composed_role_scope']->getData()->getRole(),
|
||||||
|
@@ -216,13 +216,13 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
return $this->mainLocation;
|
return $this->mainLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMainScope(?\DateTimeImmutable $atDate = null): ?Scope
|
public function getMainScope(?\DateTimeImmutable $at = null): ?Scope
|
||||||
{
|
{
|
||||||
$atDate ??= new \DateTimeImmutable('now');
|
$at ??= new \DateTimeImmutable('now');
|
||||||
|
|
||||||
foreach ($this->scopeHistories as $scopeHistory) {
|
foreach ($this->scopeHistories as $scopeHistory) {
|
||||||
if ($atDate >= $scopeHistory->getStartDate() && (
|
if ($at >= $scopeHistory->getStartDate() && (
|
||||||
null === $scopeHistory->getEndDate() || $atDate < $scopeHistory->getEndDate()
|
null === $scopeHistory->getEndDate() || $at < $scopeHistory->getEndDate()
|
||||||
)) {
|
)) {
|
||||||
return $scopeHistory->getScope();
|
return $scopeHistory->getScope();
|
||||||
}
|
}
|
||||||
@@ -265,13 +265,13 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
return $this->salt;
|
return $this->salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserJob(?\DateTimeImmutable $atDate = null): ?UserJob
|
public function getUserJob(?\DateTimeImmutable $at = null): ?UserJob
|
||||||
{
|
{
|
||||||
$atDate ??= new \DateTimeImmutable('now');
|
$at ??= new \DateTimeImmutable('now');
|
||||||
|
|
||||||
foreach ($this->jobHistories as $jobHistory) {
|
foreach ($this->jobHistories as $jobHistory) {
|
||||||
if ($atDate >= $jobHistory->getStartDate() && (
|
if ($at >= $jobHistory->getStartDate() && (
|
||||||
null === $jobHistory->getEndDate() || $atDate < $jobHistory->getEndDate()
|
null === $jobHistory->getEndDate() || $at < $jobHistory->getEndDate()
|
||||||
)) {
|
)) {
|
||||||
return $jobHistory->getJob();
|
return $jobHistory->getJob();
|
||||||
}
|
}
|
||||||
@@ -285,11 +285,6 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
return $this->jobHistories;
|
return $this->jobHistories;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserScopeHistories(): Collection
|
|
||||||
{
|
|
||||||
return $this->scopeHistories;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ArrayCollection|UserJobHistory[]
|
* @return ArrayCollection|UserJobHistory[]
|
||||||
*/
|
*/
|
||||||
|
@@ -1,25 +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\MainBundle\Export;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform data from filter.
|
|
||||||
*
|
|
||||||
* This interface defines a method for transforming filter's form data before it is processed.
|
|
||||||
*
|
|
||||||
* You can implement this interface on @see{FilterInterface} or @see{AggregatorInterface}, to allow to transform existing data in saved exports
|
|
||||||
* and replace it with some default values, or new default values.
|
|
||||||
*/
|
|
||||||
interface DataTransformerInterface
|
|
||||||
{
|
|
||||||
public function transformData(?array $before): array;
|
|
||||||
}
|
|
@@ -32,9 +32,6 @@ interface FilterInterface extends ModifierInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default data, that can be use as "data" for the form.
|
* Get the default data, that can be use as "data" for the form.
|
||||||
*
|
|
||||||
* In case of adding new parameters to a filter, you can implement a @see{DataTransformerFilterInterface} to
|
|
||||||
* transforme the filters's data saved in an export to the desired state.
|
|
||||||
*/
|
*/
|
||||||
public function getFormDefaultData(): array;
|
public function getFormDefaultData(): array;
|
||||||
|
|
||||||
|
@@ -11,9 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Form\Type\Export;
|
namespace Chill\MainBundle\Form\Type\Export;
|
||||||
|
|
||||||
use Chill\MainBundle\Export\DataTransformerInterface;
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\CallbackTransformer;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@@ -21,7 +19,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
|
|
||||||
class AggregatorType extends AbstractType
|
class AggregatorType extends AbstractType
|
||||||
{
|
{
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function __construct() {}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
$exportManager = $options['export_manager'];
|
$exportManager = $options['export_manager'];
|
||||||
$aggregator = $exportManager->getAggregator($options['aggregator_alias']);
|
$aggregator = $exportManager->getAggregator($options['aggregator_alias']);
|
||||||
@@ -32,24 +32,17 @@ class AggregatorType extends AbstractType
|
|||||||
'required' => false,
|
'required' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$aggregatorFormBuilder = $builder->create('form', FormType::class, [
|
$filterFormBuilder = $builder->create('form', FormType::class, [
|
||||||
'compound' => true,
|
'compound' => true,
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'error_bubbling' => false,
|
'error_bubbling' => false,
|
||||||
]);
|
]);
|
||||||
$aggregator->buildForm($aggregatorFormBuilder);
|
$aggregator->buildForm($filterFormBuilder);
|
||||||
|
|
||||||
if ($aggregator instanceof DataTransformerInterface) {
|
$builder->add($filterFormBuilder);
|
||||||
$aggregatorFormBuilder->addViewTransformer(new CallbackTransformer(
|
|
||||||
fn (?array $data) => $data,
|
|
||||||
fn (?array $data) => $aggregator->transformData($data),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$builder->add($aggregatorFormBuilder);
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
}
|
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
{
|
||||||
$resolver->setRequired('aggregator_alias')
|
$resolver->setRequired('aggregator_alias')
|
||||||
->setRequired('export_manager')
|
->setRequired('export_manager')
|
||||||
|
@@ -11,10 +11,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Form\Type\Export;
|
namespace Chill\MainBundle\Form\Type\Export;
|
||||||
|
|
||||||
use Chill\MainBundle\Export\DataTransformerInterface;
|
|
||||||
use Chill\MainBundle\Export\FilterInterface;
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\CallbackTransformer;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@@ -43,13 +41,6 @@ class FilterType extends AbstractType
|
|||||||
]);
|
]);
|
||||||
$filter->buildForm($filterFormBuilder);
|
$filter->buildForm($filterFormBuilder);
|
||||||
|
|
||||||
if ($filter instanceof DataTransformerInterface) {
|
|
||||||
$filterFormBuilder->addViewTransformer(new CallbackTransformer(
|
|
||||||
fn (?array $data) => $data,
|
|
||||||
fn (?array $data) => $filter->transformData($data),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$builder->add($filterFormBuilder);
|
$builder->add($filterFormBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -43,14 +43,7 @@ export const download_report = (url, container) => {
|
|||||||
content = URL.createObjectURL(blob);
|
content = URL.createObjectURL(blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
const extensions = new Map();
|
extension = mime.getExtension(type);
|
||||||
extensions.set('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx');
|
|
||||||
extensions.set('application/vnd.oasis.opendocument.spreadsheet', 'ods');
|
|
||||||
extensions.set('application/vnd.ms-excel', 'xlsx');
|
|
||||||
extensions.set('text/csv', 'csv');
|
|
||||||
extensions.set('text/csv; charset=utf-8', 'csv');
|
|
||||||
|
|
||||||
extension = extensions.get(type);
|
|
||||||
|
|
||||||
link.appendChild(document.createTextNode(download_text));
|
link.appendChild(document.createTextNode(download_text));
|
||||||
link.classList.add("btn", "btn-action");
|
link.classList.add("btn", "btn-action");
|
||||||
@@ -62,7 +55,7 @@ export const download_report = (url, container) => {
|
|||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
container.appendChild(link);
|
container.appendChild(link);
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
console.error(error);
|
console.log(error);
|
||||||
var problem_text =
|
var problem_text =
|
||||||
document.createTextNode("Problem during download");
|
document.createTextNode("Problem during download");
|
||||||
|
|
||||||
|
@@ -12,5 +12,5 @@ window.addEventListener("DOMContentLoaded", function(e) {
|
|||||||
container = document.querySelector("#download_container")
|
container = document.querySelector("#download_container")
|
||||||
;
|
;
|
||||||
|
|
||||||
download_report(export_generate_url + query.toString(), container);
|
download_report(export_generate_url + "?" + query.toString(), container);
|
||||||
});
|
});
|
||||||
|
@@ -139,7 +139,7 @@ const postprocess = (html: string): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const convertMarkdownToHtml = (markdown: string): string => {
|
const convertMarkdownToHtml = (markdown: string): string => {
|
||||||
marked.use({'hooks': {postprocess, preprocess}, 'async': false});
|
marked.use({'hooks': {postprocess, preprocess}});
|
||||||
const rawHtml = marked(markdown) as string;
|
const rawHtml = marked(markdown) as string;
|
||||||
return rawHtml;
|
return rawHtml;
|
||||||
};
|
};
|
||||||
|
@@ -40,7 +40,7 @@
|
|||||||
{{ 'by_user'|trans ~ ' ' }}
|
{{ 'by_user'|trans ~ ' ' }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="user">
|
<span class="user">
|
||||||
{{ user|chill_entity_render_box({'at_date': comment.date}) }}
|
{{ user|chill_entity_render_box(options['user']) }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<span class="chill-entity entity-user">
|
<span class="chill-entity entity-user">
|
||||||
{{- user.label }}
|
{{- user.label }}
|
||||||
{%- if opts['user_job'] and user.userJob(opts['at_date']) is not null %}
|
{%- if opts['user_job'] and user.userJob(opts['at']) is not null %}
|
||||||
<span class="user-job">({{ user.userJob(opts['at_date']).label|localize_translatable_string }})</span>
|
<span class="user-job">({{ user.userJob(opts['at']).label|localize_translatable_string }})</span>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- if opts['main_scope'] and user.mainScope(opts['at_date']) is not null %}
|
{%- if opts['main_scope'] and user.mainScope(opts['at']) is not null %}
|
||||||
<span class="main-scope">({{ user.mainScope(opts['at_date']).name|localize_translatable_string }})</span>
|
<span class="main-scope">({{ user.mainScope(opts['at']).name|localize_translatable_string }})</span>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- if opts['absence'] and user.isAbsent %}
|
{%- if opts['absence'] and user.isAbsent %}
|
||||||
<span class="badge bg-danger rounded-pill" title="{{ 'absence.Absent'|trans|escape('html_attr') }}">{{ 'absence.A'|trans }}</span>
|
<span class="badge bg-danger rounded-pill" title="{{ 'absence.Absent'|trans|escape('html_attr') }}">{{ 'absence.A'|trans }}</span>
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
{% if menus|length > 0 %}
|
|
||||||
<li class="dropdown">
|
|
||||||
<a class="dropdown-toggle btn btn-sm btn-outline-primary"
|
|
||||||
href="#"
|
|
||||||
role="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false">
|
|
||||||
<i class="fa fa-flash"></i>
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
{% for menu in menus %}
|
|
||||||
<a class="dropdown-item"
|
|
||||||
href="{{ menu.uri }}"
|
|
||||||
><i class="fa fa-{{- menu.extras.icon }} fa-fw"></i>
|
|
||||||
{{ menu.label|trans }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
@@ -21,7 +21,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{% if not c.notification.isSystem %}
|
{% if not c.notification.isSystem %}
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ c.notification.sender|chill_entity_render_string({'at_date': c.notification.date}) }}
|
{{ c.notification.sender|chill_entity_render_string }}
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
|
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% for a in c.notification.addressees %}
|
{% for a in c.notification.addressees %}
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ a|chill_entity_render_string({'at_date': c.notification.date}) }}
|
{{ a|chill_entity_render_string }}
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for a in c.notification.addressesEmails %}
|
{% for a in c.notification.addressesEmails %}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{% extends "@ChillMain/layout.html.twig" %}
|
{% extends "@ChillMain/layout.html.twig" %}
|
||||||
|
|
||||||
{% block title 'notification.show notification from %sender%'|trans(
|
{% block title 'notification.show notification from %sender%'|trans(
|
||||||
{ '%sender%': notification.sender|chill_entity_render_string({'at_date': notification.date}) }
|
{ '%sender%': notification.sender|chill_entity_render_string }
|
||||||
) ~ ' ' ~ notification.title %}
|
) ~ ' ' ~ notification.title %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
@@ -31,14 +31,14 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
{{ 'By'|trans }}
|
{{ 'By'|trans }}
|
||||||
{{ step.previous.transitionBy|chill_entity_render_box({'at_date': step.previous.transitionAt }) }},
|
{{ step.previous.transitionBy|chill_entity_render_box }},
|
||||||
{{ step.previous.transitionAt|format_datetime('short', 'short') }}
|
{{ step.previous.transitionAt|format_datetime('short', 'short') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-4">{{ 'workflow.Created by'|trans }}</div>
|
<div class="col-sm-4">{{ 'workflow.Created by'|trans }}</div>
|
||||||
<div class="col-sm-8">{{ step.entityWorkflow.createdBy|chill_entity_render_box({'at_date': step.entityWorkflow.createdAt}) }}</div>
|
<div class="col-sm-8">{{ step.entityWorkflow.createdBy|chill_entity_render_box }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-4">{{ 'Le'|trans }}</div>
|
<div class="col-sm-4">{{ 'Le'|trans }}</div>
|
||||||
@@ -110,8 +110,8 @@
|
|||||||
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
|
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
|
||||||
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for u in entity_workflow.currentStepChained.destUserByAccessKey %}
|
{% for u in entity_workflow.currentStep.destUserByAccessKey %}
|
||||||
<li>{{ u|chill_entity_render_box({'at_date': entity_workflow.currentStepChained.previous.transitionAt }) }}</li>
|
<li>{{ u|chill_entity_render_box }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@@ -42,7 +42,7 @@
|
|||||||
<div class="item-col" style="width: inherit;">
|
<div class="item-col" style="width: inherit;">
|
||||||
{% if step.transitionBy is not null %}
|
{% if step.transitionBy is not null %}
|
||||||
<div>
|
<div>
|
||||||
{{ step.transitionBy|chill_entity_render_box({'at_date': step.transitionAt}) }}
|
{{ step.transitionBy|chill_entity_render_box }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }} : </b></p>
|
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }} : </b></p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for u in step.destUser %}
|
{% for u in step.destUser %}
|
||||||
<li>{{ u|chill_entity_render_box({'at_date': step.previous.transitionAt}) }}</li>
|
<li>{{ u|chill_entity_render_box }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<p><b>{{ 'workflow.Users put in Cc'|trans }} : </b></p>
|
<p><b>{{ 'workflow.Users put in Cc'|trans }} : </b></p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for u in step.ccUser %}
|
{% for u in step.ccUser %}
|
||||||
<li>{{ u|chill_entity_render_box({'at_date': step.previous.transitionAt}) }}</li>
|
<li>{{ u|chill_entity_render_box }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for u in step.destUserByAccessKey %}
|
{% for u in step.destUserByAccessKey %}
|
||||||
<li>{{ u|chill_entity_render_box({'at_date': step.previous.transitionAt}) }}</li>
|
<li>{{ u|chill_entity_render_box }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
{% if step.previous is not null %}
|
{% if step.previous is not null %}
|
||||||
<li>
|
<li>
|
||||||
<span class="item-key">{{ 'By'|trans ~ ' : ' }}</span>
|
<span class="item-key">{{ 'By'|trans ~ ' : ' }}</span>
|
||||||
<b>{{ step.previous.transitionBy|chill_entity_render_box({'at_date': step.previous.transitionAt }) }}</b>
|
<b>{{ step.previous.transitionBy|chill_entity_render_box }}</b>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
|
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
|
||||||
@@ -12,19 +12,19 @@
|
|||||||
<li>
|
<li>
|
||||||
<span class="item-key">{{ 'workflow.For'|trans ~ ' : ' }}</span>
|
<span class="item-key">{{ 'workflow.For'|trans ~ ' : ' }}</span>
|
||||||
<b>
|
<b>
|
||||||
{% for d in step.destUser %}{{ d|chill_entity_render_string({'at_date': step.previous.transitionAt}) }}{% if not loop.last %}, {% endif %}{% endfor %}
|
{% for d in step.destUser %}{{ d|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}
|
||||||
</b>
|
</b>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="item-key">{{ 'workflow.Cc'|trans ~ ' : ' }}</span>
|
<span class="item-key">{{ 'workflow.Cc'|trans ~ ' : ' }}</span>
|
||||||
<b>
|
<b>
|
||||||
{% for u in step.ccUser %}{{ u|chill_entity_render_string({'at_date': step.previous.transitionAt }) }}{% if not loop.last %}, {% endif %}{% endfor %}
|
{% for u in step.ccUser %}{{ u|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}
|
||||||
</b>
|
</b>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>
|
<li>
|
||||||
<span class="item-key">{{ 'workflow.Created by'|trans ~ ' : ' }}</span>
|
<span class="item-key">{{ 'workflow.Created by'|trans ~ ' : ' }}</span>
|
||||||
<b>{{ step.entityWorkflow.createdBy|chill_entity_render_box({'at_date': step.entityWorkflow.createdAt }) }}</b>
|
<b>{{ step.entityWorkflow.createdBy|chill_entity_render_box }}</b>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
|
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
|
||||||
|
@@ -11,6 +11,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Routing;
|
namespace Chill\MainBundle\Routing;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
use Twig\Extension\AbstractExtension;
|
use Twig\Extension\AbstractExtension;
|
||||||
use Twig\TwigFunction;
|
use Twig\TwigFunction;
|
||||||
@@ -18,8 +20,10 @@ use Twig\TwigFunction;
|
|||||||
/**
|
/**
|
||||||
* Add the filter 'chill_menu'.
|
* Add the filter 'chill_menu'.
|
||||||
*/
|
*/
|
||||||
class MenuTwig extends AbstractExtension
|
class MenuTwig extends AbstractExtension implements ContainerAwareInterface
|
||||||
{
|
{
|
||||||
|
private ?ContainerInterface $container = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the default parameters for chillMenu.
|
* the default parameters for chillMenu.
|
||||||
*
|
*
|
||||||
@@ -80,4 +84,9 @@ class MenuTwig extends AbstractExtension
|
|||||||
{
|
{
|
||||||
return 'chill_menu';
|
return 'chill_menu';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setContainer(?ContainerInterface $container = null)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -52,7 +52,7 @@ class EntityWorkflowStepNormalizer implements NormalizerAwareInterface, Normaliz
|
|||||||
$data['transitionPreviousBy'] = $this->normalizer->normalize(
|
$data['transitionPreviousBy'] = $this->normalizer->normalize(
|
||||||
$previous->getTransitionBy(),
|
$previous->getTransitionBy(),
|
||||||
$format,
|
$format,
|
||||||
[...$context, UserNormalizer::AT_DATE => $previous->getTransitionAt()]
|
$context
|
||||||
);
|
);
|
||||||
$data['transitionPreviousAt'] = $this->normalizer->normalize(
|
$data['transitionPreviousAt'] = $this->normalizer->normalize(
|
||||||
$previous->getTransitionAt(),
|
$previous->getTransitionAt(),
|
||||||
|
@@ -45,7 +45,7 @@ class NotificationNormalizer implements NormalizerAwareInterface, NormalizerInte
|
|||||||
'message' => $object->getMessage(),
|
'message' => $object->getMessage(),
|
||||||
'relatedEntityClass' => $object->getRelatedEntityClass(),
|
'relatedEntityClass' => $object->getRelatedEntityClass(),
|
||||||
'relatedEntityId' => $object->getRelatedEntityId(),
|
'relatedEntityId' => $object->getRelatedEntityId(),
|
||||||
'sender' => $this->normalizer->normalize($object->getSender(), $format, [...$context, UserNormalizer::AT_DATE => $object->getDate()]),
|
'sender' => $this->normalizer->normalize($object->getSender(), $format, $context),
|
||||||
'title' => $object->getTitle(),
|
'title' => $object->getTitle(),
|
||||||
'entity' => null !== $entity ? $this->normalizer->normalize($entity, $format, $context) : null,
|
'entity' => null !== $entity ? $this->normalizer->normalize($entity, $format, $context) : null,
|
||||||
];
|
];
|
||||||
|
@@ -19,7 +19,6 @@ use Chill\MainBundle\Entity\User;
|
|||||||
use Chill\MainBundle\Entity\UserJob;
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
use Chill\MainBundle\Templating\Entity\UserRender;
|
use Chill\MainBundle\Templating\Entity\UserRender;
|
||||||
use libphonenumber\PhoneNumber;
|
use libphonenumber\PhoneNumber;
|
||||||
use Symfony\Component\Clock\ClockInterface;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
|
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||||
@@ -28,8 +27,6 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
|
|||||||
{
|
{
|
||||||
use NormalizerAwareTrait;
|
use NormalizerAwareTrait;
|
||||||
|
|
||||||
final public const AT_DATE = 'chill:user:at_date';
|
|
||||||
|
|
||||||
final public const NULL_USER = [
|
final public const NULL_USER = [
|
||||||
'type' => 'user',
|
'type' => 'user',
|
||||||
'id' => '',
|
'id' => '',
|
||||||
@@ -41,16 +38,10 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
|
|||||||
'isAbsent' => false,
|
'isAbsent' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(private readonly UserRender $userRender, private readonly ClockInterface $clock) {}
|
public function __construct(private readonly UserRender $userRender) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed|null $format
|
|
||||||
*
|
|
||||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
|
|
||||||
*/
|
|
||||||
public function normalize($object, $format = null, array $context = [])
|
public function normalize($object, $format = null, array $context = [])
|
||||||
{
|
{
|
||||||
/** @var array{"chill:user:at_date"?: \DateTimeImmutable|\DateTime} $context */
|
|
||||||
/** @var User $object */
|
/** @var User $object */
|
||||||
$userJobContext = array_merge(
|
$userJobContext = array_merge(
|
||||||
$context,
|
$context,
|
||||||
@@ -81,23 +72,18 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
|
|||||||
return [...self::NULL_USER, 'phonenumber' => $this->normalizer->normalize(null, $format, $phonenumberContext), 'civility' => $this->normalizer->normalize(null, $format, $civilityContext), 'user_job' => $this->normalizer->normalize(null, $format, $userJobContext), 'main_center' => $this->normalizer->normalize(null, $format, $centerContext), 'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext), 'current_location' => $this->normalizer->normalize(null, $format, $locationContext), 'main_location' => $this->normalizer->normalize(null, $format, $locationContext)];
|
return [...self::NULL_USER, 'phonenumber' => $this->normalizer->normalize(null, $format, $phonenumberContext), 'civility' => $this->normalizer->normalize(null, $format, $civilityContext), 'user_job' => $this->normalizer->normalize(null, $format, $userJobContext), 'main_center' => $this->normalizer->normalize(null, $format, $centerContext), 'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext), 'current_location' => $this->normalizer->normalize(null, $format, $locationContext), 'main_location' => $this->normalizer->normalize(null, $format, $locationContext)];
|
||||||
}
|
}
|
||||||
|
|
||||||
$at = $context[self::AT_DATE] ?? $this->clock->now();
|
|
||||||
if ($at instanceof \DateTime) {
|
|
||||||
$at = \DateTimeImmutable::createFromMutable($at);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'type' => 'user',
|
'type' => 'user',
|
||||||
'id' => $object->getId(),
|
'id' => $object->getId(),
|
||||||
'username' => $object->getUsername(),
|
'username' => $object->getUsername(),
|
||||||
'text' => $this->userRender->renderString($object, ['at_date' => $at]),
|
'text' => $this->userRender->renderString($object, []),
|
||||||
'text_without_absent' => $this->userRender->renderString($object, ['absence' => false]),
|
'text_without_absent' => $this->userRender->renderString($object, ['absence' => false]),
|
||||||
'label' => $object->getLabel(),
|
'label' => $object->getLabel(),
|
||||||
'email' => (string) $object->getEmail(),
|
'email' => (string) $object->getEmail(),
|
||||||
'phonenumber' => $this->normalizer->normalize($object->getPhonenumber(), $format, $phonenumberContext),
|
'phonenumber' => $this->normalizer->normalize($object->getPhonenumber(), $format, $phonenumberContext),
|
||||||
'user_job' => $this->normalizer->normalize($object->getUserJob($at), $format, $userJobContext),
|
'user_job' => $this->normalizer->normalize($object->getUserJob(), $format, $userJobContext),
|
||||||
'main_center' => $this->normalizer->normalize($object->getMainCenter(), $format, $centerContext),
|
'main_center' => $this->normalizer->normalize($object->getMainCenter(), $format, $centerContext),
|
||||||
'main_scope' => $this->normalizer->normalize($object->getMainScope($at), $format, $scopeContext),
|
'main_scope' => $this->normalizer->normalize($object->getMainScope(), $format, $scopeContext),
|
||||||
'isAbsent' => $object->isAbsent(),
|
'isAbsent' => $object->isAbsent(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -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\MainBundle\Service\Import;
|
|
||||||
|
|
||||||
use League\Csv\Reader;
|
|
||||||
use League\Csv\Statement;
|
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
||||||
|
|
||||||
class AddressReferenceLU
|
|
||||||
{
|
|
||||||
private const RELEASE = 'https://data.public.lu/fr/datasets/r/5cadc5b8-6a7d-4283-87bc-f9e58dd771f7';
|
|
||||||
|
|
||||||
public function __construct(private readonly HttpClientInterface $client, private readonly AddressReferenceBaseImporter $addressBaseImporter, private readonly PostalCodeBaseImporter $postalCodeBaseImporter, private readonly AddressToReferenceMatcher $addressToReferenceMatcher) {}
|
|
||||||
|
|
||||||
public function import(): void
|
|
||||||
{
|
|
||||||
$downloadUrl = self::RELEASE;
|
|
||||||
|
|
||||||
$response = $this->client->request('GET', $downloadUrl);
|
|
||||||
|
|
||||||
if (200 !== $response->getStatusCode()) {
|
|
||||||
throw new \Exception('Could not download CSV: '.$response->getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
$file = tmpfile();
|
|
||||||
|
|
||||||
foreach ($this->client->stream($response) as $chunk) {
|
|
||||||
fwrite($file, $chunk->getContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek($file, 0);
|
|
||||||
|
|
||||||
$csv = Reader::createFromStream($file);
|
|
||||||
$csv->setDelimiter(';');
|
|
||||||
$csv->setHeaderOffset(0);
|
|
||||||
|
|
||||||
$this->process_postal_code($csv);
|
|
||||||
|
|
||||||
$this->process_address($csv);
|
|
||||||
|
|
||||||
$this->addressToReferenceMatcher->checkAddressesMatchingReferences();
|
|
||||||
|
|
||||||
fclose($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function process_address(Reader $csv): void
|
|
||||||
{
|
|
||||||
$stmt = Statement::create()->process($csv);
|
|
||||||
foreach ($stmt as $record) {
|
|
||||||
$this->addressBaseImporter->importAddress(
|
|
||||||
$record['id_geoportail'],
|
|
||||||
$record['code_postal'],
|
|
||||||
$record['code_postal'],
|
|
||||||
$record['rue'],
|
|
||||||
$record['numero'],
|
|
||||||
'bd-addresses.lux',
|
|
||||||
(float) $record['lat_wgs84'],
|
|
||||||
(float) $record['lon_wgs84'],
|
|
||||||
4326
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addressBaseImporter->finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function process_postal_code(Reader $csv): void
|
|
||||||
{
|
|
||||||
$stmt = Statement::create()->process($csv);
|
|
||||||
$arr_postal_codes = [];
|
|
||||||
foreach ($stmt as $record) {
|
|
||||||
if (false === \array_key_exists($record['code_postal'], $arr_postal_codes)) {
|
|
||||||
$this->postalCodeBaseImporter->importCode(
|
|
||||||
'LU',
|
|
||||||
trim((string) $record['localite']),
|
|
||||||
trim((string) $record['code_postal']),
|
|
||||||
trim((string) $record['code_postal']),
|
|
||||||
'bd-addresses.lux',
|
|
||||||
(float) $record['lat_wgs84'],
|
|
||||||
(float) $record['lon_wgs84'],
|
|
||||||
4326
|
|
||||||
);
|
|
||||||
$arr_postal_codes[$record['code_postal']] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user