Compare commits

..

6 Commits

269 changed files with 1561 additions and 2753 deletions

View File

@@ -0,0 +1,6 @@
kind: Feature
body: 'Add filtering to admin lists: social actions, social issues, goals, results, and evaluations'
time: 2025-12-10T03:20:45.135973502+01:00
custom:
Issue: "478"
SchemaChange: No schema change

View File

@@ -0,0 +1,7 @@
kind: Fixed
body: |
Fix migration query after previous fix
time: 2025-12-11T21:49:08.899926492+01:00
custom:
Issue: "466"
SchemaChange: No schema change

View File

@@ -1,9 +0,0 @@
## v4.11.0 - 2025-12-17
### Feature
* ([#478](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/478)) Add filtering to admin lists: social actions, social issues, goals, results, and evaluations
### Fixed
* ([#466](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/466)) Fix migration query after previous fix
* Fix translation key/value
Cannot start with % and should be wrapped in "".

View File

@@ -1,16 +0,0 @@
## v4.12.0 - 2026-01-15
### Feature
* ([#473](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/473)) Display version of chill bundles in application footer
* Increase the delay before removing stale workflow from 90 days to 180 days.
### Fixed
* ([#480](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/480)) Fix the condition to display concerned persons in calendar list items.
* ([#481](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/481)) Fix ordering of social actions: actions with a closing date in the future should be considered as 'still open'.
* ([#477](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/477)) Fix export group by center for persons without a center in CenterAggregator.php
* Fix the calculation of budget balance to only take into account resources and charges that are still actual
* ([#489](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/489)) Fix desactivation date for Goals and results
* ([#490](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/490)) Prevent sending a notification when the user signs the document himself
* ([#491](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/491)) Fix: acc periods of which user is the referrer should not be included if when the list is filtered by center and none of the participations are part of the center
* ([#492](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/492)) fix CommentInput: replace deprecated value binding with model-value
* ([#493](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/493)) fix issue with stored object permissions associated with workflows (as attachment, or through a related entity)
BC: the constructor's signature of `\Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter` has changed.

View File

@@ -1,4 +0,0 @@
## v4.12.1 - 2026-02-01
### Fixed
* ([#496](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/496)) Add the option to deal with duplicate address in BAN adress importer

View File

@@ -1,15 +0,0 @@
## v4.13.0 - 2026-02-23
### Feature
* ([#500](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/500)) ([!964](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/964)) Limit the number of public download of stored object to 30 downloads
* ([#495](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/495)) ([!967](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/967)) Send email related to notification in both html and txt format, and render quote correctly
### Fixed
* ([#438](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/438)) Change wrong color of submit button "Désigner comme adresse du parcours"
* ([#498](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/498)) For giving edit permissions on documents, take into account the workflow creator
* Fixed mispelling of address in translations: addresse -> adresse
* ([#499](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/499)) ([!963](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/963)) Fix: some postal code appears in the UI, although they are marked as deleted
* ([#501](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/501)) ([!966](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/966)) Fix deprecation in the markdown rendering
* ([#494](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/494)) ([!965](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/965)) Remove unused all-day slot display
### DX
* ([!960](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/960)) Configure changie to ask for merge request number for a better tracking of changes

View File

@@ -1,6 +0,0 @@
## v4.14.0 - 2026-03-09
### Feature
* ([#486](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/486)) ([!<no value>](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/<no value>)) Add filter and aggregator based on referrer's main center for exports of accompanying period
### Fixed
* ([#502](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/502)) ([!968](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/968)) Fix import of postal code: mark postal code as deleted if they are not present in the import any more
* ([#503](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/503)) ([!969](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/969)) Add a flash message when reassigning accompanying course (reassign list)

View File

@@ -7,7 +7,7 @@ versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
kindFormat: '### {{.Kind}}' kindFormat: '### {{.Kind}}'
# Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description. # Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description.
changeFormat: >- changeFormat: >-
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{ if not (eq .Custom.MR "") }}([!{{ .Custom.MR }}](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/{{ .Custom.MR }})) {{ end }}{{ .Body }} {{ if and .Custom.SchemaChange (ne .Custom.SchemaChange "No schema change") }} * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{ .Body }} {{ if and .Custom.SchemaChange (ne .Custom.SchemaChange "No schema change") }}
**Schema Change**: {{ .Custom.SchemaChange }} **Schema Change**: {{ .Custom.SchemaChange }}
{{- end -}} {{- end -}}
@@ -30,12 +30,6 @@ custom:
type: int type: int
minInt: 1 minInt: 1
- key: MR
label: Merge request number (on chill-bundles repository) (optional)
optional: true
type: int
minInt: 1
body: body:
# allow multiline messages # allow multiline messages
block: true block: true

13
.gitignore vendored
View File

@@ -50,15 +50,12 @@ phpstan.neon
###> symfony/phpunit-bridge ### ###> symfony/phpunit-bridge ###
###< symfony/phpunit-bridge ### ###< symfony/phpunit-bridge ###
###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###
###> friendsofphp/php-cs-fixer ### ###> friendsofphp/php-cs-fixer ###
/.php-cs-fixer.php /.php-cs-fixer.php
/.php-cs-fixer.cache /.php-cs-fixer.cache
###< friendsofphp/php-cs-fixer ### ###< friendsofphp/php-cs-fixer ###
###> pentatrion/vite-bundle ###
/node_modules/
/public/build/
###< pentatrion/vite-bundle ###

View File

@@ -238,13 +238,13 @@ The tests are run from the project's root (not from the bundle's root).
```bash ```bash
# Run all tests # Run all tests
symfony composer exec phpunit vendor/bin/phpunit
# Run a specific test file # Run a specific test file
symfony composer exec phpunit -- path/to/TestFile.php vendor/bin/phpunit path/to/TestFile.php
# Run a specific test method # Run a specific test method
symfony composer exec phpunit --filter methodName path/to/TestFile.php vendor/bin/phpunit --filter methodName path/to/TestFile.php
``` ```
When writing tests, only test specific files. Do not run all tests or the full When writing tests, only test specific files. Do not run all tests or the full

View File

@@ -6,61 +6,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v4.14.0 - 2026-03-09
### Feature
* ([#486](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/486)) ([!<no value>](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/<no value>)) Add filter and aggregator based on referrer's main center for exports of accompanying period
### Fixed
* ([#502](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/502)) ([!968](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/968)) Fix import of postal code: mark postal code as deleted if they are not present in the import any more
* ([#503](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/503)) ([!969](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/969)) Add a flash message when reassigning accompanying course (reassign list)
## v4.13.0 - 2026-02-23
### Feature
* ([#500](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/500)) ([!964](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/964)) Limit the number of public download of stored object to 30 downloads
* ([#495](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/495)) ([!967](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/967)) Send email related to notification in both html and txt format, and render quote correctly
### Fixed
* ([#438](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/438)) Change wrong color of submit button "Désigner comme adresse du parcours"
* ([#498](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/498)) For giving edit permissions on documents, take into account the workflow creator
* Fixed mispelling of address in translations: addresse -> adresse
* ([#499](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/499)) ([!963](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/963)) Fix: some postal code appears in the UI, although they are marked as deleted
* ([#501](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/501)) ([!966](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/966)) Fix deprecation in the markdown rendering
* ([#494](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/494)) ([!965](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/965)) Remove unused all-day slot display
### DX
* ([!960](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/960)) Configure changie to ask for merge request number for a better tracking of changes
## v4.12.1 - 2026-02-01
### Fixed
* ([#496](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/496)) Add the option to deal with duplicate address in BAN adress importer
## v4.12.0 - 2026-01-15
### Feature
* ([#473](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/473)) Display version of chill bundles in application footer
* Increase the delay before removing stale workflow from 90 days to 180 days.
### Fixed
* ([#480](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/480)) Fix the condition to display concerned persons in calendar list items.
* ([#481](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/481)) Fix ordering of social actions: actions with a closing date in the future should be considered as 'still open'.
* ([#477](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/477)) Fix export group by center for persons without a center in CenterAggregator.php
* Fix the calculation of budget balance to only take into account resources and charges that are still actual
* ([#489](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/489)) Fix desactivation date for Goals and results
* ([#490](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/490)) Prevent sending a notification when the user signs the document himself
* ([#491](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/491)) Fix: acc periods of which user is the referrer should not be included if when the list is filtered by center and none of the participations are part of the center
* ([#492](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/492)) fix CommentInput: replace deprecated value binding with model-value
* ([#493](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/493)) fix issue with stored object permissions associated with workflows (as attachment, or through a related entity)
BC: the constructor's signature of `\Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter` has changed.
## v4.11.0 - 2025-12-17
### Feature
* ([#478](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/478)) Add filtering to admin lists: social actions, social issues, goals, results, and evaluations
### Fixed
* ([#466](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/466)) Fix migration query after previous fix
* Fix translation key/value
Cannot start with % and should be wrapped in "".
## v4.10.1 - 2025-12-11 ## v4.10.1 - 2025-12-11
### Fixed ### Fixed
* Fix missing translation variable in NewLocation component * Fix missing translation variable in NewLocation component

View File

@@ -21,7 +21,6 @@
"ext-openssl": "*", "ext-openssl": "*",
"ext-redis": "*", "ext-redis": "*",
"ext-zlib": "*", "ext-zlib": "*",
"composer-runtime-api": "*",
"champs-libres/wopi-bundle": "dev-symfony-v5@dev", "champs-libres/wopi-bundle": "dev-symfony-v5@dev",
"champs-libres/wopi-lib": "dev-master@dev", "champs-libres/wopi-lib": "dev-master@dev",
"doctrine/data-fixtures": "^1.8", "doctrine/data-fixtures": "^1.8",
@@ -38,6 +37,7 @@
"ocramius/package-versions": "^1.10 || ^2", "ocramius/package-versions": "^1.10 || ^2",
"odolbeau/phone-number-bundle": "^3.6", "odolbeau/phone-number-bundle": "^3.6",
"ovh/ovh": "^3.0", "ovh/ovh": "^3.0",
"pentatrion/vite-bundle": "^8.2",
"phpoffice/phpspreadsheet": "^1.16", "phpoffice/phpspreadsheet": "^1.16",
"ramsey/uuid-doctrine": "^1.7", "ramsey/uuid-doctrine": "^1.7",
"sensio/framework-extra-bundle": "^5.5", "sensio/framework-extra-bundle": "^5.5",
@@ -83,9 +83,8 @@
"symfony/templating": "^5.4", "symfony/templating": "^5.4",
"symfony/translation": "^5.4", "symfony/translation": "^5.4",
"symfony/twig-bundle": "^5.4", "symfony/twig-bundle": "^5.4",
"symfony/ux-translator": "2.31.0", "symfony/ux-translator": "^2.22",
"symfony/validator": "^5.4", "symfony/validator": "^5.4",
"symfony/webpack-encore-bundle": "^1.11",
"symfony/workflow": "^5.4", "symfony/workflow": "^5.4",
"symfony/yaml": "^5.4", "symfony/yaml": "^5.4",
"thenetworg/oauth2-azure": "^2.0", "thenetworg/oauth2-azure": "^2.0",
@@ -98,7 +97,7 @@
"require-dev": { "require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", "doctrine/doctrine-fixtures-bundle": "^3.3",
"fakerphp/faker": "^1.13", "fakerphp/faker": "^1.13",
"friendsofphp/php-cs-fixer": "3.93.0", "friendsofphp/php-cs-fixer": "3.65.0",
"jangregor/phpstan-prophecy": "^1.0", "jangregor/phpstan-prophecy": "^1.0",
"nelmio/alice": "^3.8", "nelmio/alice": "^3.8",
"nikic/php-parser": "^4.15", "nikic/php-parser": "^4.15",

View File

@@ -17,7 +17,6 @@ return [
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Chill\ActivityBundle\ChillActivityBundle::class => ['all' => true], Chill\ActivityBundle\ChillActivityBundle::class => ['all' => true],
@@ -37,4 +36,5 @@ return [
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true], Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true], loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
Pentatrion\ViteBundle\PentatrionViteBundle::class => ['all' => true],
]; ];

View File

@@ -1,6 +1,8 @@
framework: framework:
assets: assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json' # Disable Encore-style manifest for default assets package. Vite (Pentatrion) uses public/build/.vite/manifest.json
# internally for vite_entry_* helpers; Symfony's asset() should not depend on a manifest.
json_manifest_path: null
when@test: when@test:
framework: framework:

View File

@@ -1,41 +0,0 @@
webpack_encore:
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
output_path: '%kernel.project_dir%/public/build'
# If multiple builds are defined (as shown below), you can disable the default build:
# output_path: false
# Set attributes that will be rendered on all script and link tags
script_attributes:
defer: true
# Uncomment (also under link_attributes) if using Turbo Drive
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
# 'data-turbo-track': reload
# link_attributes:
# Uncomment if using Turbo Drive
# 'data-turbo-track': reload
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
# crossorigin: 'anonymous'
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
# preload: true
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
# strict_mode: false
# If you have multiple builds:
# builds:
# frontend: '%kernel.project_dir%/public/frontend/build'
# pass the build name as the 3rd argument to the Twig functions
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
#when@prod:
# webpack_encore:
# # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# # Available in version 1.2
# cache: true
when@test:
webpack_encore:
strict_mode: false

View File

@@ -0,0 +1,9 @@
when@dev:
_pentatrion_vite:
prefix: /build
resource: "@PentatrionViteBundle/Resources/config/routing.yaml"
_profiler_vite:
path: /_profiler/vite
defaults:
_controller: Pentatrion\ViteBundle\Controller\ProfilerController::info

View File

@@ -1,6 +1,15 @@
{ {
"name": "chill", "name": "chill-bundles",
"private": true,
"version": "2.0.0", "version": "2.0.0",
"description": "Chill web application frontend (Vite build at repo root)",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"eslint": "eslint . --ext .js,.ts,.vue"
},
"devDependencies": { "devDependencies": {
"@alexlafroscia/yaml-merge": "^4.0.0", "@alexlafroscia/yaml-merge": "^4.0.0",
"@apidevtools/swagger-cli": "^4.0.4", "@apidevtools/swagger-cli": "^4.0.4",
@@ -11,11 +20,11 @@
"@hotwired/stimulus": "^3.0.0", "@hotwired/stimulus": "^3.0.0",
"@luminateone/eslint-baseline": "^1.0.9", "@luminateone/eslint-baseline": "^1.0.9",
"@symfony/stimulus-bridge": "^3.2.0", "@symfony/stimulus-bridge": "^3.2.0",
"@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/eslint__js": "^8.42.3", "@types/eslint__js": "^8.42.3",
"@typescript-eslint/parser": "^8.12.2", "@typescript-eslint/parser": "^8.12.2",
"@vitejs/plugin-vue": "^5.1.4",
"bindings": "^1.5.0", "bindings": "^1.5.0",
"bootstrap": "^5.3.6", "bootstrap": "^5.3.6",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
@@ -40,9 +49,9 @@
"ts-loader": "^9.3.1", "ts-loader": "^9.3.1",
"typescript": "^5.6.3", "typescript": "^5.6.3",
"typescript-eslint": "^8.13.0", "typescript-eslint": "^8.13.0",
"vue-loader": "^17.0.0", "vite": "^5.4.8",
"webpack": "^5.75.0", "vite-plugin-symfony": "^8",
"webpack-cli": "^5.0.1" "vue-loader": "^17.0.0"
}, },
"dependencies": { "dependencies": {
"@fullcalendar/core": "^6.1.4", "@fullcalendar/core": "^6.1.4",
@@ -71,21 +80,5 @@
"vue-multiselect": "3.0.0-alpha.2", "vue-multiselect": "3.0.0-alpha.2",
"vue-toast-notification": "^3.1.2", "vue-toast-notification": "^3.1.2",
"vuex": "^4.0.0" "vuex": "^4.0.0"
}, }
"browserslist": [
"defaults and fully supports es6-module and not dead"
],
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress",
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
"specs-create-dir": "mkdir -p templates/api",
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
"version": "node --version",
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
},
"private": true
} }

View File

@@ -1,3 +0,0 @@
kind: Added
body: Use admin delegated account for handling authentication
time: 2026-01-22T15:32:23.932994899+01:00

View File

@@ -80,19 +80,12 @@ final readonly class CreateZimbraComponent
$location = $calendar->getCalendar()->getLocation(); $location = $calendar->getCalendar()->getLocation();
$hasLocation = $calendar->getCalendar()->hasLocation(); $hasLocation = $calendar->getCalendar()->hasLocation();
$isPrivate = $calendar->getCalendar()->getAccompanyingPeriod()?->isConfidential() ?? false; $isPrivate = $calendar->getCalendar()->getAccompanyingPeriod()?->isConfidential() ?? false;
} elseif ($calendar instanceof Calendar) { } else {
$startDate = $calendar->getStartDate(); $startDate = $calendar->getStartDate();
$endDate = $calendar->getEndDate(); $endDate = $calendar->getEndDate();
$location = $calendar->getLocation(); $location = $calendar->getLocation();
$hasLocation = $calendar->hasLocation(); $hasLocation = $calendar->hasLocation();
$isPrivate = $calendar->getAccompanyingPeriod()?->isConfidential() ?? false; $isPrivate = $calendar->getAccompanyingPeriod()?->isConfidential() ?? false;
} else {
// Calendar range case
$startDate = $calendar->getStartDate();
$endDate = $calendar->getEndDate();
$location = $calendar->getLocation();
$hasLocation = $calendar->hasLocation();
$isPrivate = false;
} }
$comp = new InviteComponent(); $comp = new InviteComponent();

View File

@@ -11,84 +11,48 @@ declare(strict_types=1);
namespace Chill\ZimbraBundle\Calendar\Connector\ZimbraConnector; namespace Chill\ZimbraBundle\Calendar\Connector\ZimbraConnector;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpClient\Psr18Client; use Symfony\Component\HttpClient\Psr18Client;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
use Zimbra\Admin\AdminApi;
use Zimbra\Common\Enum\AccountBy; use Zimbra\Common\Enum\AccountBy;
use Zimbra\Common\Soap\ClientFactory; use Zimbra\Common\Soap\ClientFactory;
use Zimbra\Common\Struct\AccountSelector;
use Zimbra\Common\Struct\Header\AccountInfo; use Zimbra\Common\Struct\Header\AccountInfo;
use Zimbra\Mail\MailApi; use Zimbra\Mail\MailApi;
final class SoapClientBuilder final readonly class SoapClientBuilder
{ {
private readonly string $username; private string $username;
private readonly string $password; private string $password;
private readonly string $url; private string $url;
private readonly string $adminUrl; public function __construct(private ParameterBagInterface $parameterBag, private HttpClientInterface $client)
{
private readonly bool $verifyHost;
private readonly bool $verifyPeer;
private readonly bool $adminVerifyHost;
private readonly bool $adminVerifyPeer;
/**
* Keep the cache of the tokens.
*
* @var array<string, array{token: string, expirationTime: \DateTimeImmutable}>
*/
private array $tokenCache = [];
public function __construct(
private readonly ParameterBagInterface $parameterBag,
private readonly HttpClientInterface $client,
private readonly ClockInterface $clock,
) {
$dsn = $this->parameterBag->get('chill_calendar.remote_calendar_dsn'); $dsn = $this->parameterBag->get('chill_calendar.remote_calendar_dsn');
$url = parse_url($dsn); $url = parse_url($dsn);
$this->username = urldecode($url['user']); $this->username = urldecode($url['user']);
$this->password = urldecode($url['pass']); $this->password = urldecode($url['pass']);
if ('zimbra+http' === $url['scheme']) { if ('zimbra+http' === $url['scheme']) {
$scheme = 'http'; $scheme = 'http://';
$port = $url['port'] ?? 80; $port = $url['port'] ?? 80;
} elseif ('zimbra+https' === $url['scheme']) { } elseif ('zimbra+https' === $url['scheme']) {
$scheme = 'https'; $scheme = 'https://';
$port = $url['port'] ?? 443; $port = $url['port'] ?? 443;
} else { } else {
throw new \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException('Unsupported remote calendar scheme: '.$url['scheme']); throw new \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException('Unsupported remote calendar scheme: '.$url['scheme']);
} }
// get attributes for adminUrl $this->url = $scheme.$url['host'].':'.$port;
$query = [];
parse_str($url['query'] ?? '', $query);
$adminPort = $query['adminPort'] ?? '7071';
$adminHost = $query['adminHost'] ?? $url['host'];
$adminScheme = $query['adminScheme'] ?? $scheme;
$this->verifyPeer = (bool) ($query['verifyPeer'] ?? true);
$this->verifyHost = (bool) ($query['verifyHost'] ?? true);
$this->adminVerifyHost = (bool) ($query['adminVerifyHost'] ?? $this->verifyPeer);
$this->adminVerifyPeer = (bool) ($query['adminVerifyPeer'] ?? $this->verifyHost);
$this->url = $scheme.'://'.$url['host'].':'.$port;
$this->adminUrl = $adminScheme.'://'.$adminHost.':'.$adminPort;
} }
private function buildApi(): MailApi private function buildApi(): MailApi
{ {
$baseClient = $this->client->withOptions([ $baseClient = $this->client->withOptions([
'base_uri' => $location = $this->url.'/service/soap', 'base_uri' => $location = $this->url.'/service/soap',
'verify_host' => $this->verifyHost, 'verify_host' => false,
'verify_peer' => $this->verifyPeer, 'verify_peer' => false,
]); ]);
$psr18Client = new Psr18Client($baseClient); $psr18Client = new Psr18Client($baseClient);
$api = new MailApi(); $api = new MailApi();
@@ -98,36 +62,12 @@ final class SoapClientBuilder
return $api; return $api;
} }
private function buildAdminApi(): AdminApi
{
$baseClient = $this->client->withOptions([
'base_uri' => $location = $this->adminUrl.'/service/admin/soap',
'verify_host' => $this->adminVerifyHost,
'verify_peer' => $this->adminVerifyPeer,
]);
$psr18Client = new Psr18Client($baseClient);
$api = new AdminApi();
$client = ClientFactory::create($location, $psr18Client);
$api->setClient($client);
return $api;
}
public function getApiForAccount(string $accountName): MailApi public function getApiForAccount(string $accountName): MailApi
{ {
['token' => $token, 'expirationTime' => $expirationTime] = $this->tokenCache[$accountName] $api = $this->buildApi();
?? ['token' => null, 'expirationTime' => null]; $response = $api->authByAccountName($this->username, $this->password);
if (null === $token || null === $expirationTime || $expirationTime <= $this->clock->now()) { $token = $response->getAuthToken();
$adminApi = $this->buildAdminApi();
$adminApi->auth($this->username, $this->password);
$delegateResponse = $adminApi->delegateAuth(new AccountSelector(AccountBy::NAME, $accountName));
$token = $delegateResponse->getAuthToken();
$expiration = $delegateResponse->getLifetime();
$expirationTime = $this->clock->now()->add(new \DateInterval('PT'.$expiration.'S'));
$this->tokenCache[$accountName] = ['token' => $token, 'expirationTime' => $expirationTime];
}
$apiBy = $this->buildApi(); $apiBy = $this->buildApi();
$apiBy->setAuthToken($token); $apiBy->setAuthToken($token);

View File

@@ -69,7 +69,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
} }
/** (non-PHPdoc). /** (non-PHPdoc).
* @see PrependExtensionInterface::prepend() * @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
*/ */
public function prependRoutes(ContainerBuilder $container) public function prependRoutes(ContainerBuilder $container)
{ {

View File

@@ -1 +1 @@
require("./chillactivity.scss"); import "./chillactivity.scss";

View File

@@ -124,9 +124,9 @@
{# {{ form(delete_form) }} #} {# {{ form(delete_form) }} #}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ vite_entry_script_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }} {{ vite_entry_link_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}

View File

@@ -15,7 +15,7 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
@@ -23,12 +23,12 @@
}); });
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};
</script> </script>
{{ encore_entry_script_tags('vue_activity') }} {{ vite_entry_script_tags('vue_activity') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }} {{ vite_entry_link_tags('vue_activity') }}
{{ encore_entry_link_tags('page_edit_activity') }} {{ vite_entry_link_tags('page_edit_activity') }}
{% endblock %} {% endblock %}

View File

@@ -30,7 +30,7 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
@@ -38,10 +38,10 @@
}); });
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};
</script> </script>
{{ encore_entry_script_tags('vue_activity') }} {{ vite_entry_script_tags('vue_activity') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }} {{ vite_entry_link_tags('vue_activity') }}
{% endblock %} {% endblock %}

View File

@@ -7,14 +7,14 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ vite_entry_script_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ vite_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -22,14 +22,14 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ vite_entry_script_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ vite_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -121,9 +121,9 @@
{{ form_end(form) }} {{ form_end(form) }}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ vite_entry_script_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }} {{ vite_entry_link_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}

View File

@@ -15,16 +15,16 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};
{% if default_location is not null %}window.default_location_id = {{ default_location.id }}{% endif %}; {% if default_location is not null %}window.default_location_id = {{ default_location.id }}{% endif %};
</script> </script>
{{ encore_entry_script_tags('vue_activity') }} {{ vite_entry_script_tags('vue_activity') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }} {{ vite_entry_link_tags('vue_activity') }}
{% endblock %} {% endblock %}

View File

@@ -14,7 +14,7 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
@@ -22,10 +22,10 @@
}); });
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};
</script> </script>
{{ encore_entry_script_tags('vue_activity') }} {{ vite_entry_script_tags('vue_activity') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }} {{ vite_entry_link_tags('vue_activity') }}
{% endblock %} {% endblock %}

View File

@@ -6,16 +6,16 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ vite_entry_script_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ vite_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% import '@ChillActivity/ActivityReason/macro.html.twig' as m %} {% import '@ChillActivity/ActivityReason/macro.html.twig' as m %}

View File

@@ -6,14 +6,14 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ vite_entry_script_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ vite_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% import '@ChillActivity/ActivityReason/macro.html.twig' as m %} {% import '@ChillActivity/ActivityReason/macro.html.twig' as m %}

View File

@@ -16,8 +16,7 @@ use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface; use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter; use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository; use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelperInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
class ActivityStoredObjectVoter extends AbstractStoredObjectVoter class ActivityStoredObjectVoter extends AbstractStoredObjectVoter
@@ -25,10 +24,9 @@ class ActivityStoredObjectVoter extends AbstractStoredObjectVoter
public function __construct( public function __construct(
private readonly ActivityRepository $repository, private readonly ActivityRepository $repository,
Security $security, Security $security,
WorkflowRelatedEntityPermissionHelperInterface $workflowDocumentService, WorkflowRelatedEntityPermissionHelper $workflowDocumentService,
EntityWorkflowAttachmentRepository $attachmentRepository,
) { ) {
parent::__construct($security, $attachmentRepository, $workflowDocumentService); parent::__construct($security, $workflowDocumentService);
} }
protected function getRepository(): AssociatedEntityToStoredObjectInterface protected function getRepository(): AssociatedEntityToStoredObjectInterface

View File

@@ -1,18 +0,0 @@
// this file loads all assets from the Chill person bundle
module.exports = function (encore, entries) {
entries.push(__dirname + "/Resources/public/chill/index.js");
encore.addAliases({
ChillActivityAssets: __dirname + "/Resources/public",
});
encore.addEntry(
"page_edit_activity",
__dirname + "/Resources/public/page/edit_activity/index.scss",
);
encore.addEntry(
"vue_activity",
__dirname + "/Resources/public/vuejs/Activity/index.js",
);
};

View File

@@ -0,0 +1,14 @@
export default {
chillBase: [
"./src/Bundle/ChillActivityBundle/Resources/public/chill/index.js",
],
aliases: {
ChillActivityAssets: "./src/Bundle/ChillActivityBundle/Resources/public",
},
inputs: {
page_edit_activity:
"./src/Bundle/ChillActivityBundle/Resources/public/page/edit_activity/index.scss",
vue_activity:
"./src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/index.js",
},
};

View File

@@ -2,12 +2,12 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ vite_entry_script_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_pickentity_type') }} {{ vite_entry_link_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}
{% block title %} {% block title %}

View File

@@ -56,7 +56,7 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac
} }
/** (non-PHPdoc). /** (non-PHPdoc).
* @see PrependExtensionInterface::prepend() * @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
*/ */
public function prependRoutes(ContainerBuilder $container) public function prependRoutes(ContainerBuilder $container)
{ {

View File

@@ -1 +1 @@
require("./chillbudget.scss"); import("./chillbudget.scss");

View File

@@ -72,20 +72,14 @@
{% macro table_results(actualCharges, actualResources, results) %} {% macro table_results(actualCharges, actualResources, results) %}
{% set now = date() %}
{% set totalCharges = 0 %} {% set totalCharges = 0 %}
{% for c in actualCharges %} {% for c in actualCharges %}
{% if c.startDate <= now and (c.endDate is null or c.endDate >= now) %} {% set totalCharges = totalCharges + c.amount %}
{% set totalCharges = totalCharges + c.amount %}
{% endif %}
{% endfor %} {% endfor %}
{% set totalResources = 0 %} {% set totalResources = 0 %}
{% for r in actualResources %} {% for r in actualResources %}
{% if r.startDate <= now and (r.endDate is null or r.endDate >= now) %} {% set totalResources = totalResources + r.amount %}
{% set totalResources = totalResources + r.amount %}
{% endif %}
{% endfor %} {% endfor %}
{% set result = (totalResources - totalCharges) %} {% set result = (totalResources - totalCharges) %}

View File

@@ -7,11 +7,11 @@
{% block title title %} {% block title title %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('page_budget') }} {{ vite_entry_script_tags('page_budget') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('page_budget') }} {{ vite_entry_link_tags('page_budget') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -7,11 +7,11 @@
{% block title title %} {% block title title %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('page_budget') }} {{ vite_entry_script_tags('page_budget') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('page_budget') }} {{ vite_entry_link_tags('page_budget') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -1,8 +0,0 @@
// this file loads all assets from the Chill budget bundle
module.exports = function (encore, entries) {
encore.addAliases({
ChillBudgetAssets: __dirname + "/Resources/public",
});
encore.addEntry("page_budget", __dirname + "/Resources/public/page/index.js");
};

View File

@@ -0,0 +1,8 @@
export default {
aliases: {
ChillBudgetAssets: "./src/Bundle/ChillBudgetBundle/Resources/public",
},
inputs: {
page_budget: "./src/Bundle/ChillBudgetBundle/Resources/public/page/index.js",
},
};

View File

@@ -1 +1 @@
require("./scss/calendar.scss"); import("./scss/calendar.scss");

View File

@@ -346,7 +346,6 @@ const baseOptions = ref<CalendarOptions>({
center: "title", center: "title",
right: "timeGridWeek,timeGridDay", right: "timeGridWeek,timeGridDay",
}, },
allDaySlot: false,
}); });
const ranges = computed<EventInput[]>(() => { const ranges = computed<EventInput[]>(() => {

View File

@@ -255,5 +255,3 @@ export default {
}, },
}; };
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

View File

@@ -75,7 +75,7 @@
</div> </div>
{% if calendar.comment.comment is not empty {% if calendar.comment.comment is not empty
or calendar.persons|length > 0 or calendar.users|length > 0
or calendar.thirdParties|length > 0 or calendar.thirdParties|length > 0
or calendar.users|length > 0 %} or calendar.users|length > 0 %}
<div class="item-row details separator"> <div class="item-row details separator">

View File

@@ -20,13 +20,13 @@
window.endDate = {{ entity.endDate|date('Y-m-d H:i:s')|json_encode|raw }}; window.endDate = {{ entity.endDate|date('Y-m-d H:i:s')|json_encode|raw }};
window.mainUser = {{ entity.mainUser.id }}; window.mainUser = {{ entity.mainUser.id }};
</script> </script>
{{ encore_entry_script_tags('vue_calendar') }} {{ vite_entry_script_tags('vue_calendar') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('vue_calendar') }} {{ vite_entry_link_tags('vue_calendar') }}
{{ encore_entry_link_tags('page_calendar') }} {{ vite_entry_link_tags('page_calendar') }}
{% endblock %} {% endblock %}
{% block block_post_menu %} {% block block_post_menu %}

View File

@@ -20,13 +20,13 @@
window.endDate = {{ entity.endDate|date('Y-m-d H:i:s')|json_encode|raw }}; window.endDate = {{ entity.endDate|date('Y-m-d H:i:s')|json_encode|raw }};
window.mainUser = {{ entity.mainUser.id }}; window.mainUser = {{ entity.mainUser.id }};
</script> </script>
{{ encore_entry_script_tags('vue_calendar') }} {{ vite_entry_script_tags('vue_calendar') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('vue_calendar') }} {{ vite_entry_link_tags('vue_calendar') }}
{{ encore_entry_link_tags('page_calendar') }} {{ vite_entry_link_tags('page_calendar') }}
{% endblock %} {% endblock %}
{% block block_post_menu %} {% block block_post_menu %}

View File

@@ -23,10 +23,10 @@
window.endDate = {{ entity.endDate|date('Y-m-d H:i:s')|json_encode|raw }}; window.endDate = {{ entity.endDate|date('Y-m-d H:i:s')|json_encode|raw }};
window.mainUser = {{ entity.mainUser.id }}; window.mainUser = {{ entity.mainUser.id }};
</script> </script>
{{ encore_entry_script_tags('vue_calendar') }} {{ vite_entry_script_tags('vue_calendar') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('vue_calendar') }} {{ vite_entry_link_tags('vue_calendar') }}
{% endblock %} {% endblock %}

View File

@@ -9,14 +9,14 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_answer') }} {{ vite_entry_script_tags('mod_answer') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_answer') }} {{ vite_entry_link_tags('mod_answer') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -8,14 +8,14 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_answer') }} {{ vite_entry_script_tags('mod_answer') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_answer') }} {{ vite_entry_link_tags('mod_answer') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -16,10 +16,10 @@
<script type="text/javascript"> <script type="text/javascript">
window.userId = {{ user.id }}; window.userId = {{ user.id }};
</script> </script>
{{ encore_entry_script_tags('vue_mycalendarrange') }} {{ vite_entry_script_tags('vue_mycalendarrange') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('vue_mycalendarrange') }} {{ vite_entry_link_tags('vue_mycalendarrange') }}
{% endblock %} {% endblock %}

View File

@@ -15,18 +15,18 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ vite_entry_script_tags('mod_pickentity_type') }}
<script type="text/javascript"> <script type="text/javascript">
window.entity = {{ entity_json|json_encode|raw }}; window.entity = {{ entity_json|json_encode|raw }};
{% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %}; {% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %};
</script> </script>
{{ encore_entry_script_tags('vue_calendar') }} {{ vite_entry_script_tags('vue_calendar') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('vue_calendar') }} {{ vite_entry_link_tags('vue_calendar') }}
{{ encore_entry_link_tags('mod_pickentity_type') }} {{ vite_entry_link_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}
{% block block_post_menu %} {% block block_post_menu %}

View File

@@ -15,18 +15,18 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ vite_entry_script_tags('mod_pickentity_type') }}
<script type="text/javascript"> <script type="text/javascript">
window.entity = {{ entity_json|json_encode|raw }}; window.entity = {{ entity_json|json_encode|raw }};
{% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %}; {% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %};
</script> </script>
{{ encore_entry_script_tags('vue_calendar') }} {{ vite_entry_script_tags('vue_calendar') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('vue_calendar') }} {{ vite_entry_link_tags('vue_calendar') }}
{{ encore_entry_link_tags('mod_pickentity_type') }} {{ vite_entry_link_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}
{% block block_post_menu %} {% block block_post_menu %}

View File

@@ -9,12 +9,12 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -8,12 +8,12 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -9,12 +9,12 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -8,12 +8,12 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -29,12 +29,12 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_answer') }} {{ vite_entry_script_tags('mod_answer') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_answer') }} {{ vite_entry_link_tags('mod_answer') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}

View File

@@ -1,25 +0,0 @@
// this file loads all assets from the Chill calendar bundle
module.exports = function (encore, entries) {
entries.push(__dirname + "/Resources/public/chill/chill.js");
encore.addAliases({
ChillCalendarAssets: __dirname + "/Resources/public",
});
encore.addEntry(
"vue_calendar",
__dirname + "/Resources/public/vuejs/Calendar/index.js",
);
encore.addEntry(
"vue_mycalendarrange",
__dirname + "/Resources/public/vuejs/MyCalendarRange/index2.ts",
);
encore.addEntry(
"page_calendar",
__dirname + "/Resources/public/chill/index.js",
);
encore.addEntry(
"mod_answer",
__dirname + "/Resources/public/module/Invite/answer.js",
);
};

View File

@@ -0,0 +1,18 @@
export default {
chillBase: [
"./src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js",
],
aliases: {
ChillCalendarAssets: "./src/Bundle/ChillCalendarBundle/Resources/public",
},
inputs: {
vue_calendar:
"./src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/index.js",
vue_mycalendarrange:
"./src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index2.ts",
page_calendar:
"./src/Bundle/ChillCalendarBundle/Resources/public/chill/index.js",
mod_answer:
"./src/Bundle/ChillCalendarBundle/Resources/public/module/Invite/answer.js",
},
};

View File

@@ -52,7 +52,7 @@ class ChillCustomFieldsExtension extends Extension implements PrependExtensionIn
} }
/** (non-PHPdoc). /** (non-PHPdoc).
* @see PrependExtensionInterface::prepend() * @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
*/ */
public function prepend(ContainerBuilder $container) public function prepend(ContainerBuilder $container)
{ {

View File

@@ -25,7 +25,7 @@ class ChoiceWithOtherType extends AbstractType
private string $otherValueLabel = 'Other value'; private string $otherValueLabel = 'Other value';
/** (non-PHPdoc). /** (non-PHPdoc).
* @see AbstractType::buildForm() * @see \Symfony\Component\Form\AbstractType::buildForm()
*/ */
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
@@ -42,7 +42,7 @@ class ChoiceWithOtherType extends AbstractType
} }
/** (non-PHPdoc). /** (non-PHPdoc).
* @see AbstractType::configureOptions() * @see \Symfony\Component\Form\AbstractType::configureOptions()
*/ */
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {

View File

@@ -22,7 +22,7 @@ use Symfony\Component\Form\FormEvents;
class ChoicesListType extends AbstractType class ChoicesListType extends AbstractType
{ {
/** (non-PHPdoc). /** (non-PHPdoc).
* @see AbstractType::buildForm() * @see \Symfony\Component\Form\AbstractType::buildForm()
*/ */
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {

View File

@@ -82,7 +82,7 @@ class CustomFieldProvider implements ContainerAwareInterface
/** /**
* (non-PHPdoc). * (non-PHPdoc).
* *
* @see ContainerAwareInterface::setContainer() * @see \Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer()
*/ */
public function setContainer(?ContainerInterface $container = null) public function setContainer(?ContainerInterface $container = null)
{ {

View File

@@ -17,9 +17,9 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}

View File

@@ -2,12 +2,12 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}

View File

@@ -17,9 +17,9 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}

View File

@@ -3,11 +3,11 @@
{% block title 'docgen.Generate a document'|trans %} {% block title 'docgen.Generate a document'|trans %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ vite_entry_script_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }} {{ vite_entry_link_tags('mod_pickentity_type') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -1,11 +0,0 @@
// this file loads all assets from the Chill DocGenerator bundle
module.exports = function (encore, entries) {
encore.addAliases({
ChillDocGeneratorAssets: __dirname + "/Resources/public",
});
encore.addEntry(
"mod_docgen_picktemplate",
__dirname + "/Resources/public/module/PickTemplate/index.js",
);
};

View File

@@ -0,0 +1,10 @@
export default {
aliases: {
ChillDocGeneratorAssets:
"./src/Bundle/ChillDocGeneratorBundle/Resources/public",
},
inputs: {
mod_docgen_picktemplate:
"./src/Bundle/ChillDocGeneratorBundle/Resources/public/module/PickTemplate/index.js",
},
};

View File

@@ -1,2 +1,2 @@
require("./uploader.js"); import("./uploader.js");
require("./downloader.js"); import("./downloader.js");

View File

@@ -22,8 +22,8 @@ import { initializeButtons } from "./downloader.js";
// load css // load css
//require('dropzone/dist/basic.css'); //require('dropzone/dist/basic.css');
require("dropzone/dist/dropzone.css"); import("dropzone/dist/dropzone.css");
require("./index.scss"); import("./index.scss");
// //
// disable dropzone autodiscover // disable dropzone autodiscover

View File

@@ -366,7 +366,7 @@ import {
} from "../../types"; } from "../../types";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import * as pdfjsLib from "pdfjs-dist"; import * as pdfjsLib from "pdfjs-dist";
import { import type {
PDFDocumentProxy, PDFDocumentProxy,
PDFPageProxy, PDFPageProxy,
} from "pdfjs-dist/types/src/display/api"; } from "pdfjs-dist/types/src/display/api";

View File

@@ -42,10 +42,10 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}

View File

@@ -38,10 +38,10 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}

View File

@@ -12,9 +12,9 @@
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('mod_entity_workflow_pick') }} {{ vite_entry_link_tags('mod_entity_workflow_pick') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@@ -80,7 +80,7 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{{ encore_entry_script_tags('mod_entity_workflow_pick') }} {{ vite_entry_script_tags('mod_entity_workflow_pick') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}

View File

@@ -3,14 +3,14 @@ activeRouteKey = '' %} {% block title %}
{{ "Documents" }} {{ "Documents" }}
{% endblock %} {% block js %} {% endblock %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags("mod_docgen_picktemplate") }} {{ vite_entry_script_tags("mod_docgen_picktemplate") }}
{{ encore_entry_script_tags("mod_entity_workflow_pick") }} {{ vite_entry_script_tags("mod_entity_workflow_pick") }}
{{ encore_entry_script_tags("mod_document_action_buttons_group") }} {{ vite_entry_script_tags("mod_document_action_buttons_group") }}
{% endblock %} {% block css %} {% endblock %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags("mod_docgen_picktemplate") }} {{ vite_entry_script_tags("mod_docgen_picktemplate") }}
{{ encore_entry_link_tags("mod_entity_workflow_pick") }} {{ vite_entry_link_tags("mod_entity_workflow_pick") }}
{{ encore_entry_link_tags("mod_document_action_buttons_group") }} {{ vite_entry_link_tags("mod_document_action_buttons_group") }}
{% endblock %} {% block content %} {% endblock %} {% block content %}
<div class="document-list"> <div class="document-list">
<h1>{{ "Documents" }}</h1> <h1>{{ "Documents" }}</h1>

View File

@@ -13,14 +13,14 @@ License * along with this program. If not, see <http://www.gnu.org/licenses/>.
{{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }} {{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}
{% endblock %} {% block js %} {% endblock %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags("mod_docgen_picktemplate") }} {{ vite_entry_script_tags("mod_docgen_picktemplate") }}
{{ encore_entry_script_tags("mod_entity_workflow_pick") }} {{ vite_entry_script_tags("mod_entity_workflow_pick") }}
{{ encore_entry_script_tags("mod_document_action_buttons_group") }} {{ vite_entry_script_tags("mod_document_action_buttons_group") }}
{% endblock %} {% block css %} {% endblock %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags("mod_docgen_picktemplate") }} {{ vite_entry_link_tags("mod_docgen_picktemplate") }}
{{ encore_entry_link_tags("mod_entity_workflow_pick") }} {{ vite_entry_link_tags("mod_entity_workflow_pick") }}
{{ encore_entry_link_tags("mod_document_action_buttons_group") }} {{ vite_entry_link_tags("mod_document_action_buttons_group") }}
{% endblock %} {% block content %} {% endblock %} {% block content %}
<div class="document-list"> <div class="document-list">

View File

@@ -58,9 +58,9 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}

View File

@@ -54,9 +54,9 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_async_upload') }} {{ vite_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ vite_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}

View File

@@ -24,11 +24,11 @@
{% block title %}{{ 'Detail of document of %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %} {% block title %}{{ 'Detail of document of %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -7,21 +7,21 @@
<link rel="shortcut icon" href="{{ asset('build/images/favicon.ico') }}" type="image/x-icon"> <link rel="shortcut icon" href="{{ asset('build/images/favicon.ico') }}" type="image/x-icon">
<title>Signature</title> <title>Signature</title>
{{ encore_entry_link_tags('mod_bootstrap') }} {{ vite_entry_link_tags('mod_bootstrap') }}
{{ encore_entry_link_tags('mod_forkawesome') }} {{ vite_entry_link_tags('mod_forkawesome') }}
{{ encore_entry_link_tags('chill') }} {{ vite_entry_link_tags('chill') }}
{{ encore_entry_link_tags('vue_document_signature') }} {{ vite_entry_link_tags('vue_document_signature') }}
</head> </head>
<body> <body>
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
<script type="text/javascript"> <script type="text/javascript">
window.signature = {{ signature|json_encode|raw }}; window.signature = {{ signature|json_encode|raw }};
</script> </script>
{{ encore_entry_script_tags('vue_document_signature') }} {{ vite_entry_script_tags('vue_document_signature') }}
{% endblock %} {% endblock %}
<div class="content" id="content"> <div class="content" id="content">

View File

@@ -2,12 +2,12 @@
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_document_download_button') }} {{ vite_entry_link_tags('mod_document_download_button') }}
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_document_download_button') }} {{ vite_entry_script_tags('mod_document_download_button') }}
{% endblock %} {% endblock %}
{% block title %}{{ 'workflow.public_link.title'|trans }} - {{ title }}{% endblock %} {% block title %}{{ 'workflow.public_link.title'|trans }} - {{ title }}{% endblock %}

View File

@@ -15,10 +15,7 @@ use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface; use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoterInterface; use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoterInterface;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelperInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@@ -37,8 +34,7 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
public function __construct( public function __construct(
private readonly Security $security, private readonly Security $security,
private readonly EntityWorkflowAttachmentRepository $entityWorkflowAttachmentRepository, private readonly ?WorkflowRelatedEntityPermissionHelper $workflowDocumentService = null,
private readonly WorkflowRelatedEntityPermissionHelperInterface $workflowDocumentService,
) {} ) {}
public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool
@@ -50,6 +46,16 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool
{ {
// we first try to get the permission from the workflow, as attachement (this is the less intensive query)
$workflowPermissionAsAttachment = match ($attribute) {
StoredObjectRoleEnum::SEE => $this->workflowDocumentService->isAllowedByWorkflowForReadOperation($subject),
StoredObjectRoleEnum::EDIT => $this->workflowDocumentService->isAllowedByWorkflowForWriteOperation($subject),
};
if (WorkflowRelatedEntityPermissionHelper::FORCE_DENIED === $workflowPermissionAsAttachment) {
return false;
}
// Retrieve the related entity // Retrieve the related entity
$entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject); $entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject);
@@ -59,7 +65,7 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
$regularPermission = $this->security->isGranted($voterAttribute, $entity); $regularPermission = $this->security->isGranted($voterAttribute, $entity);
if (!$this->canBeAssociatedWithWorkflow()) { if (!$this->canBeAssociatedWithWorkflow()) {
return $this->voteOnStoredObjectAsAttachementOfAWorkflow($attribute, $regularPermission, $subject); return $regularPermission;
} }
$workflowPermission = match ($attribute) { $workflowPermission = match ($attribute) {
@@ -68,41 +74,9 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
}; };
return match ($workflowPermission) { return match ($workflowPermission) {
WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT => true, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT => true,
WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED => false, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED => false,
WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN => $this->voteOnStoredObjectAsAttachementOfAWorkflow($attribute, $regularPermission, $subject), WorkflowRelatedEntityPermissionHelper::ABSTAIN => WorkflowRelatedEntityPermissionHelper::FORCE_GRANT === $workflowPermissionAsAttachment || $regularPermission,
}; };
} }
private function voteOnStoredObjectAsAttachementOfAWorkflow(StoredObjectRoleEnum $attribute, bool $regularPermission, StoredObject $storedObject): bool
{
$attachments = $this->entityWorkflowAttachmentRepository->findByStoredObject($storedObject);
// we get all the entity workflows where the stored object is attached
$entityWorkflows = array_map(static fn (EntityWorkflowAttachment $attachment) => $attachment->getEntityWorkflow(), $attachments);
// we compute all the permission for each entity workflow
$permissions = array_map(fn (EntityWorkflow $entityWorkflow): string => match ($attribute) {
StoredObjectRoleEnum::SEE => $this->workflowDocumentService->isAllowedByWorkflowForReadOperation($entityWorkflow),
StoredObjectRoleEnum::EDIT => $this->workflowDocumentService->isAllowedByWorkflowForWriteOperation($entityWorkflow),
}, $entityWorkflows);
// now, we reduce the permissions: abstain are ignored. Between DENIED and and GRANT, DENIED takes precedence
$computedPermission = WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN;
foreach ($permissions as $permission) {
if (WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED === $permission) {
return false;
}
if (WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT === $permission) {
$computedPermission = WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT;
}
}
if (WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN === $computedPermission) {
return $regularPermission;
}
// this is the case where WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT is returned
return true;
}
} }

View File

@@ -16,7 +16,6 @@ use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface; use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper; use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@@ -26,9 +25,8 @@ final class AccompanyingCourseDocumentStoredObjectVoter extends AbstractStoredOb
private readonly AccompanyingCourseDocumentRepository $repository, private readonly AccompanyingCourseDocumentRepository $repository,
Security $security, Security $security,
WorkflowRelatedEntityPermissionHelper $workflowDocumentService, WorkflowRelatedEntityPermissionHelper $workflowDocumentService,
EntityWorkflowAttachmentRepository $attachmentRepository,
) { ) {
parent::__construct($security, $attachmentRepository, $workflowDocumentService); parent::__construct($security, $workflowDocumentService);
} }
protected function getRepository(): AssociatedEntityToStoredObjectInterface protected function getRepository(): AssociatedEntityToStoredObjectInterface

View File

@@ -16,7 +16,6 @@ use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
use Chill\DocStoreBundle\Repository\PersonDocumentRepository; use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper; use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@@ -26,9 +25,8 @@ class PersonDocumentStoredObjectVoter extends AbstractStoredObjectVoter
private readonly PersonDocumentRepository $repository, private readonly PersonDocumentRepository $repository,
Security $security, Security $security,
WorkflowRelatedEntityPermissionHelper $workflowDocumentService, WorkflowRelatedEntityPermissionHelper $workflowDocumentService,
EntityWorkflowAttachmentRepository $attachmentRepository,
) { ) {
parent::__construct($security, $attachmentRepository, $workflowDocumentService); parent::__construct($security, $workflowDocumentService);
} }
protected function getRepository(): AssociatedEntityToStoredObjectInterface protected function getRepository(): AssociatedEntityToStoredObjectInterface

View File

@@ -16,11 +16,8 @@ use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter; use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment; use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelperInterface;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@@ -34,31 +31,21 @@ class AbstractStoredObjectVoterTest extends TestCase
{ {
use ProphecyTrait; use ProphecyTrait;
/**
* @param array<int, EntityWorkflowAttachment> $attachments
*
* @return void
*/
private function buildStoredObjectVoter( private function buildStoredObjectVoter(
bool $canBeAssociatedWithWorkflow, bool $canBeAssociatedWithWorkflow,
AssociatedEntityToStoredObjectInterface $repository, AssociatedEntityToStoredObjectInterface $repository,
Security $security, Security $security,
?WorkflowRelatedEntityPermissionHelperInterface $workflowDocumentService = null, ?WorkflowRelatedEntityPermissionHelper $workflowDocumentService = null,
array $attachments = [],
): AbstractStoredObjectVoter { ): AbstractStoredObjectVoter {
$attachmentsRepository = $this->prophesize(EntityWorkflowAttachmentRepository::class);
$attachmentsRepository->findByStoredObject(Argument::type(StoredObject::class))->willReturn($attachments);
// Anonymous class extending the abstract class // Anonymous class extending the abstract class
return new class ($canBeAssociatedWithWorkflow, $repository, $security, $attachmentsRepository->reveal(), $workflowDocumentService) extends AbstractStoredObjectVoter { return new class ($canBeAssociatedWithWorkflow, $repository, $security, $workflowDocumentService) extends AbstractStoredObjectVoter {
public function __construct( public function __construct(
private readonly bool $canBeAssociatedWithWorkflow, private readonly bool $canBeAssociatedWithWorkflow,
private readonly AssociatedEntityToStoredObjectInterface $repository, private readonly AssociatedEntityToStoredObjectInterface $repository,
Security $security, Security $security,
EntityWorkflowAttachmentRepository $attachmentRepository, ?WorkflowRelatedEntityPermissionHelper $workflowDocumentService = null,
WorkflowRelatedEntityPermissionHelperInterface $workflowDocumentService,
) { ) {
parent::__construct($security, $attachmentRepository, $workflowDocumentService); parent::__construct($security, $workflowDocumentService);
} }
protected function attributeToRole($attribute): string protected function attributeToRole($attribute): string
@@ -85,29 +72,28 @@ class AbstractStoredObjectVoterTest extends TestCase
public function testSupportsOnAttribute(): void public function testSupportsOnAttribute(): void
{ {
$entityWorkflowService = $this->prophesize(WorkflowRelatedEntityPermissionHelperInterface::class); $voter = $this->buildStoredObjectVoter(false, new DummyRepository(new \stdClass()), $this->prophesize(Security::class)->reveal(), null);
$voter = $this->buildStoredObjectVoter(false, new DummyRepository(new \stdClass()), $this->prophesize(Security::class)->reveal(), $entityWorkflowService->reveal());
self::assertTrue($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject())); self::assertTrue($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject()));
$voter = $this->buildStoredObjectVoter(false, new DummyRepository(new User()), $this->prophesize(Security::class)->reveal(), $entityWorkflowService->reveal()); $voter = $this->buildStoredObjectVoter(false, new DummyRepository(new User()), $this->prophesize(Security::class)->reveal(), null);
self::assertFalse($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject())); self::assertFalse($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject()));
$voter = $this->buildStoredObjectVoter(false, new DummyRepository(null), $this->prophesize(Security::class)->reveal(), $entityWorkflowService->reveal()); $voter = $this->buildStoredObjectVoter(false, new DummyRepository(null), $this->prophesize(Security::class)->reveal(), null);
self::assertFalse($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject())); self::assertFalse($voter->supports(StoredObjectRoleEnum::SEE, new StoredObject()));
} }
/** /**
* @dataProvider dataProviderVoteOnAttributeWithWorkflow * @dataProvider dataProviderVoteOnAttributeWithStoredObjectPermission
*/ */
public function testVoteOnAttributeWithWorkflow( public function testVoteOnAttributeWithStoredObjectPermission(
StoredObjectRoleEnum $attribute, StoredObjectRoleEnum $attribute,
bool $expected, bool $expected,
bool $isGrantedRegularPermission, bool $isGrantedRegularPermission,
string $isGrantedWorkflowPermission, string $isGrantedWorkflowPermission,
string $isGrantedStoredObjectAttachment,
): void { ): void {
$storedObject = new StoredObject(); $storedObject = new StoredObject();
$repository = new DummyRepository($related = new \stdClass()); $repository = new DummyRepository($related = new \stdClass());
@@ -116,28 +102,31 @@ class AbstractStoredObjectVoterTest extends TestCase
$security = $this->prophesize(Security::class); $security = $this->prophesize(Security::class);
$security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission); $security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission);
$workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelperInterface::class); $workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class);
$security = $this->prophesize(Security::class); $security = $this->prophesize(Security::class);
$security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission); $security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission);
$attachementRepository = $this->prophesize(EntityWorkflowAttachmentRepository::class);
$attachementRepository->findByStoredObject($storedObject)->willReturn([]);
if (StoredObjectRoleEnum::SEE === $attribute) { if (StoredObjectRoleEnum::SEE === $attribute) {
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($storedObject)
->shouldBeCalled()
->willReturn($isGrantedStoredObjectAttachment);
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related) $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related)
->willReturn($isGrantedWorkflowPermission); ->willReturn($isGrantedWorkflowPermission);
} elseif (StoredObjectRoleEnum::EDIT === $attribute) { } elseif (StoredObjectRoleEnum::EDIT === $attribute) {
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($storedObject)
->shouldBeCalled()
->willReturn($isGrantedStoredObjectAttachment);
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($related) $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($related)
->willReturn($isGrantedWorkflowPermission); ->willReturn($isGrantedWorkflowPermission);
} else { } else {
throw new \LogicException('Invalid attribute for StoredObjectVoter'); throw new \LogicException('Invalid attribute for StoredObjectVoter');
} }
$storedObjectVoter = new class ($repository, $workflowRelatedEntityPermissionHelper->reveal(), $security->reveal(), $attachementRepository->reveal()) extends AbstractStoredObjectVoter { $storedObjectVoter = new class ($repository, $workflowRelatedEntityPermissionHelper->reveal(), $security->reveal()) extends AbstractStoredObjectVoter {
public function __construct(private $repository, $helper, $security, EntityWorkflowAttachmentRepository $attachmentRepository) public function __construct(private $repository, $helper, $security)
{ {
parent::__construct($security, $attachmentRepository, $helper); parent::__construct($security, $helper);
} }
protected function getRepository(): AssociatedEntityToStoredObjectInterface protected function getRepository(): AssociatedEntityToStoredObjectInterface
@@ -166,64 +155,96 @@ class AbstractStoredObjectVoterTest extends TestCase
self::assertEquals($expected, $actual); self::assertEquals($expected, $actual);
} }
public static function dataProviderVoteOnAttributeWithWorkflow(): iterable public static function dataProviderVoteOnAttributeWithStoredObjectPermission(): iterable
{ {
foreach (['read' => StoredObjectRoleEnum::SEE, 'write' => StoredObjectRoleEnum::EDIT] as $action => $attribute) { foreach (['read' => StoredObjectRoleEnum::SEE, 'write' => StoredObjectRoleEnum::EDIT] as $action => $attribute) {
yield 'Not related to any workflow nor attachment ('.$action.')' => [ yield 'Not related to any workflow nor attachment ('.$action.')' => [
$attribute, $attribute,
true, true,
true, true,
WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN, WorkflowRelatedEntityPermissionHelper::ABSTAIN,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
]; ];
yield 'Not related to any workflow nor attachment (refuse) ('.$action.')' => [ yield 'Not related to any workflow nor attachment (refuse) ('.$action.')' => [
$attribute, $attribute,
false, false,
false, false,
WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN, WorkflowRelatedEntityPermissionHelper::ABSTAIN,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
]; ];
yield 'Is granted by a workflow takes precedence (workflow) ('.$action.')' => [ yield 'Is granted by a workflow takes precedence (workflow) ('.$action.')' => [
$attribute, $attribute,
false, false,
true, true,
WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
]; ];
yield 'Is granted by a workflow takes precedence (stored object) ('.$action.')' => [ yield 'Is granted by a workflow takes precedence (stored object) ('.$action.')' => [
$attribute, $attribute,
false, false,
true,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
];
yield 'Is granted by a workflow takes precedence (workflow) although grant ('.$action.')' => [
$attribute,
false, false,
WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN, true,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
]; ];
yield 'Is granted by a workflow takes precedence (stored object) although grant ('.$action.')' => [ yield 'Is granted by a workflow takes precedence (stored object) although grant ('.$action.')' => [
$attribute, $attribute,
false,
true, true,
true, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
]; ];
yield 'Is granted by a workflow takes precedence (initially refused) (workflow) although grant ('.$action.')' => [ yield 'Is granted by a workflow takes precedence (initially refused) (workflow) although grant ('.$action.')' => [
$attribute, $attribute,
false, false,
false, false,
WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
]; ];
yield 'Is granted by a workflow takes precedence (initially refused) (stored object) although grant ('.$action.')' => [ yield 'Is granted by a workflow takes precedence (initially refused) (stored object) although grant ('.$action.')' => [
$attribute,
false,
false,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
];
yield 'Force grant inverse the regular permission (workflow) ('.$action.')' => [
$attribute, $attribute,
true, true,
false, false,
WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
]; ];
yield 'Force grant inverse the regular permission (so) ('.$action.')' => [
$attribute,
true,
false,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
];
} }
} }
/** /**
* @dataProvider dataProviderVoteOnAttribute * @dataProvider dataProviderVoteOnAttributeWithoutStoredObjectPermission
*/ */
public function testVoteOnAttribute( public function testVoteOnAttributeWithoutStoredObjectPermission(
StoredObjectRoleEnum $attribute, StoredObjectRoleEnum $attribute,
bool $expected, bool $expected,
bool $canBeAssociatedWithWorkflow, bool $canBeAssociatedWithWorkflow,
@@ -239,7 +260,10 @@ class AbstractStoredObjectVoterTest extends TestCase
$security = $this->prophesize(Security::class); $security = $this->prophesize(Security::class);
$security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission); $security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission);
$workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelperInterface::class); $workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class);
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($storedObject)->willReturn(WorkflowRelatedEntityPermissionHelper::ABSTAIN);
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($storedObject)->willReturn(WorkflowRelatedEntityPermissionHelper::ABSTAIN);
if (null !== $isGrantedWorkflowPermissionRead) { if (null !== $isGrantedWorkflowPermissionRead) {
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related) $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related)
@@ -259,155 +283,27 @@ class AbstractStoredObjectVoterTest extends TestCase
self::assertEquals($expected, $voter->voteOnAttribute($attribute, $storedObject, $token), $message); self::assertEquals($expected, $voter->voteOnAttribute($attribute, $storedObject, $token), $message);
} }
public static function dataProviderVoteOnAttribute(): iterable public static function dataProviderVoteOnAttributeWithoutStoredObjectPermission(): iterable
{ {
// not associated on a workflow // not associated on a workflow
yield [StoredObjectRoleEnum::SEE, true, false, true, null, null, 'not associated on a workflow, granted by regular access, must not rely on helper']; yield [StoredObjectRoleEnum::SEE, true, false, true, null, null, 'not associated on a workflow, granted by regular access, must not rely on helper'];
yield [StoredObjectRoleEnum::SEE, false, false, false, null, null, 'not associated on a workflow, denied by regular access, must not rely on helper']; yield [StoredObjectRoleEnum::SEE, false, false, false, null, null, 'not associated on a workflow, denied by regular access, must not rely on helper'];
// associated on a workflow, read operation // associated on a workflow, read operation
yield [StoredObjectRoleEnum::SEE, true, true, true, WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT, null, 'associated on a workflow, read by regular, force grant by workflow, ask for read, should be granted']; yield [StoredObjectRoleEnum::SEE, true, true, true, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, null, 'associated on a workflow, read by regular, force grant by workflow, ask for read, should be granted'];
yield [StoredObjectRoleEnum::SEE, true, true, true, WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN, null, 'associated on a workflow, read by regular, abstain by workflow, ask for read, should be granted']; yield [StoredObjectRoleEnum::SEE, true, true, true, WorkflowRelatedEntityPermissionHelper::ABSTAIN, null, 'associated on a workflow, read by regular, abstain by workflow, ask for read, should be granted'];
yield [StoredObjectRoleEnum::SEE, false, true, true, WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED, null, 'associated on a workflow, read by regular, force grant by workflow, ask for read, should be denied']; yield [StoredObjectRoleEnum::SEE, false, true, true, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, null, 'associated on a workflow, read by regular, force grant by workflow, ask for read, should be denied'];
yield [StoredObjectRoleEnum::SEE, true, true, false, WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT, null, 'associated on a workflow, denied read by regular, force grant by workflow, ask for read, should be granted']; yield [StoredObjectRoleEnum::SEE, true, true, false, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, null, 'associated on a workflow, denied read by regular, force grant by workflow, ask for read, should be granted'];
yield [StoredObjectRoleEnum::SEE, false, true, false, WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN, null, 'associated on a workflow, denied read by regular, abstain by workflow, ask for read, should be granted']; yield [StoredObjectRoleEnum::SEE, false, true, false, WorkflowRelatedEntityPermissionHelper::ABSTAIN, null, 'associated on a workflow, denied read by regular, abstain by workflow, ask for read, should be granted'];
yield [StoredObjectRoleEnum::SEE, false, true, false, WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED, null, 'associated on a workflow, denied read by regular, force grant by workflow, ask for read, should be denied']; yield [StoredObjectRoleEnum::SEE, false, true, false, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, null, 'associated on a workflow, denied read by regular, force grant by workflow, ask for read, should be denied'];
// association on a workflow, write operation // association on a workflow, write operation
yield [StoredObjectRoleEnum::EDIT, true, true, true, null, WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT, 'associated on a workflow, write by regular, force grant by workflow, ask for write, should be granted']; yield [StoredObjectRoleEnum::EDIT, true, true, true, null, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, 'associated on a workflow, write by regular, force grant by workflow, ask for write, should be granted'];
yield [StoredObjectRoleEnum::EDIT, true, true, true, null, WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN, 'associated on a workflow, write by regular, abstain by workflow, ask for write, should be granted']; yield [StoredObjectRoleEnum::EDIT, true, true, true, null, WorkflowRelatedEntityPermissionHelper::ABSTAIN, 'associated on a workflow, write by regular, abstain by workflow, ask for write, should be granted'];
yield [StoredObjectRoleEnum::EDIT, false, true, true, null, WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED, 'associated on a workflow, write by regular, force grant by workflow, ask for write, should be denied']; yield [StoredObjectRoleEnum::EDIT, false, true, true, null, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, 'associated on a workflow, write by regular, force grant by workflow, ask for write, should be denied'];
yield [StoredObjectRoleEnum::EDIT, true, true, false, null, WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT, 'associated on a workflow, denied write by regular, force grant by workflow, ask for write, should be granted']; yield [StoredObjectRoleEnum::EDIT, true, true, false, null, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, 'associated on a workflow, denied write by regular, force grant by workflow, ask for write, should be granted'];
yield [StoredObjectRoleEnum::EDIT, false, true, false, null, WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN, 'associated on a workflow, denied write by regular, abstain by workflow, ask for write, should be granted']; yield [StoredObjectRoleEnum::EDIT, false, true, false, null, WorkflowRelatedEntityPermissionHelper::ABSTAIN, 'associated on a workflow, denied write by regular, abstain by workflow, ask for write, should be granted'];
yield [StoredObjectRoleEnum::EDIT, false, true, false, null, WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED, 'associated on a workflow, denied write by regular, force grant by workflow, ask for write, should be denied']; yield [StoredObjectRoleEnum::EDIT, false, true, false, null, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, 'associated on a workflow, denied write by regular, force grant by workflow, ask for write, should be denied'];
}
/**
* @dataProvider dataProviderPrecedenceOfDirectAssociationOverWorkflowAttachments
*/
public function testPrecedenceOfDirectAssociationOverWorkflowAttachments(
StoredObjectRoleEnum $attribute,
bool $expected,
bool $regularPermission,
string $directWorkflowPermission,
string $attachmentWorkflowPermission,
string $message,
): void {
$storedObject = new StoredObject();
$repository = new DummyRepository($related = new \stdClass());
$token = new UsernamePasswordToken(new User(), 'dummy');
$security = $this->prophesize(Security::class);
$security->isGranted('SOME_ROLE', $related)->willReturn($regularPermission);
$workflowHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelperInterface::class);
// Direct association permission
if (StoredObjectRoleEnum::SEE === $attribute) {
$workflowHelper->isAllowedByWorkflowForReadOperation($related)
->willReturn($directWorkflowPermission);
} else {
$workflowHelper->isAllowedByWorkflowForWriteOperation($related)
->willReturn($directWorkflowPermission);
}
// Attachment permission
$entityWorkflow = $this->prophesize(\Chill\MainBundle\Entity\Workflow\EntityWorkflow::class)->reveal();
$attachment = $this->prophesize(EntityWorkflowAttachment::class);
$attachment->getEntityWorkflow()->willReturn($entityWorkflow);
if (StoredObjectRoleEnum::SEE === $attribute) {
$workflowHelper->isAllowedByWorkflowForReadOperation($entityWorkflow)
->willReturn($attachmentWorkflowPermission);
} else {
$workflowHelper->isAllowedByWorkflowForWriteOperation($entityWorkflow)
->willReturn($attachmentWorkflowPermission);
}
$voter = $this->buildStoredObjectVoter(
true,
$repository,
$security->reveal(),
$workflowHelper->reveal(),
[$attachment->reveal()]
);
self::assertEquals($expected, $voter->voteOnAttribute($attribute, $storedObject, $token), $message);
}
public static function dataProviderPrecedenceOfDirectAssociationOverWorkflowAttachments(): iterable
{
$cases = [
[
'expected' => true,
'regular' => false,
'direct' => WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT,
'attachment' => WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED,
'message' => 'Direct FORCE_GRANT should win over attachment FORCE_DENIED',
],
[
'expected' => false,
'regular' => true,
'direct' => WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED,
'attachment' => WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT,
'message' => 'Direct FORCE_DENIED should win over attachment FORCE_GRANT',
],
[
'expected' => true,
'regular' => false,
'direct' => WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT,
'attachment' => WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN,
'message' => 'Direct FORCE_GRANT should win over attachment ABSTAIN',
],
[
'expected' => false,
'regular' => true,
'direct' => WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED,
'attachment' => WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN,
'message' => 'Direct FORCE_DENIED should win over attachment ABSTAIN',
],
[
'expected' => true,
'regular' => false,
'direct' => WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN,
'attachment' => WorkflowRelatedEntityPermissionHelperInterface::FORCE_GRANT,
'message' => 'Direct ABSTAIN should let attachment FORCE_GRANT win',
],
[
'expected' => false,
'regular' => true,
'direct' => WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN,
'attachment' => WorkflowRelatedEntityPermissionHelperInterface::FORCE_DENIED,
'message' => 'Direct ABSTAIN should let attachment FORCE_DENIED win',
],
[
'expected' => true,
'regular' => true,
'direct' => WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN,
'attachment' => WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN,
'message' => 'Both ABSTAIN should let regular permission (true) win',
],
[
'expected' => false,
'regular' => false,
'direct' => WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN,
'attachment' => WorkflowRelatedEntityPermissionHelperInterface::ABSTAIN,
'message' => 'Both ABSTAIN should let regular permission (false) win',
],
];
foreach ([StoredObjectRoleEnum::SEE, StoredObjectRoleEnum::EDIT] as $attribute) {
foreach ($cases as $case) {
yield sprintf('%s - %s', $attribute->name, $case['message']) => [
$attribute,
$case['expected'],
$case['regular'],
$case['direct'],
$case['attachment'],
$case['message'],
];
}
}
} }
} }

View File

@@ -1,21 +0,0 @@
module.exports = function (encore) {
encore.addAliases({
ChillDocStoreAssets: __dirname + "/Resources/public",
});
encore.addEntry(
"mod_async_upload",
__dirname + "/Resources/public/module/async_upload/index.ts",
);
encore.addEntry(
"mod_document_action_buttons_group",
__dirname + "/Resources/public/module/document_action_buttons_group/index",
);
encore.addEntry(
"mod_document_download_button",
__dirname + "/Resources/public/module/button_download/index",
);
encore.addEntry(
"vue_document_signature",
__dirname + "/Resources/public/vuejs/DocumentSignature/index",
);
};

View File

@@ -0,0 +1,15 @@
export default {
aliases: {
ChillDocStoreAssets: "./src/Bundle/ChillDocStoreBundle/Resources/public",
},
inputs: {
mod_async_upload:
"./src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts",
mod_document_action_buttons_group:
"./src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts",
mod_document_download_button:
"./src/Bundle/ChillDocStoreBundle/Resources/public/module/button_download/index.ts",
vue_document_signature:
"./src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/index.ts",
},
};

View File

@@ -52,7 +52,7 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
} }
/** (non-PHPdoc). /** (non-PHPdoc).
* @see PrependExtensionInterface::prepend() * @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
*/ */
public function prepend(ContainerBuilder $container): void public function prepend(ContainerBuilder $container): void
{ {

View File

@@ -1 +1 @@
require("./chillevent.scss"); import("./chillevent.scss");

View File

@@ -1,10 +1,10 @@
{% extends '@ChillEvent/layout.html.twig' %} {% block js %} {% extends '@ChillEvent/layout.html.twig' %} {% block js %}
{{ encore_entry_script_tags("mod_async_upload") }} {{ vite_entry_script_tags("mod_async_upload") }}
{{ encore_entry_script_tags("mod_pickentity_type") }} {{ vite_entry_script_tags("mod_pickentity_type") }}
{% endblock %} {% block css %} {% endblock %} {% block css %}
{{ encore_entry_link_tags("mod_async_upload") }} {{ vite_entry_link_tags("mod_async_upload") }}
{{ encore_entry_link_tags("mod_pickentity_type") }} {{ vite_entry_link_tags("mod_pickentity_type") }}
{% endblock %} {% block title 'Event edit'|trans %} {% block event_content -%} {% endblock %} {% block title 'Event edit'|trans %} {% block event_content -%}
<div class="col-10"> <div class="col-10">

View File

@@ -1,9 +1,9 @@
{% extends '@ChillEvent/layout.html.twig' %} {% extends '@ChillEvent/layout.html.twig' %}
{% block css %} {% block css %}
{{ encore_entry_link_tags("mod_async_upload") }} {{ vite_entry_link_tags("mod_async_upload") }}
{{ encore_entry_link_tags("mod_pickentity_type") }} {{ vite_entry_link_tags("mod_pickentity_type") }}
{{ encore_entry_link_tags('vue_event') }} {{ vite_entry_link_tags('vue_event') }}
{% endblock %} {% endblock %}
{% block title 'Event creation'|trans %} {% block title 'Event creation'|trans %}
@@ -52,9 +52,9 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_script_tags("mod_async_upload") }} {{ vite_entry_script_tags("mod_async_upload") }}
{{ encore_entry_script_tags("mod_pickentity_type") }} {{ vite_entry_script_tags("mod_pickentity_type") }}
{{ encore_entry_script_tags('vue_event') }} {{ vite_entry_script_tags('vue_event') }}
<script type="text/javascript"> <script type="text/javascript">
window.entity = {{ entity_json|json_encode|raw }}; window.entity = {{ entity_json|json_encode|raw }};
{% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %} {% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %}

View File

@@ -1,10 +1,10 @@
{% extends '@ChillEvent/layout.html.twig' %} {% block title 'Events'|trans %} {% {% extends '@ChillEvent/layout.html.twig' %} {% block title 'Events'|trans %} {%
block js %} block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags("mod_pickentity_type") }} {{ vite_entry_script_tags("mod_pickentity_type") }}
{% endblock %} {% block css %} {% endblock %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags("mod_pickentity_type") }} {{ vite_entry_link_tags("mod_pickentity_type") }}
{% endblock %} {% block content %} {% endblock %} {% block content %}
<div class="col-10"> <div class="col-10">
<h1>{{ block("title") }}</h1> <h1>{{ block("title") }}</h1>

View File

@@ -6,14 +6,14 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ vite_entry_script_tags('mod_pickentity_type') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ vite_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_pickentity_type') }} {{ vite_entry_link_tags('mod_pickentity_type') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ vite_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %} {% endblock %}
{% macro insert_onthefly(type, entity, parent = null) %} {% macro insert_onthefly(type, entity, parent = null) %}

View File

@@ -14,7 +14,6 @@ namespace Chill\EventBundle\Security\Authorization;
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface; use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter; use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper; use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
use Chill\EventBundle\Entity\Event; use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Repository\EventRepository; use Chill\EventBundle\Repository\EventRepository;
@@ -27,9 +26,8 @@ class EventStoredObjectVoter extends AbstractStoredObjectVoter
private readonly EventRepository $repository, private readonly EventRepository $repository,
Security $security, Security $security,
WorkflowRelatedEntityPermissionHelper $workflowDocumentService, WorkflowRelatedEntityPermissionHelper $workflowDocumentService,
EntityWorkflowAttachmentRepository $attachmentRepository,
) { ) {
parent::__construct($security, $attachmentRepository, $workflowDocumentService); parent::__construct($security, $workflowDocumentService);
} }
protected function getRepository(): AssociatedEntityToStoredObjectInterface protected function getRepository(): AssociatedEntityToStoredObjectInterface

View File

@@ -1,10 +0,0 @@
module.exports = function (encore, entries) {
entries.push(__dirname + "/Resources/public/chill/index.js");
encore.addEntry(
"vue_event",
__dirname + "/Resources/public/vuejs/index.js",
);
};

View File

@@ -0,0 +1,8 @@
export default {
chillBase: [
"./src/Bundle/ChillEventBundle/Resources/public/chill/index.js",
],
inputs: {
vue_event: "./src/Bundle/ChillEventBundle/Resources/public/vuejs/index.js",
},
};

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