mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-30 02:25:00 +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).
|
||||
|
||||
|
||||
## 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
|
||||
### 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
|
||||
|
@@ -19,6 +19,7 @@
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.13.0",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"graylog2/gelf-php": "^1.5",
|
||||
"knplabs/knp-menu-bundle": "^3.0",
|
||||
"knplabs/knp-time-bundle": "^1.12",
|
||||
"knpuniversity/oauth2-client-bundle": "^2.10",
|
||||
@@ -31,6 +32,7 @@
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"ramsey/uuid-doctrine": "^1.7",
|
||||
"sensio/framework-extra-bundle": "^5.5",
|
||||
"smalot/pdfparser": "^2.10",
|
||||
"spomky-labs/base64url": "^2.0",
|
||||
"symfony/asset": "^5.4",
|
||||
"symfony/browser-kit": "^5.4",
|
||||
@@ -91,12 +93,11 @@
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpunit/phpunit": "^10.5.24",
|
||||
"phpunit/phpunit": ">= 7.5",
|
||||
"rector/rector": "^1.1.0",
|
||||
"symfony/debug-bundle": "^5.4",
|
||||
"symfony/dotenv": "^5.4",
|
||||
"symfony/maker-bundle": "^1.20",
|
||||
"symfony/phpunit-bridge": "^7.1",
|
||||
"symfony/runtime": "^5.4",
|
||||
"symfony/stopwatch": "^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
|
||||
# install chill and some dependencies
|
||||
# TODO fix the suffix "alpha1" and replace by ^3.0.0 when version 3.0.0 will be released
|
||||
symfony composer require chill-project/chill-bundles v3.0.0-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.
|
||||
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
|
||||
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
|
||||
by the symfony server, from the docker compose services.
|
||||
@@ -110,15 +110,10 @@ you can either:
|
||||
.. code-block:: env
|
||||
|
||||
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
|
||||
# note: if you copy-paste the line above, the password will be "admin".
|
||||
|
||||
- add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env,
|
||||
not the password in clear text).
|
||||
|
||||
- set up the jwt authentication bundle
|
||||
|
||||
Some environment variables are available for the JWT authentication bundle in the :code:`.env` file.
|
||||
|
||||
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
|
||||
# prepare some views
|
||||
symfony console chill:db:sync-views
|
||||
# generate jwt token, required for some api features (webdav access, ...)
|
||||
symfony console lexik:jwt:generate-keypair
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -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:
|
||||
|
||||
- add fixtures
|
||||
- add profiler and debug bundle
|
||||
- add profiler and var-dumper to debug
|
||||
|
||||
Install fixtures
|
||||
****************
|
||||
@@ -186,7 +179,7 @@ Install fixtures
|
||||
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
|
||||
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`.
|
||||
|
||||
@@ -199,7 +192,7 @@ Add web profiler and debugger
|
||||
|
||||
.. 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
|
||||
************************
|
||||
|
@@ -46,11 +46,9 @@
|
||||
"@fullcalendar/vue3": "^6.1.4",
|
||||
"@popperjs/core": "^2.9.2",
|
||||
"@types/leaflet": "^1.9.3",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"dropzone": "^5.7.6",
|
||||
"es6-promise": "^4.2.8",
|
||||
"leaflet": "^1.7.1",
|
||||
"marked": "^12.0.2",
|
||||
"masonry-layout": "^4.2.2",
|
||||
"mime": "^4.0.0",
|
||||
"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 list">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -87,7 +87,7 @@
|
||||
<li>
|
||||
{% if bloc.type == '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>
|
||||
{% else %}
|
||||
{{ _self.insert_onthefly(bloc.type, item) }}
|
||||
@@ -114,7 +114,7 @@
|
||||
<li>
|
||||
{% if bloc.type == '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>
|
||||
{% else %}
|
||||
{{ _self.insert_onthefly(bloc.type, item) }}
|
||||
@@ -142,7 +142,7 @@
|
||||
<span class="wl-item">
|
||||
{% if bloc.type == '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' %}
|
||||
{% set invite = entity.inviteForUser(item) %}
|
||||
{% if invite is not null %}
|
||||
|
@@ -41,7 +41,7 @@
|
||||
{% if activity.user and t.userVisible %}
|
||||
<li>
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
|
@@ -37,7 +37,7 @@
|
||||
{%- if entity.user is not null %}
|
||||
<dt class="inline">{{ 'Referrer'|trans|capitalize }}</dt>
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
|
@@ -145,7 +145,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
|
||||
throw new \RuntimeException('Could not determine context of activity.');
|
||||
}
|
||||
} 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)) {
|
||||
return false;
|
||||
}
|
||||
|
@@ -77,18 +77,6 @@ Choose a type: Choisir un type
|
||||
4 hours: 4 heures
|
||||
4 hours 30: 4 heures 30
|
||||
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
|
||||
Persons in accompanying course: Usagers du parcours
|
||||
Third persons: Tiers non-pro.
|
||||
@@ -222,7 +210,6 @@ Documents label: Libellé du champ Documents
|
||||
# activity type category admin
|
||||
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 in accompanying course: Créer un échange dans le parcours
|
||||
|
||||
# activity delete
|
||||
Remove activity: Supprimer un échange
|
||||
|
@@ -49,13 +49,13 @@
|
||||
<li>
|
||||
<span>
|
||||
<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>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<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>
|
||||
</li>
|
||||
|
@@ -18,10 +18,10 @@
|
||||
<dd>{{ entity.type|chill_entity_render_box }}</dd>
|
||||
|
||||
<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>
|
||||
<dd>{{ entity.agent|chill_entity_render_box({'at_date': entity.date}) }}</dd>
|
||||
<dd>{{ entity.agent }}</dd>
|
||||
|
||||
<dt class="inline">{{ 'Asideactivity location'|trans }}</dt>
|
||||
{%- if entity.location.name is defined -%}
|
||||
|
@@ -72,21 +72,21 @@ days: jours
|
||||
1 hour 30: 1 heure 30
|
||||
1 hour 45: 1 heure 45
|
||||
2 hours: 2 heures
|
||||
2 hours 30: 2 heures 30
|
||||
2 hours 30: 2 heure 30
|
||||
3 hours: 3 heures
|
||||
3 hours 30: 3 heures 30
|
||||
3 hours 30: 3 heure 30
|
||||
4 hours: 4 heures
|
||||
4 hours 30: 4 heures 30
|
||||
4 hours 30: 4 heure 30
|
||||
5 hours: 5 heures
|
||||
5 hours 30: 5 heures 30
|
||||
5 hours 30: 5 heure 30
|
||||
6 hours: 6 heures
|
||||
6 hours 30: 6 heures 30
|
||||
6 hours 30: 6 heure 30
|
||||
7 hours: 7 heures
|
||||
7 hours 30: 7 heures 30
|
||||
7 hours 30: 7 heure 30
|
||||
8 hours: 8 heures
|
||||
8 hours 30: 8 heures 30
|
||||
8 hours 30: 8 heure 30
|
||||
9 hours: 9 heures
|
||||
9 hours 30: 9 heures 30
|
||||
9 hours 30: 9 heure 30
|
||||
10 hours: 10 heures
|
||||
1/2 day: 1/2 jour
|
||||
1 day: 1 jour
|
||||
|
@@ -440,16 +440,6 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente
|
||||
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
|
||||
{
|
||||
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/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">
|
||||
<ul class="list-content">
|
||||
{% 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 %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -132,7 +132,7 @@
|
||||
<li class="cancel">
|
||||
<span class="createdBy">
|
||||
{{ '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>
|
||||
</li>
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
||||
|
@@ -89,7 +89,7 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
|
||||
switch ($attribute) {
|
||||
case self::SEE:
|
||||
case self::CREATE:
|
||||
if (AccompanyingPeriod::STEP_DRAFT === $subject->getStep() || AccompanyingPeriod::STEP_CLOSED === $subject->getStep()) {
|
||||
if (AccompanyingPeriod::STEP_DRAFT === $subject->getStep()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -26,7 +26,6 @@ The calendar item has been successfully removed.: Le rendez-vous a été supprim
|
||||
From the day: Du
|
||||
to the day: au
|
||||
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 not send SMS: Aucun SMS de rappel ne sera 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;
|
||||
}
|
||||
|
||||
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 */
|
||||
if ($viewData->getFilename() !== $forms['stored_object']->getData()['filename']) {
|
||||
// we want to keep the previous history
|
||||
$viewData->saveHistory();
|
||||
// we do not want to erase the previous object
|
||||
$viewData = new StoredObject();
|
||||
}
|
||||
|
||||
$viewData->setFilename($forms['stored_object']->getData()['filename']);
|
||||
|
@@ -4,13 +4,13 @@
|
||||
Actions
|
||||
</button>
|
||||
<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>
|
||||
</li>
|
||||
<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>
|
||||
</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>
|
||||
</li>
|
||||
<li v-if="props.canDownload">
|
||||
|
@@ -13,7 +13,7 @@ import {reactive} from "vue";
|
||||
import {StoredObject, StoredObjectCreated} from "../../types";
|
||||
|
||||
interface ConvertButtonConfig {
|
||||
storedObject: StoredObject,
|
||||
storedObject: StoredObject|StoredObjectCreated,
|
||||
classes: { [key: string]: boolean},
|
||||
filename?: string,
|
||||
};
|
||||
|
@@ -11,7 +11,7 @@ import {build_wopi_editor_link} from "./helpers";
|
||||
import {StoredObject, StoredObjectCreated, WopiEditButtonExecutableBeforeLeaveFunction} from "../../types";
|
||||
|
||||
interface WopiEditButtonConfig {
|
||||
storedObject: StoredObject,
|
||||
storedObject: StoredObject|StoredObjectCreated,
|
||||
returnPath?: string,
|
||||
classes: {[k: string] : boolean},
|
||||
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"}
|
||||
JSON];
|
||||
$model = new StoredObject();
|
||||
$originalObjectId = spl_object_hash($model);
|
||||
$originalObjectId = spl_object_id($model);
|
||||
$form = $this->factory->create(StoredObjectType::class, $model, ['has_title' => true]);
|
||||
|
||||
$form->submit($formData);
|
||||
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
$model = $form->getData();
|
||||
$this->assertEquals($originalObjectId, spl_object_hash($model));
|
||||
$this->assertNotEquals($originalObjectId, spl_object_hash($model));
|
||||
$this->assertEquals('abcdef', $model->getFilename());
|
||||
$this->assertEquals([10, 15, 20, 30], $model->getIv());
|
||||
$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\Form\EventType;
|
||||
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\User;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
@@ -418,6 +418,7 @@ final class EventController extends AbstractController
|
||||
$builder->add('event_id', HiddenType::class, [
|
||||
'data' => $event->getId(),
|
||||
]);
|
||||
dump($event->getId());
|
||||
|
||||
return $builder->getForm();
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ use Chill\EventBundle\Entity\Event;
|
||||
use Chill\EventBundle\Entity\Participation;
|
||||
use Chill\EventBundle\Form\ParticipationType;
|
||||
use Chill\EventBundle\Repository\EventRepository;
|
||||
use Chill\EventBundle\Security\ParticipationVoter;
|
||||
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
@@ -11,8 +11,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\EventBundle\DependencyInjection;
|
||||
|
||||
use Chill\EventBundle\Security\EventVoter;
|
||||
use Chill\EventBundle\Security\ParticipationVoter;
|
||||
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
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->load('services.yaml');
|
||||
$loader->load('services/security.yaml');
|
||||
$loader->load('services/authorization.yaml');
|
||||
$loader->load('services/fixtures.yaml');
|
||||
$loader->load('services/forms.yaml');
|
||||
$loader->load('services/repositories.yaml');
|
||||
$loader->load('services/search.yaml');
|
||||
$loader->load('services/timeline.yaml');
|
||||
$loader->load('services/export.yaml');
|
||||
}
|
||||
|
||||
/** (non-PHPdoc).
|
||||
|
@@ -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;
|
||||
|
||||
use Chill\EventBundle\Security\EventVoter;
|
||||
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
@@ -11,7 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\EventBundle\Menu;
|
||||
|
||||
use Chill\EventBundle\Security\EventVoter;
|
||||
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
@@ -13,7 +13,7 @@ namespace Chill\EventBundle\Repository;
|
||||
|
||||
use Chill\EventBundle\Entity\Event;
|
||||
use Chill\EventBundle\Entity\Participation;
|
||||
use Chill\EventBundle\Security\EventVoter;
|
||||
use Chill\EventBundle\Security\Authorization\EventVoter;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
@@ -12,57 +12,13 @@ declare(strict_types=1);
|
||||
namespace Chill\EventBundle\Repository;
|
||||
|
||||
use Chill\EventBundle\Entity\Role;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
readonly class RoleRepository implements ObjectRepository
|
||||
class RoleRepository extends ServiceEntityRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, private TranslatableStringHelper $translatableStringHelper)
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(Role::class);
|
||||
}
|
||||
|
||||
public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder
|
||||
{
|
||||
return $this->repository->createQueryBuilder($alias, $indexBy);
|
||||
}
|
||||
|
||||
public function find($id)
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findAllActive(): array
|
||||
{
|
||||
$roles = $this->repository->findBy(['active' => true]);
|
||||
|
||||
usort($roles, fn (Role $a, Role $b) => $this->translatableStringHelper->localize($a->getName()) <=> $this->translatableStringHelper->localize($b->getName()));
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria)
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return Role::class;
|
||||
parent::__construct($registry, Role::class);
|
||||
}
|
||||
}
|
||||
|
@@ -9,19 +9,18 @@ declare(strict_types=1);
|
||||
* 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\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||
|
||||
/**
|
||||
* Description of EventVoter.
|
||||
@@ -43,46 +42,61 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
|
||||
|
||||
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(
|
||||
private readonly AuthorizationHelper $authorizationHelper,
|
||||
private readonly LoggerInterface $logger,
|
||||
VoterHelperFactoryInterface $voterHelperFactory
|
||||
AccessDecisionManagerInterface $accessDecisionManager,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->voterHelper = $voterHelperFactory
|
||||
->generate(self::class)
|
||||
->addCheckFor(null, [self::SEE])
|
||||
->addCheckFor(Event::class, [...self::ROLES])
|
||||
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
||||
->addCheckFor(Center::class, [self::STATS])
|
||||
->build();
|
||||
$this->accessDecisionManager = $accessDecisionManager;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function getRoles(): array
|
||||
{
|
||||
return [...self::ROLES, self::STATS];
|
||||
return self::ROLES;
|
||||
}
|
||||
|
||||
public function getRolesWithHierarchy(): array
|
||||
{
|
||||
return [
|
||||
'Event' => $this->getRoles(),
|
||||
'Event' => self::ROLES,
|
||||
];
|
||||
}
|
||||
|
||||
public function getRolesWithoutScope(): array
|
||||
{
|
||||
return [self::ROLES, self::STATS];
|
||||
return [];
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$this->logger->debug(sprintf('Voting from %s class', self::class));
|
||||
@@ -104,5 +118,15 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
|
||||
->getReachableCenters($token->getUser(), $attribute);
|
||||
|
||||
return \count($centers) > 0;
|
||||
|
||||
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->authorizationHelper->userHasAccess(
|
||||
$token->getUser(),
|
||||
$subject,
|
||||
$attribute
|
||||
);
|
||||
}
|
||||
}
|
@@ -9,19 +9,18 @@ declare(strict_types=1);
|
||||
* 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\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||
|
||||
class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
|
||||
{
|
||||
@@ -40,48 +39,58 @@ class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierar
|
||||
|
||||
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(
|
||||
private readonly AuthorizationHelper $authorizationHelper,
|
||||
private readonly LoggerInterface $logger,
|
||||
VoterHelperFactoryInterface $voterHelperFactory
|
||||
AccessDecisionManagerInterface $accessDecisionManager,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->voterHelper = $voterHelperFactory
|
||||
->generate(self::class)
|
||||
->addCheckFor(null, [self::SEE])
|
||||
->addCheckFor(Participation::class, [...self::ROLES])
|
||||
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
||||
->addCheckFor(Center::class, [self::STATS])
|
||||
->build();
|
||||
$this->accessDecisionManager = $accessDecisionManager;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function getRoles(): array
|
||||
{
|
||||
return [...self::ROLES, self::STATS];
|
||||
return self::ROLES;
|
||||
}
|
||||
|
||||
public function getRolesWithHierarchy(): array
|
||||
{
|
||||
return [
|
||||
'Participation' => $this->getRoles(),
|
||||
'Event' => self::ROLES,
|
||||
];
|
||||
}
|
||||
|
||||
public function getRolesWithoutScope(): array
|
||||
{
|
||||
return [self::ROLES, self::STATS];
|
||||
return [];
|
||||
}
|
||||
|
||||
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 Participation $subject
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -106,5 +115,15 @@ class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierar
|
||||
->getReachableCenters($token->getUser(), $attribute);
|
||||
|
||||
return \count($centers) > 0;
|
||||
|
||||
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->authorizationHelper->userHasAccess(
|
||||
$token->getUser(),
|
||||
$subject,
|
||||
$attribute
|
||||
);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
||||
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\Scope;
|
||||
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 moderator: Choisir un animateur
|
||||
|
||||
# exports
|
||||
Select a format: Choisir un format
|
||||
Export: Exporter
|
||||
|
||||
Count events: Nombre d'événements
|
||||
Count events by various parameters.: Compte le nombre d'événements selon divers critères
|
||||
Exports of events: Exports d'événements
|
||||
|
||||
Filtered by event date: Filtrer par date d'événement
|
||||
'Filtered by date of event: only between %date_from% and %date_to%': "Filtré par date d'événement: uniquement entre le %date_from% et le %date_to%"
|
||||
Events after this date: Événements après cette date
|
||||
Events before this date: Événements avant cette date
|
||||
Filtered by event type: Filtrer par type d'événement
|
||||
'Filtered by event type: only %list%': "Filtré par type: uniquement %list%"
|
||||
Group event by date: Grouper par date d'événement
|
||||
Group by event type: Grouper par type d'événement
|
||||
|
||||
Count event participants: Nombre de participations
|
||||
Count participants to an event by various parameters.: Compte le nombre de participations selon divers critères
|
||||
Exports of event participants: Exports de participations
|
||||
'Filtered by participant roles: only %list%': "Filtré par rôles de participation: uniquement %list%"
|
||||
Filter by participant roles: Filtrer par rôles de participation
|
||||
Part roles: Rôles de participation
|
||||
Group by participant role: Grouper par rôle de participation
|
||||
|
||||
|
||||
Events configuration: Configuration des événements
|
||||
Events configuration menu: Menu des événements
|
||||
|
@@ -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) {
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
|
@@ -61,6 +61,8 @@ final class PermissionsGroupController extends AbstractController
|
||||
$form = $this->createAddRoleScopeForm($permissionsGroup);
|
||||
$form->handleRequest($request);
|
||||
|
||||
dump($form->isSubmitted());
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$roleScope = $this->getPersistentRoleScopeBy(
|
||||
$form['composed_role_scope']->getData()->getRole(),
|
||||
|
@@ -216,13 +216,13 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
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) {
|
||||
if ($atDate >= $scopeHistory->getStartDate() && (
|
||||
null === $scopeHistory->getEndDate() || $atDate < $scopeHistory->getEndDate()
|
||||
if ($at >= $scopeHistory->getStartDate() && (
|
||||
null === $scopeHistory->getEndDate() || $at < $scopeHistory->getEndDate()
|
||||
)) {
|
||||
return $scopeHistory->getScope();
|
||||
}
|
||||
@@ -265,13 +265,13 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
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) {
|
||||
if ($atDate >= $jobHistory->getStartDate() && (
|
||||
null === $jobHistory->getEndDate() || $atDate < $jobHistory->getEndDate()
|
||||
if ($at >= $jobHistory->getStartDate() && (
|
||||
null === $jobHistory->getEndDate() || $at < $jobHistory->getEndDate()
|
||||
)) {
|
||||
return $jobHistory->getJob();
|
||||
}
|
||||
@@ -285,11 +285,6 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
return $this->jobHistories;
|
||||
}
|
||||
|
||||
public function getUserScopeHistories(): Collection
|
||||
{
|
||||
return $this->scopeHistories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* 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;
|
||||
|
||||
|
@@ -11,9 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Form\Type\Export;
|
||||
|
||||
use Chill\MainBundle\Export\DataTransformerInterface;
|
||||
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\FormType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
@@ -21,7 +19,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
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'];
|
||||
$aggregator = $exportManager->getAggregator($options['aggregator_alias']);
|
||||
@@ -32,24 +32,17 @@ class AggregatorType extends AbstractType
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$aggregatorFormBuilder = $builder->create('form', FormType::class, [
|
||||
$filterFormBuilder = $builder->create('form', FormType::class, [
|
||||
'compound' => true,
|
||||
'required' => false,
|
||||
'error_bubbling' => false,
|
||||
]);
|
||||
$aggregator->buildForm($aggregatorFormBuilder);
|
||||
$aggregator->buildForm($filterFormBuilder);
|
||||
|
||||
if ($aggregator instanceof DataTransformerInterface) {
|
||||
$aggregatorFormBuilder->addViewTransformer(new CallbackTransformer(
|
||||
fn (?array $data) => $data,
|
||||
fn (?array $data) => $aggregator->transformData($data),
|
||||
));
|
||||
$builder->add($filterFormBuilder);
|
||||
}
|
||||
|
||||
$builder->add($aggregatorFormBuilder);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setRequired('aggregator_alias')
|
||||
->setRequired('export_manager')
|
||||
|
@@ -11,10 +11,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Form\Type\Export;
|
||||
|
||||
use Chill\MainBundle\Export\DataTransformerInterface;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
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\FormType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
@@ -43,13 +41,6 @@ class FilterType extends AbstractType
|
||||
]);
|
||||
$filter->buildForm($filterFormBuilder);
|
||||
|
||||
if ($filter instanceof DataTransformerInterface) {
|
||||
$filterFormBuilder->addViewTransformer(new CallbackTransformer(
|
||||
fn (?array $data) => $data,
|
||||
fn (?array $data) => $filter->transformData($data),
|
||||
));
|
||||
}
|
||||
|
||||
$builder->add($filterFormBuilder);
|
||||
}
|
||||
|
||||
|
@@ -43,14 +43,7 @@ export const download_report = (url, container) => {
|
||||
content = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
const extensions = new Map();
|
||||
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);
|
||||
extension = mime.getExtension(type);
|
||||
|
||||
link.appendChild(document.createTextNode(download_text));
|
||||
link.classList.add("btn", "btn-action");
|
||||
@@ -62,7 +55,7 @@ export const download_report = (url, container) => {
|
||||
container.innerHTML = "";
|
||||
container.appendChild(link);
|
||||
}).catch(function(error) {
|
||||
console.error(error);
|
||||
console.log(error);
|
||||
var problem_text =
|
||||
document.createTextNode("Problem during download");
|
||||
|
||||
|
@@ -12,5 +12,5 @@ window.addEventListener("DOMContentLoaded", function(e) {
|
||||
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 => {
|
||||
marked.use({'hooks': {postprocess, preprocess}, 'async': false});
|
||||
marked.use({'hooks': {postprocess, preprocess}});
|
||||
const rawHtml = marked(markdown) as string;
|
||||
return rawHtml;
|
||||
};
|
||||
|
@@ -40,7 +40,7 @@
|
||||
{{ 'by_user'|trans ~ ' ' }}
|
||||
{% endif %}
|
||||
<span class="user">
|
||||
{{ user|chill_entity_render_box({'at_date': comment.date}) }}
|
||||
{{ user|chill_entity_render_box(options['user']) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<span class="chill-entity entity-user">
|
||||
{{- user.label }}
|
||||
{%- if opts['user_job'] and user.userJob(opts['at_date']) is not null %}
|
||||
<span class="user-job">({{ user.userJob(opts['at_date']).label|localize_translatable_string }})</span>
|
||||
{%- if opts['user_job'] and user.userJob(opts['at']) is not null %}
|
||||
<span class="user-job">({{ user.userJob(opts['at']).label|localize_translatable_string }})</span>
|
||||
{%- endif -%}
|
||||
{%- if opts['main_scope'] and user.mainScope(opts['at_date']) is not null %}
|
||||
<span class="main-scope">({{ user.mainScope(opts['at_date']).name|localize_translatable_string }})</span>
|
||||
{%- if opts['main_scope'] and user.mainScope(opts['at']) is not null %}
|
||||
<span class="main-scope">({{ user.mainScope(opts['at']).name|localize_translatable_string }})</span>
|
||||
{%- endif -%}
|
||||
{%- if opts['absence'] and user.isAbsent %}
|
||||
<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>
|
||||
{% if not c.notification.isSystem %}
|
||||
<span class="badge-user">
|
||||
{{ c.notification.sender|chill_entity_render_string({'at_date': c.notification.date}) }}
|
||||
{{ c.notification.sender|chill_entity_render_string }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
|
||||
@@ -53,7 +53,7 @@
|
||||
{% endif %}
|
||||
{% for a in c.notification.addressees %}
|
||||
<span class="badge-user">
|
||||
{{ a|chill_entity_render_string({'at_date': c.notification.date}) }}
|
||||
{{ a|chill_entity_render_string }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% for a in c.notification.addressesEmails %}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% 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 %}
|
||||
|
||||
{% block js %}
|
||||
|
@@ -31,14 +31,14 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ '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') }}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<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 class="row">
|
||||
<div class="col-sm-4">{{ 'Le'|trans }}</div>
|
||||
@@ -110,8 +110,8 @@
|
||||
{% 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>
|
||||
<ul>
|
||||
{% for u in entity_workflow.currentStepChained.destUserByAccessKey %}
|
||||
<li>{{ u|chill_entity_render_box({'at_date': entity_workflow.currentStepChained.previous.transitionAt }) }}</li>
|
||||
{% for u in entity_workflow.currentStep.destUserByAccessKey %}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@@ -42,7 +42,7 @@
|
||||
<div class="item-col" style="width: inherit;">
|
||||
{% if step.transitionBy is not null %}
|
||||
<div>
|
||||
{{ step.transitionBy|chill_entity_render_box({'at_date': step.transitionAt}) }}
|
||||
{{ step.transitionBy|chill_entity_render_box }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
@@ -76,7 +76,7 @@
|
||||
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }} : </b></p>
|
||||
<ul>
|
||||
{% 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 %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
@@ -85,7 +85,7 @@
|
||||
<p><b>{{ 'workflow.Users put in Cc'|trans }} : </b></p>
|
||||
<ul>
|
||||
{% 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 %}
|
||||
</ul>
|
||||
{% 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>
|
||||
<ul>
|
||||
{% 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 %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
{% if step.previous is not null %}
|
||||
<li>
|
||||
<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>
|
||||
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
|
||||
@@ -12,19 +12,19 @@
|
||||
<li>
|
||||
<span class="item-key">{{ 'workflow.For'|trans ~ ' : ' }}</span>
|
||||
<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>
|
||||
</li>
|
||||
<li>
|
||||
<span class="item-key">{{ 'workflow.Cc'|trans ~ ' : ' }}</span>
|
||||
<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>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<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>
|
||||
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
|
||||
|
@@ -11,6 +11,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Routing;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
@@ -18,8 +20,10 @@ use Twig\TwigFunction;
|
||||
/**
|
||||
* Add the filter 'chill_menu'.
|
||||
*/
|
||||
class MenuTwig extends AbstractExtension
|
||||
class MenuTwig extends AbstractExtension implements ContainerAwareInterface
|
||||
{
|
||||
private ?ContainerInterface $container = null;
|
||||
|
||||
/**
|
||||
* the default parameters for chillMenu.
|
||||
*
|
||||
@@ -80,4 +84,9 @@ class MenuTwig extends AbstractExtension
|
||||
{
|
||||
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(
|
||||
$previous->getTransitionBy(),
|
||||
$format,
|
||||
[...$context, UserNormalizer::AT_DATE => $previous->getTransitionAt()]
|
||||
$context
|
||||
);
|
||||
$data['transitionPreviousAt'] = $this->normalizer->normalize(
|
||||
$previous->getTransitionAt(),
|
||||
|
@@ -45,7 +45,7 @@ class NotificationNormalizer implements NormalizerAwareInterface, NormalizerInte
|
||||
'message' => $object->getMessage(),
|
||||
'relatedEntityClass' => $object->getRelatedEntityClass(),
|
||||
'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(),
|
||||
'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\Templating\Entity\UserRender;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
@@ -28,8 +27,6 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
|
||||
{
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
final public const AT_DATE = 'chill:user:at_date';
|
||||
|
||||
final public const NULL_USER = [
|
||||
'type' => 'user',
|
||||
'id' => '',
|
||||
@@ -41,16 +38,10 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
|
||||
'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 = [])
|
||||
{
|
||||
/** @var array{"chill:user:at_date"?: \DateTimeImmutable|\DateTime} $context */
|
||||
/** @var User $object */
|
||||
$userJobContext = array_merge(
|
||||
$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)];
|
||||
}
|
||||
|
||||
$at = $context[self::AT_DATE] ?? $this->clock->now();
|
||||
if ($at instanceof \DateTime) {
|
||||
$at = \DateTimeImmutable::createFromMutable($at);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'type' => 'user',
|
||||
'id' => $object->getId(),
|
||||
'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]),
|
||||
'label' => $object->getLabel(),
|
||||
'email' => (string) $object->getEmail(),
|
||||
'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_scope' => $this->normalizer->normalize($object->getMainScope($at), $format, $scopeContext),
|
||||
'main_scope' => $this->normalizer->normalize($object->getMainScope(), $format, $scopeContext),
|
||||
'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