diff --git a/.env b/.env index ed844e0..395355e 100644 --- a/.env +++ b/.env @@ -5,6 +5,7 @@ ###> aakb/itkdev-docker configuration ### COMPOSE_PROJECT_NAME=itstyr COMPOSE_DOMAIN=itstyr.local.itkdev.dk +ITKDEV_TEMPLATE=symfony-6 ###< aakb/itkdev-docker configuration ### ###> symfony/framework-bundle ### diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index b62c51a..483da6e 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -2,7 +2,7 @@ # github/workflows/changelog.yaml in # https://github.com/itk-dev/devops_itkdev-docker if need be. -### ## Changelog +### ### Changelog ### ### Checks that changelog has been updated diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml index aa65bd6..fe13351 100644 --- a/.github/workflows/composer.yaml +++ b/.github/workflows/composer.yaml @@ -2,11 +2,11 @@ # github/workflows/composer.yaml in # https://github.com/itk-dev/devops_itkdev-docker if need be. -### ## Composer +### ### Composer ### ### Validates composer.json and checks that it's normalized. ### -### ### Assumptions +### #### Assumptions ### ### 1. A docker compose service named `phpfpm` can be run and `composer` can be ### run inside the `phpfpm` service. @@ -31,6 +31,9 @@ env: on: pull_request: push: + branches: + - main + - develop jobs: composer-validate: diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml new file mode 100644 index 0000000..5b20c8d --- /dev/null +++ b/.github/workflows/javascript.yaml @@ -0,0 +1,36 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/symfony/javascript.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Symfony JavaScript (and TypeScript) +### +### Validates JavaScript files. +### +### #### Assumptions +### +### 1. A docker compose service named `prettier` for running +### [Prettier](https://prettier.io/) exists. + +name: JavaScript + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + javascript-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + + - run: | + docker network create frontend + + - run: | + docker compose run --rm prettier 'assets/**/*.js' --check diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml index 12df4d7..60fc0ee 100644 --- a/.github/workflows/markdown.yaml +++ b/.github/workflows/markdown.yaml @@ -2,19 +2,30 @@ # github/workflows/markdown.yaml in # https://github.com/itk-dev/devops_itkdev-docker if need be. -### ## Markdown +### ### Markdown ### -### Uses [itkdev/markdownlint](https://hub.docker.com/r/itkdev/markdownlint) to -### link all Markdown files (`**/*.md`) in the project. +### Lints Markdown files (`**/*.md`) in the project. ### -### [markdownlint-cli configuration ### files](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration), -### `.markdownlint.jsonc` and `.markdownlintignore` control what is actually linted and how. +### [markdownlint-cli configuration +### files](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration), +### `.markdownlint.jsonc` and `.markdownlintignore`, control what is actually +### linted and how. +### +### #### Assumptions +### +### 1. A docker compose service named `markdownlint` for running `markdownlint` +### (from +### [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli)) +### exists. name: Markdown on: pull_request: push: + branches: + - main + - develop jobs: markdown-lint: @@ -26,4 +37,7 @@ jobs: uses: actions/checkout@v4 - run: | - docker run --rm --volume "$PWD":/md itkdev/markdownlint '**/*.md' + docker network create frontend + + - run: | + docker compose run --rm markdownlint markdownlint '**/*.md' diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml new file mode 100644 index 0000000..60fb70e --- /dev/null +++ b/.github/workflows/php.yaml @@ -0,0 +1,56 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/symfony/php.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Symfony PHP +### +### Checks that PHP code adheres to the [Symfony coding +### standards](https://symfony.com/doc/current/contributing/code/standards.html). +### +### #### Assumptions +### +### 1. A docker compose service named `phpfpm` can be run and `composer` can be +### run inside the `phpfpm` service. 2. +### [friendsofphp/php-cs-fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) +### is a dev requirement in `composer.json`: +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev friendsofphp/php-cs-fixer +### ``` +### +### Clean up and check code by running +### +### ``` shell +### docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix +### docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff +### ``` +### +### > [!NOTE] The template adds `.php-cs-fixer.dist.php` as [a configuration +### > file for PHP CS +### > Fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/config.rst) +### > and this makes it possible to override the actual configuration used in a +### > project by adding a more important configuration file, `.php-cs-fixer.php`. + +name: Symfony PHP + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + coding-standards: + name: PHP - Check Coding Standards + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: | + docker network create frontend + docker compose run --rm phpfpm composer install + # https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/usage.rst#the-check-command + docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/.github/workflows/styles.yaml b/.github/workflows/styles.yaml new file mode 100644 index 0000000..edc7960 --- /dev/null +++ b/.github/workflows/styles.yaml @@ -0,0 +1,36 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/symfony/styles.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Symfony Styles (CSS and SCSS) +### +### Validates styles files. +### +### #### Assumptions +### +### 1. A docker compose service named `prettier` for running +### [Prettier](https://prettier.io/) exists. + +name: Styles + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + styles-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + + - run: | + docker network create frontend + + - run: | + docker compose run --rm prettier 'assets/**/*.{css,scss}' --check diff --git a/.github/workflows/twig.yaml b/.github/workflows/twig.yaml new file mode 100644 index 0000000..9b0e343 --- /dev/null +++ b/.github/workflows/twig.yaml @@ -0,0 +1,48 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/twig.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Twig +### +### Validates Twig files +### +### #### Assumptions +### +### 1. A docker compose service named `phpfpm` can be run and `composer` can be +### run inside the `phpfpm` service. +### 2. [vincentlanglet/twig-cs-fixer](https://github.com/VincentLanglet/Twig-CS-Fixer) +### is a dev requirement in `composer.json`: +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev vincentlanglet/twig-cs-fixer +### ``` +### +### 3. A [Configuration +### file](https://github.com/VincentLanglet/Twig-CS-Fixer/blob/main/docs/configuration.md#configuration-file) +### in the root of the project defines which files to check and rules to use. + +name: Twig + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + twig-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + + - run: | + docker network create frontend + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc index 37d8959..0253096 100644 --- a/.markdownlint.jsonc +++ b/.markdownlint.jsonc @@ -1,3 +1,6 @@ +// This file is copied from config/markdown/.markdownlint.jsonc in https://github.com/itk-dev/devops_itkdev-docker. +// Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + // markdownlint-cli configuration file (cf. https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration) { "default": true, diff --git a/.markdownlintignore b/.markdownlintignore index 3869d41..fe1b7fe 100644 --- a/.markdownlintignore +++ b/.markdownlintignore @@ -1,3 +1,6 @@ +# This file is copied from config/markdown/.markdownlintignore in https://github.com/itk-dev/devops_itkdev-docker. +# Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + # https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#ignoring-files vendor/ node_modules/ @@ -5,4 +8,9 @@ LICENSE.md # Drupal web/*.md web/core/ +web/libraries/ web/*/contrib/ + +# Project ignores +mappings.md +templates/svg/font-awesome-info.md diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index c4c8edb..ca0693b 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,16 +1,23 @@ in(__DIR__) - ->exclude('var') -; +// https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/config.rst -return (new PhpCsFixer\Config()) - ->setRules([ - '@Symfony' => true, - 'phpdoc_align' => false, - // Don't remove @param that define collection and array generics (yes they should have complete comments, but). - 'no_superfluous_phpdoc_tags' => false, - ]) - ->setFinder($finder) -; +$finder = new PhpCsFixer\Finder(); +// Check all files … +$finder->in(__DIR__); +// … that are not ignored by VCS +$finder->ignoreVCSIgnored(true); + +$config = new PhpCsFixer\Config(); +$config->setFinder($finder); + +$config->setRules([ + '@Symfony' => true, + 'phpdoc_align' => false, + // Don't remove @param that define collection and array generics (yes they should have complete comments, but). + 'no_superfluous_phpdoc_tags' => false, +]); + +return $config; diff --git a/.twig-cs-fixer.dist.php b/.twig-cs-fixer.dist.php new file mode 100644 index 0000000..8242555 --- /dev/null +++ b/.twig-cs-fixer.dist.php @@ -0,0 +1,16 @@ +in(__DIR__); +// … that are not ignored by VCS +$finder->ignoreVCSIgnored(true); + +$config = new TwigCsFixer\Config\Config(); +$config->setFinder($finder); + +return $config; diff --git a/CHANGELOG.md b/CHANGELOG.md index f17b74a..e9ff485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ See [keep a changelog] for information about writing changes to this log. ## [Unreleased] +* [PR-33](https://github.com/itk-dev/sysstatus/pull/33) + * Inlined actions + * Cleaned up report views * [PR-32](https://github.com/itk-dev/sysstatus/pull/32) 3644: Cleaned up template and controller * [PR-31](https://github.com/itk-dev/sysstatus/pull/31) diff --git a/Taskfile.yml b/Taskfile.yml index 42ed500..73241e7 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -34,21 +34,36 @@ tasks: coding-standards:apply: desc: "Apply coding standards" cmds: + - task: coding-standards:assets:apply - task: coding-standards:composer:apply - task: coding-standards:markdown:apply - task: coding-standards:php:apply - task: coding-standards:twig:apply + - task: coding-standards:yaml:apply silent: true coding-standards:check: desc: "Apply coding standards" cmds: + - task: coding-standards:assets:check - task: coding-standards:composer:check - task: coding-standards:markdown:check - task: coding-standards:php:check - task: coding-standards:twig:check + - task: coding-standards:yaml:check silent: true + coding-standards:assets:apply: + desc: "Apply coding standards for assets" + cmds: + - task compose -- run --rm prettier 'assets/' --write + + coding-standards:assets:check: + desc: "Apply and check coding standards for assets" + cmds: + - task: coding-standards:assets:apply + - task compose -- run --rm prettier 'assets/' --check + coding-standards:composer:apply: desc: "Apply coding standards for Composer" cmds: @@ -101,6 +116,17 @@ tasks: - task compose -- exec phpfpm vendor/bin/twig-cs-fixer check silent: true + coding-standards:yaml:apply: + desc: "Apply coding standards for YAML" + cmds: + - task compose -- run --rm prettier --parser yaml phpstan*.neon 'config/**/*.{yml,yaml}' --write + + coding-standards:yaml:check: + desc: "Apply and check coding standards for YAML" + cmds: + - task: coding-standards:yaml:apply + - task compose -- run --rm prettier --parser yaml phpstan*.neon 'config/**/*.{yml,yaml}' --check + code-analysis: cmds: - task compose -- exec phpfpm vendor/bin/phpstan diff --git a/assets/app.js b/assets/app.js index aec61e6..683fbe7 100644 --- a/assets/app.js +++ b/assets/app.js @@ -4,4 +4,4 @@ * This file will be included onto the page via the importmap() Twig function, * which should already be in your base.html.twig. */ -import './styles/app.css'; +import "./styles/app.css"; diff --git a/assets/styles/app.css b/assets/styles/app.css index aac723b..335396f 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -1,226 +1,322 @@ :root { - --body-max-width: 100%; - --font-size-xs: 0.7rem; + --body-max-width: 100%; + --font-size-xs: 0.7rem; } .wrapper { - overflow: auto; + overflow: auto; } .header-icon { - font-size: 2.5em; + font-size: 2.5em; } .field-smiley--title-hover { - cursor: default; + cursor: default; } .edit-icon { - font-size: 1.5em; - font-weight: bold; - color: grey; + font-size: 1.5em; + font-weight: bold; + color: grey; } .edit-icon:hover { - color: black; + color: black; } .smiley-table .smiley-icon { - width: 30px; + width: 30px; } .easyadmin-override--show-actions { - float: right; + float: right; } .custom-filters--submit-button, .custom-filters--submit-button:hover, .custom-filters--submit-button:focus { - border-top-left-radius: 0; - border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; } .custom-filters .form-action { - display: flex; + display: flex; } .custom-filters > div:first-child { - flex-grow: 1; + flex-grow: 1; } .custom-filters { - margin-bottom: 0.5em; + margin-bottom: 0.5em; } .custom-filters select.form-control { - -webkit-appearance: none; - -moz-appearance: none; - -webkit-border-radius: 0; - background-image: url("../images/arrow-down.svg"); - background-position: 95% 50%; - background-repeat: no-repeat; - background-size: 16px; + -webkit-appearance: none; + -moz-appearance: none; + -webkit-border-radius: 0; + background-image: url("../images/arrow-down.svg"); + background-position: 95% 50%; + background-repeat: no-repeat; + background-size: 16px; } .custom-filters select:-moz-focusring { - color: transparent; - text-shadow: 0 0 0 #000; + color: transparent; + text-shadow: 0 0 0 #000; } .custom-filters option:not(:checked) { - color: black; + color: black; } .custom-filters select::-ms-expand { - display: none; + display: none; +} + +.table-bordered thead > tr > th { + border: 0px; } .table td, .table th { - vertical-align: middle; + vertical-align: middle; } .table td h3, .table th h3 { - margin-bottom: 0; + margin-bottom: 0; } td.dashboard--table-answer { - width: 55px; - height: 45px; + width: 55px; + height: 45px; +} + +body.list table thead th.rotate a { + color: #337ab7; + padding: 0; +} + +body.list table thead th.rotate a:hover { + background: transparent; } body.list table tbody td.dashboard--table-answer { - text-align: center; + text-align: center; } .dashboard--table-spacing { - background-color: #eee; - height: 30px; + background-color: #eee; + height: 30px; } .dashboard--table { - overflow-x: auto; + overflow-x: auto; } .dashboard--last-column { - width: 30px; + width: 30px; } .smiley-icon + .tooltip > .tooltip-inner { - background-color: #fff; - border: 3px solid #000; - padding: 10px; - color: #000; - font-size: 1.2em; - text-align: left; + background-color: #fff; + border: 3px solid #000; + padding: 10px; + color: #000; + font-size: 1.2em; + text-align: left; } .smiley-icon + .tooltip > .tooltip-inner { - max-width: 800px; - white-space: pre-wrap; + max-width: 800px; + white-space: pre-wrap; } .smiley-icon + .in { - opacity: 1 !important; + opacity: 1 !important; } .smiley-icon-green svg path { - fill: #008855; + fill: #008855; } .smiley-icon-yellow svg path { - fill: #f6bd1d; + fill: #f6bd1d; } .smiley-icon-red svg path { - fill: #d32f2f; + fill: #d32f2f; } .smiley-icon-blue svg path { - fill: #3661d8; + fill: #3661d8; } .smiley-icon-grey svg path { - fill: #666666; + fill: #666666; } .export-section { - margin-top: 2em; - margin-bottom: 2em; + margin-top: 2em; + margin-bottom: 2em; } .export-form { - display: flex; - flex-direction: column; - width: 500px; - align-items: flex-start; - justify-content: flex-start; + display: flex; + flex-direction: column; + width: 500px; + align-items: flex-start; + justify-content: flex-start; } .login-section { - display: flex; - justify-content: center; + display: flex; + justify-content: center; } /* Fix position of table heads while scrolling */ .affix { - top: 0; - z-index: 1040; + top: 0; + z-index: 1040; } .table-right-column { - min-width: 100px; + min-width: 100px; } .dashboard--table { - overflow: hidden; + overflow: hidden; } .pagination { - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; } .current, .page, .next, .last { - margin: 5px; + margin: 5px; } .dashboard--table-cell { - display: flex; - justify-content: center; - align-items: center; + display: flex; + justify-content: center; + align-items: center; } .form-errors { - color: red; + color: red; } .question-form-textarea { - width: 300px; + width: 300px; } +/* .ea-detail .field-group .field-label { - width: 200px !important; + width: 200px !important; } .dashboard--table { - table { - th { - vertical-align: top; - font-size: var(--font-size-xs); - font-weight: normal; - line-height: 1.1; - } + table { + th { + vertical-align: top; + font-size: var(--font-size-xs); + font-weight: normal; + line-height: 1.1; } + } } +*/ /* Don't let fragment links stand out */ h2 { - a { - color: inherit; + a { + color: inherit; + } +} + +.ea-detail { + .field-group { + /* Show field values like input controls for improved (?) readability */ + .field-label { + margin-top: 5px; + } + + .field-value { + border: var(--border-width) var(--border-style) var(--border-color); + padding: 4px 7px 5px; + border-radius: var(--border-radius); + } + } + + /* Un-flex label and value for boolean fields back to label: value */ + .field-group.field-boolean { + flex-direction: initial; + + .field-label { + flex: initial; + margin: 0 15px 0 0; + min-inline-size: initial; + text-align: right; + } + + .field-value { + flex: initial; + inline-size: initial; + min-inline-size: initial; + text-align: initial; + } + } +} + +th.rotate { + height: 200px; + white-space: nowrap; + max-width: 55px; + padding: 0; + vertical-align: bottom; + + > div { + transform-origin: top left; + transform: translate(0px, 55px) rotate(315deg); + width: 300px; + max-height: 55px; + border-top: 1px solid #dee2e6; + padding: 5px 0 0 40px; + height: 55px; + + > span { + padding: 5px 10px 5px 0px; + display: block; + width: 265px; + overflow: hidden; + text-overflow: ellipsis; + + a { + width: 800px; + } + } + } +} + +/* Help our theme.themeCategories and category.questions table stuff fall into place visually (cf. template/admin/form.html.twig) */ +.field-collection .table-ea-collection { + td { + vertical-align: top; + } + + tr.field-collection-item { + display: table-row; + + .field-collection-delete-button { + inset-block-start: initial; + inset-inline-end: initial; + position: initial; } + } } diff --git a/composer.lock b/composer.lock index 7e3e0d7..9b3f621 100644 --- a/composer.lock +++ b/composer.lock @@ -53,6 +53,7 @@ "issues": "https://github.com/Behat/Transliterator/issues", "source": "https://github.com/Behat/Transliterator/tree/v1.5.0" }, + "abandoned": true, "time": "2022-03-30T09:27:43+00:00" }, { @@ -598,26 +599,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -637,9 +641,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/doctrine-bundle", @@ -765,16 +769,16 @@ }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "3.4.1", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "e858ce0f5c12b266dce7dce24834448355155da7" + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/e858ce0f5c12b266dce7dce24834448355155da7", - "reference": "e858ce0f5c12b266dce7dce24834448355155da7", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/5a6ac7120c2924c4c070a869d08b11ccf9e277b9", + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9", "shasum": "" }, "require": { @@ -788,7 +792,6 @@ "composer/semver": "^3.0", "doctrine/coding-standard": "^12", "doctrine/orm": "^2.6 || ^3", - "doctrine/persistence": "^2.0 || ^3", "phpstan/phpstan": "^1.4 || ^2", "phpstan/phpstan-deprecation-rules": "^1 || ^2", "phpstan/phpstan-phpunit": "^1 || ^2", @@ -831,7 +834,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.4.1" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.4.2" }, "funding": [ { @@ -847,7 +850,7 @@ "type": "tidelift" } ], - "time": "2025-01-27T22:48:22+00:00" + "time": "2025-03-11T17:36:26+00:00" }, { "name": "doctrine/event-manager", @@ -1283,16 +1286,16 @@ }, { "name": "doctrine/orm", - "version": "2.20.2", + "version": "2.20.3", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "19912de9270fa6abb3d25a1a37784af6b818d534" + "reference": "17d28b5c4cac212ca92730d9a26e32a0b1516126" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/19912de9270fa6abb3d25a1a37784af6b818d534", - "reference": "19912de9270fa6abb3d25a1a37784af6b818d534", + "url": "https://api.github.com/repos/doctrine/orm/zipball/17d28b5c4cac212ca92730d9a26e32a0b1516126", + "reference": "17d28b5c4cac212ca92730d9a26e32a0b1516126", "shasum": "" }, "require": { @@ -1319,14 +1322,14 @@ }, "require-dev": { "doctrine/annotations": "^1.13 || ^2", - "doctrine/coding-standard": "^9.0.2 || ^12.0", + "doctrine/coding-standard": "^9.0.2 || ^13.0", "phpbench/phpbench": "^0.16.10 || ^1.0", "phpstan/extension-installer": "~1.1.0 || ^1.4", "phpstan/phpstan": "~1.4.10 || 2.0.3", "phpstan/phpstan-deprecation-rules": "^1 || ^2", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "psr/log": "^1 || ^2 || ^3", - "squizlabs/php_codesniffer": "3.7.2", + "squizlabs/php_codesniffer": "3.12.0", "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0", "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0", "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0" @@ -1379,9 +1382,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.20.2" + "source": "https://github.com/doctrine/orm/tree/2.20.3" }, - "time": "2025-02-04T19:17:01+00:00" + "time": "2025-05-02T17:07:53+00:00" }, { "name": "doctrine/persistence", @@ -1536,16 +1539,16 @@ }, { "name": "easycorp/easyadmin-bundle", - "version": "v4.24.5", + "version": "v4.24.7", "source": { "type": "git", "url": "https://github.com/EasyCorp/EasyAdminBundle.git", - "reference": "dcc64d06fc142f894667439e72837aa3742b5460" + "reference": "b61cd46c454d853438ded3ca21eac1ad537589aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/dcc64d06fc142f894667439e72837aa3742b5460", - "reference": "dcc64d06fc142f894667439e72837aa3742b5460", + "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/b61cd46c454d853438ded3ca21eac1ad537589aa", + "reference": "b61cd46c454d853438ded3ca21eac1ad537589aa", "shasum": "" }, "require": { @@ -1626,7 +1629,7 @@ ], "support": { "issues": "https://github.com/EasyCorp/EasyAdminBundle/issues", - "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.24.5" + "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.24.7" }, "funding": [ { @@ -1634,7 +1637,7 @@ "type": "github" } ], - "time": "2025-03-10T19:31:19+00:00" + "time": "2025-05-14T17:55:05+00:00" }, { "name": "ezyang/htmlpurifier", @@ -1791,16 +1794,16 @@ }, { "name": "gedmo/doctrine-extensions", - "version": "v3.19.0", + "version": "v3.20.0", "source": { "type": "git", "url": "https://github.com/doctrine-extensions/DoctrineExtensions.git", - "reference": "5b0b8a442d19e6701ae64535dc08f7944e2895d2" + "reference": "ea1d37586b8e4bae2a815feb38b177894b12c44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/5b0b8a442d19e6701ae64535dc08f7944e2895d2", - "reference": "5b0b8a442d19e6701ae64535dc08f7944e2895d2", + "url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/ea1d37586b8e4bae2a815feb38b177894b12c44c", + "reference": "ea1d37586b8e4bae2a815feb38b177894b12c44c", "shasum": "" }, "require": { @@ -1895,10 +1898,9 @@ "uploadable" ], "support": { - "email": "gediminas.morkevicius@gmail.com", + "docs": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc", "issues": "https://github.com/doctrine-extensions/DoctrineExtensions/issues", - "source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.19.0", - "wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc" + "source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.20.0" }, "funding": [ { @@ -1918,7 +1920,7 @@ "type": "github" } ], - "time": "2025-02-24T22:12:57+00:00" + "time": "2025-04-04T17:19:27+00:00" }, { "name": "knplabs/knp-components", @@ -2009,16 +2011,16 @@ }, { "name": "knplabs/knp-paginator-bundle", - "version": "v6.7.0", + "version": "v6.8.1", "source": { "type": "git", "url": "https://github.com/KnpLabs/KnpPaginatorBundle.git", - "reference": "a346431b9f0cddac8cd03d63e70911b6bf70ffde" + "reference": "038c50cf7d5da5e55533f6e803aeae73eb8b8d45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/KnpLabs/KnpPaginatorBundle/zipball/a346431b9f0cddac8cd03d63e70911b6bf70ffde", - "reference": "a346431b9f0cddac8cd03d63e70911b6bf70ffde", + "url": "https://api.github.com/repos/KnpLabs/KnpPaginatorBundle/zipball/038c50cf7d5da5e55533f6e803aeae73eb8b8d45", + "reference": "038c50cf7d5da5e55533f6e803aeae73eb8b8d45", "shasum": "" }, "require": { @@ -2034,8 +2036,8 @@ "twig/twig": "^3.0" }, "require-dev": { - "phpstan/phpstan": "^1.11", - "phpunit/phpunit": "^10.5 || ^11.3", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^10.5 || ^11.5 || ^12.0", "symfony/expression-language": "^6.4 || ^7.0", "symfony/templating": "^6.4 || ^7.0" }, @@ -2077,9 +2079,9 @@ ], "support": { "issues": "https://github.com/KnpLabs/KnpPaginatorBundle/issues", - "source": "https://github.com/KnpLabs/KnpPaginatorBundle/tree/v6.7.0" + "source": "https://github.com/KnpLabs/KnpPaginatorBundle/tree/v6.8.1" }, - "time": "2025-03-02T16:36:05+00:00" + "time": "2025-05-20T07:07:41+00:00" }, { "name": "maennchen/zipstream-php", @@ -2835,36 +2837,37 @@ }, { "name": "stof/doctrine-extensions-bundle", - "version": "v1.13.0", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/stof/StofDoctrineExtensionsBundle.git", - "reference": "bf00701bff3f2b7c4bf6d963101c9ea6968d694f" + "reference": "bdf3eb10baeb497ac5985b8f78a6cf55862c2662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stof/StofDoctrineExtensionsBundle/zipball/bf00701bff3f2b7c4bf6d963101c9ea6968d694f", - "reference": "bf00701bff3f2b7c4bf6d963101c9ea6968d694f", + "url": "https://api.github.com/repos/stof/StofDoctrineExtensionsBundle/zipball/bdf3eb10baeb497ac5985b8f78a6cf55862c2662", + "reference": "bdf3eb10baeb497ac5985b8f78a6cf55862c2662", "shasum": "" }, "require": { - "gedmo/doctrine-extensions": "^3.15.0", - "php": "^7.4 || ^8.0", - "symfony/cache": "^5.4 || ^6.0 || ^7.0", - "symfony/config": "^5.4 || ^6.0 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", - "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0" + "gedmo/doctrine-extensions": "^3.20.0", + "php": "^8.1", + "symfony/cache": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/translation-contracts": "^2.5 || ^3.5" }, "require-dev": { - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpstan/phpstan-strict-rules": "^1.5", - "phpstan/phpstan-symfony": "^1.3", - "symfony/mime": "^5.4 || ^6.0 || ^7.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "symfony/mime": "^6.4 || ^7.0", "symfony/phpunit-bridge": "^v6.4.1 || ^7.0.1", - "symfony/security-core": "^5.4 || ^6.0 || ^7.0" + "symfony/security-core": "^6.4 || ^7.0" }, "suggest": { "doctrine/doctrine-bundle": "to use the ORM extensions", @@ -2909,9 +2912,9 @@ ], "support": { "issues": "https://github.com/stof/StofDoctrineExtensionsBundle/issues", - "source": "https://github.com/stof/StofDoctrineExtensionsBundle/tree/v1.13.0" + "source": "https://github.com/stof/StofDoctrineExtensionsBundle/tree/v1.14.0" }, - "time": "2025-01-07T08:10:54+00:00" + "time": "2025-05-01T08:00:32+00:00" }, { "name": "symfony/asset", @@ -2984,16 +2987,16 @@ }, { "name": "symfony/asset-mapper", - "version": "v7.2.3", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/asset-mapper.git", - "reference": "d9a514cbaba040691d5b10afc20755590d2ac80a" + "reference": "6428e4b6d8cff9c5fe6f40ddbee4c9f6bfdaa0b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/d9a514cbaba040691d5b10afc20755590d2ac80a", - "reference": "d9a514cbaba040691d5b10afc20755590d2ac80a", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/6428e4b6d8cff9c5fe6f40ddbee4c9f6bfdaa0b8", + "reference": "6428e4b6d8cff9c5fe6f40ddbee4c9f6bfdaa0b8", "shasum": "" }, "require": { @@ -3043,7 +3046,7 @@ "description": "Maps directories of assets & makes them available in a public directory with versioned filenames.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset-mapper/tree/v7.2.3" + "source": "https://github.com/symfony/asset-mapper/tree/v7.2.5" }, "funding": [ { @@ -3059,20 +3062,20 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-03-26T11:29:07+00:00" }, { "name": "symfony/cache", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "d33cd9e14326e14a4145c21e600602eaf17cc9e7" + "reference": "8b49dde3f5a5e9867595a3a269977f78418d75ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/d33cd9e14326e14a4145c21e600602eaf17cc9e7", - "reference": "d33cd9e14326e14a4145c21e600602eaf17cc9e7", + "url": "https://api.github.com/repos/symfony/cache/zipball/8b49dde3f5a5e9867595a3a269977f78418d75ee", + "reference": "8b49dde3f5a5e9867595a3a269977f78418d75ee", "shasum": "" }, "require": { @@ -3141,7 +3144,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.2.4" + "source": "https://github.com/symfony/cache/tree/v7.2.6" }, "funding": [ { @@ -3157,7 +3160,7 @@ "type": "tidelift" } ], - "time": "2025-02-26T09:57:54+00:00" + "time": "2025-04-08T09:06:23+00:00" }, { "name": "symfony/cache-contracts", @@ -3311,16 +3314,16 @@ }, { "name": "symfony/config", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "7716594aaae91d9141be080240172a92ecca4d44" + "reference": "e0b050b83ba999aa77a3736cb6d5b206d65b9d0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/7716594aaae91d9141be080240172a92ecca4d44", - "reference": "7716594aaae91d9141be080240172a92ecca4d44", + "url": "https://api.github.com/repos/symfony/config/zipball/e0b050b83ba999aa77a3736cb6d5b206d65b9d0d", + "reference": "e0b050b83ba999aa77a3736cb6d5b206d65b9d0d", "shasum": "" }, "require": { @@ -3366,7 +3369,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.2.3" + "source": "https://github.com/symfony/config/tree/v7.2.6" }, "funding": [ { @@ -3382,20 +3385,20 @@ "type": "tidelift" } ], - "time": "2025-01-22T12:07:01+00:00" + "time": "2025-04-03T21:14:15+00:00" }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218", "shasum": "" }, "require": { @@ -3459,7 +3462,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.2.6" }, "funding": [ { @@ -3475,20 +3478,20 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f0a1614cccb4b8431a97076f9debc08ddca321ca" + "reference": "2ca85496cde37f825bd14f7e3548e2793ca90712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f0a1614cccb4b8431a97076f9debc08ddca321ca", - "reference": "f0a1614cccb4b8431a97076f9debc08ddca321ca", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/2ca85496cde37f825bd14f7e3548e2793ca90712", + "reference": "2ca85496cde37f825bd14f7e3548e2793ca90712", "shasum": "" }, "require": { @@ -3496,7 +3499,7 @@ "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^3.5", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^6.4.20|^7.2.5" }, "conflict": { "ext-psr": "<1.1|>=2", @@ -3539,7 +3542,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.2.4" + "source": "https://github.com/symfony/dependency-injection/tree/v7.2.6" }, "funding": [ { @@ -3555,7 +3558,7 @@ "type": "tidelift" } ], - "time": "2025-02-21T09:47:16+00:00" + "time": "2025-04-27T13:37:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3626,16 +3629,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "95bc5dde5202828bbc462bc06ba67cd244fa8a15" + "reference": "d030ea0d45746bf58d7905402bd45e9c35d412dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/95bc5dde5202828bbc462bc06ba67cd244fa8a15", - "reference": "95bc5dde5202828bbc462bc06ba67cd244fa8a15", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/d030ea0d45746bf58d7905402bd45e9c35d412dd", + "reference": "d030ea0d45746bf58d7905402bd45e9c35d412dd", "shasum": "" }, "require": { @@ -3715,7 +3718,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v7.2.4" + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.2.6" }, "funding": [ { @@ -3731,7 +3734,7 @@ "type": "tidelift" } ], - "time": "2025-02-18T16:43:05+00:00" + "time": "2025-04-27T13:34:41+00:00" }, { "name": "symfony/dotenv", @@ -3809,16 +3812,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "aabf79938aa795350c07ce6464dd1985607d95d5" + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/aabf79938aa795350c07ce6464dd1985607d95d5", - "reference": "aabf79938aa795350c07ce6464dd1985607d95d5", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", "shasum": "" }, "require": { @@ -3864,7 +3867,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.4" + "source": "https://github.com/symfony/error-handler/tree/v7.2.5" }, "funding": [ { @@ -3880,7 +3883,7 @@ "type": "tidelift" } ], - "time": "2025-02-02T20:27:07+00:00" + "time": "2025-03-03T07:12:39+00:00" }, { "name": "symfony/event-dispatcher", @@ -4170,16 +4173,16 @@ }, { "name": "symfony/flex", - "version": "v2.5.0", + "version": "v2.6.0", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "8ce1acd9842abe0e9b4c4a0bd3f259859516c018" + "reference": "ccc4d2dc15e90f0fed555d6078c3698396306cdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/8ce1acd9842abe0e9b4c4a0bd3f259859516c018", - "reference": "8ce1acd9842abe0e9b4c4a0bd3f259859516c018", + "url": "https://api.github.com/repos/symfony/flex/zipball/ccc4d2dc15e90f0fed555d6078c3698396306cdd", + "reference": "ccc4d2dc15e90f0fed555d6078c3698396306cdd", "shasum": "" }, "require": { @@ -4218,7 +4221,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.5.0" + "source": "https://github.com/symfony/flex/tree/v2.6.0" }, "funding": [ { @@ -4234,20 +4237,20 @@ "type": "tidelift" } ], - "time": "2025-03-03T07:50:46+00:00" + "time": "2025-05-21T07:23:28+00:00" }, { "name": "symfony/form", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "7209804c018b88cc2b0beabe38eef91b83f1d69a" + "reference": "e4e75b930d7a1ccd47bd3273c859c28e13d89b08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/7209804c018b88cc2b0beabe38eef91b83f1d69a", - "reference": "7209804c018b88cc2b0beabe38eef91b83f1d69a", + "url": "https://api.github.com/repos/symfony/form/zipball/e4e75b930d7a1ccd47bd3273c859c28e13d89b08", + "reference": "e4e75b930d7a1ccd47bd3273c859c28e13d89b08", "shasum": "" }, "require": { @@ -4315,7 +4318,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v7.2.4" + "source": "https://github.com/symfony/form/tree/v7.2.6" }, "funding": [ { @@ -4331,20 +4334,20 @@ "type": "tidelift" } ], - "time": "2025-02-07T22:04:27+00:00" + "time": "2025-04-30T07:52:47+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "6d6614378cd8128eed0a037ce6ac51a26c5aaed5" + "reference": "c1c6ee8946491b698b067df2258e07918c25da02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/6d6614378cd8128eed0a037ce6ac51a26c5aaed5", - "reference": "6d6614378cd8128eed0a037ce6ac51a26c5aaed5", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c1c6ee8946491b698b067df2258e07918c25da02", + "reference": "c1c6ee8946491b698b067df2258e07918c25da02", "shasum": "" }, "require": { @@ -4386,7 +4389,7 @@ "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", "symfony/security-core": "<6.4", "symfony/security-csrf": "<7.2", - "symfony/serializer": "<7.1", + "symfony/serializer": "<7.2.5", "symfony/stopwatch": "<6.4", "symfony/translation": "<6.4", "symfony/twig-bridge": "<6.4", @@ -4425,7 +4428,7 @@ "symfony/scheduler": "^6.4.4|^7.0.4", "symfony/security-bundle": "^6.4|^7.0", "symfony/semaphore": "^6.4|^7.0", - "symfony/serializer": "^7.1", + "symfony/serializer": "^7.2.5", "symfony/stopwatch": "^6.4|^7.0", "symfony/string": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", @@ -4465,7 +4468,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.2.4" + "source": "https://github.com/symfony/framework-bundle/tree/v7.2.5" }, "funding": [ { @@ -4481,7 +4484,7 @@ "type": "tidelift" } ], - "time": "2025-02-26T08:19:39+00:00" + "time": "2025-03-24T12:37:32+00:00" }, { "name": "symfony/http-client", @@ -4658,16 +4661,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0" + "reference": "6023ec7607254c87c5e69fb3558255aca440d72b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6023ec7607254c87c5e69fb3558255aca440d72b", + "reference": "6023ec7607254c87c5e69fb3558255aca440d72b", "shasum": "" }, "require": { @@ -4716,7 +4719,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.6" }, "funding": [ { @@ -4732,20 +4735,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T10:56:55+00:00" + "time": "2025-04-09T08:14:01+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9f1103734c5789798fefb90e91de4586039003ed" + "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f1103734c5789798fefb90e91de4586039003ed", - "reference": "9f1103734c5789798fefb90e91de4586039003ed", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9dec01e6094a063e738f8945ef69c0cfcf792ec", + "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec", "shasum": "" }, "require": { @@ -4830,7 +4833,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.4" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.6" }, "funding": [ { @@ -4846,20 +4849,20 @@ "type": "tidelift" } ], - "time": "2025-02-26T11:01:22+00:00" + "time": "2025-05-02T09:04:03+00:00" }, { "name": "symfony/intl", - "version": "v7.2.0", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "76bb3462c6c308f8bd97d3c178c2626ae44d4dea" + "reference": "f8a603f978b035d3a1dc23977fc8ae57558177ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/76bb3462c6c308f8bd97d3c178c2626ae44d4dea", - "reference": "76bb3462c6c308f8bd97d3c178c2626ae44d4dea", + "url": "https://api.github.com/repos/symfony/intl/zipball/f8a603f978b035d3a1dc23977fc8ae57558177ad", + "reference": "f8a603f978b035d3a1dc23977fc8ae57558177ad", "shasum": "" }, "require": { @@ -4916,7 +4919,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v7.2.0" + "source": "https://github.com/symfony/intl/tree/v7.2.6" }, "funding": [ { @@ -4932,20 +4935,20 @@ "type": "tidelift" } ], - "time": "2024-11-25T14:26:33+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/mime", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "87ca22046b78c3feaff04b337f33b38510fd686b" + "reference": "706e65c72d402539a072d0d6ad105fff6c161ef1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b", - "reference": "87ca22046b78c3feaff04b337f33b38510fd686b", + "url": "https://api.github.com/repos/symfony/mime/zipball/706e65c72d402539a072d0d6ad105fff6c161ef1", + "reference": "706e65c72d402539a072d0d6ad105fff6c161ef1", "shasum": "" }, "require": { @@ -5000,7 +5003,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.4" + "source": "https://github.com/symfony/mime/tree/v7.2.6" }, "funding": [ { @@ -5016,7 +5019,7 @@ "type": "tidelift" } ], - "time": "2025-02-19T08:51:20+00:00" + "time": "2025-04-27T13:34:41+00:00" }, { "name": "symfony/options-resolver", @@ -5159,7 +5162,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -5217,7 +5220,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -5237,16 +5240,16 @@ }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78" + "reference": "763d2a91fea5681509ca01acbc1c5e450d127811" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", - "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/763d2a91fea5681509ca01acbc1c5e450d127811", + "reference": "763d2a91fea5681509ca01acbc1c5e450d127811", "shasum": "" }, "require": { @@ -5301,7 +5304,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.32.0" }, "funding": [ { @@ -5317,20 +5320,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-21T18:38:29+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -5384,7 +5387,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" }, "funding": [ { @@ -5400,11 +5403,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -5465,7 +5468,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -5485,19 +5488,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -5545,7 +5549,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -5561,11 +5565,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", @@ -5621,7 +5625,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" }, "funding": [ { @@ -5641,16 +5645,16 @@ }, { "name": "symfony/polyfill-php84", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "e5493eb51311ab0b1cc2243416613f06ed8f18bd" + "reference": "000df7860439609837bbe28670b0be15783b7fbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/e5493eb51311ab0b1cc2243416613f06ed8f18bd", - "reference": "e5493eb51311ab0b1cc2243416613f06ed8f18bd", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", + "reference": "000df7860439609837bbe28670b0be15783b7fbf", "shasum": "" }, "require": { @@ -5697,7 +5701,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" }, "funding": [ { @@ -5713,11 +5717,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T12:04:04+00:00" + "time": "2025-02-20T12:04:08+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -5776,7 +5780,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" }, "funding": [ { @@ -5872,16 +5876,16 @@ }, { "name": "symfony/property-info", - "version": "v7.2.3", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "dedb118fd588a92f226b390250b384d25f4192fe" + "reference": "f00fd9685ecdbabe82ca25c7b739ce7bba99302c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/dedb118fd588a92f226b390250b384d25f4192fe", - "reference": "dedb118fd588a92f226b390250b384d25f4192fe", + "url": "https://api.github.com/repos/symfony/property-info/zipball/f00fd9685ecdbabe82ca25c7b739ce7bba99302c", + "reference": "f00fd9685ecdbabe82ca25c7b739ce7bba99302c", "shasum": "" }, "require": { @@ -5937,7 +5941,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.2.3" + "source": "https://github.com/symfony/property-info/tree/v7.2.5" }, "funding": [ { @@ -5953,7 +5957,7 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-03-06T16:27:19+00:00" }, { "name": "symfony/routing", @@ -6223,16 +6227,16 @@ }, { "name": "symfony/security-core", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "466784ffcd0b5a16e05394335897f790b17d07e4" + "reference": "340e120d26b3bf5eee5cea0782aebaa2f36b6722" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/466784ffcd0b5a16e05394335897f790b17d07e4", - "reference": "466784ffcd0b5a16e05394335897f790b17d07e4", + "url": "https://api.github.com/repos/symfony/security-core/zipball/340e120d26b3bf5eee5cea0782aebaa2f36b6722", + "reference": "340e120d26b3bf5eee5cea0782aebaa2f36b6722", "shasum": "" }, "require": { @@ -6290,7 +6294,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.2.3" + "source": "https://github.com/symfony/security-core/tree/v7.2.6" }, "funding": [ { @@ -6306,7 +6310,7 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-04-17T08:47:02+00:00" }, { "name": "symfony/security-csrf", @@ -6380,16 +6384,16 @@ }, { "name": "symfony/security-http", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "8478e95e273f8daa23bf4860dbad2a09d3fb3722" + "reference": "324425deb859c6a59a2c2414ae60f742976a193b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/8478e95e273f8daa23bf4860dbad2a09d3fb3722", - "reference": "8478e95e273f8daa23bf4860dbad2a09d3fb3722", + "url": "https://api.github.com/repos/symfony/security-http/zipball/324425deb859c6a59a2c2414ae60f742976a193b", + "reference": "324425deb859c6a59a2c2414ae60f742976a193b", "shasum": "" }, "require": { @@ -6448,7 +6452,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.2.4" + "source": "https://github.com/symfony/security-http/tree/v7.2.6" }, "funding": [ { @@ -6464,7 +6468,7 @@ "type": "tidelift" } ], - "time": "2025-02-11T16:46:20+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/service-contracts", @@ -6613,16 +6617,16 @@ }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931", "shasum": "" }, "require": { @@ -6680,7 +6684,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v7.2.6" }, "funding": [ { @@ -6696,20 +6700,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-04-20T20:18:16+00:00" }, { "name": "symfony/translation", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "283856e6981286cc0d800b53bd5703e8e363f05a" + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a", - "reference": "283856e6981286cc0d800b53bd5703e8e363f05a", + "url": "https://api.github.com/repos/symfony/translation/zipball/e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", "shasum": "" }, "require": { @@ -6775,7 +6779,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.2.4" + "source": "https://github.com/symfony/translation/tree/v7.2.6" }, "funding": [ { @@ -6791,7 +6795,7 @@ "type": "tidelift" } ], - "time": "2025-02-13T10:27:23+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/translation-contracts", @@ -6873,16 +6877,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "45c00afd4c9accf00a91215067c2858e5a9a3c4e" + "reference": "b1942d5515b7f0a18e16fd668a04ea952db2b0f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/45c00afd4c9accf00a91215067c2858e5a9a3c4e", - "reference": "45c00afd4c9accf00a91215067c2858e5a9a3c4e", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/b1942d5515b7f0a18e16fd668a04ea952db2b0f2", + "reference": "b1942d5515b7f0a18e16fd668a04ea952db2b0f2", "shasum": "" }, "require": { @@ -6914,7 +6918,7 @@ "symfony/emoji": "^7.1", "symfony/expression-language": "^6.4|^7.0", "symfony/finder": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", + "symfony/form": "^6.4.20|^7.2.5", "symfony/html-sanitizer": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -6963,7 +6967,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.2.4" + "source": "https://github.com/symfony/twig-bridge/tree/v7.2.5" }, "funding": [ { @@ -6979,7 +6983,7 @@ "type": "tidelift" } ], - "time": "2025-02-14T14:27:24+00:00" + "time": "2025-03-28T13:15:09+00:00" }, { "name": "symfony/twig-bundle", @@ -7067,16 +7071,16 @@ }, { "name": "symfony/type-info", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "269344575181c326781382ed53f7262feae3c6a4" + "reference": "c4824a6b658294c828e609d3d8dbb4e87f6a375d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/269344575181c326781382ed53f7262feae3c6a4", - "reference": "269344575181c326781382ed53f7262feae3c6a4", + "url": "https://api.github.com/repos/symfony/type-info/zipball/c4824a6b658294c828e609d3d8dbb4e87f6a375d", + "reference": "c4824a6b658294c828e609d3d8dbb4e87f6a375d", "shasum": "" }, "require": { @@ -7122,7 +7126,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.2.4" + "source": "https://github.com/symfony/type-info/tree/v7.2.5" }, "funding": [ { @@ -7138,7 +7142,7 @@ "type": "tidelift" } ], - "time": "2025-02-25T15:19:41+00:00" + "time": "2025-03-24T09:03:36+00:00" }, { "name": "symfony/uid", @@ -7216,16 +7220,16 @@ }, { "name": "symfony/ux-twig-component", - "version": "v2.23.0", + "version": "v2.25.2", "source": { "type": "git", "url": "https://github.com/symfony/ux-twig-component.git", - "reference": "f29033b95e93aea2d498dc40eac185ed14b07800" + "reference": "d20da25517fc09d147897d02819a046f0a0f6735" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/f29033b95e93aea2d498dc40eac185ed14b07800", - "reference": "f29033b95e93aea2d498dc40eac185ed14b07800", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/d20da25517fc09d147897d02819a046f0a0f6735", + "reference": "d20da25517fc09d147897d02819a046f0a0f6735", "shasum": "" }, "require": { @@ -7234,7 +7238,7 @@ "symfony/deprecation-contracts": "^2.2|^3.0", "symfony/event-dispatcher": "^5.4|^6.0|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", - "twig/twig": "^3.8" + "twig/twig": "^3.10.3" }, "conflict": { "symfony/config": "<5.4.0" @@ -7279,7 +7283,7 @@ "twig" ], "support": { - "source": "https://github.com/symfony/ux-twig-component/tree/v2.23.0" + "source": "https://github.com/symfony/ux-twig-component/tree/v2.25.2" }, "funding": [ { @@ -7295,20 +7299,20 @@ "type": "tidelift" } ], - "time": "2025-01-25T02:19:26+00:00" + "time": "2025-05-20T13:06:01+00:00" }, { "name": "symfony/validator", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "00936b34ef29d0e0e9a5340bbce6e7562092da56" + "reference": "f7c32e309885a97fc9572335e22c2c2d31f328c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/00936b34ef29d0e0e9a5340bbce6e7562092da56", - "reference": "00936b34ef29d0e0e9a5340bbce6e7562092da56", + "url": "https://api.github.com/repos/symfony/validator/zipball/f7c32e309885a97fc9572335e22c2c2d31f328c4", + "reference": "f7c32e309885a97fc9572335e22c2c2d31f328c4", "shasum": "" }, "require": { @@ -7376,7 +7380,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.2.4" + "source": "https://github.com/symfony/validator/tree/v7.2.6" }, "funding": [ { @@ -7392,20 +7396,20 @@ "type": "tidelift" } ], - "time": "2025-02-21T09:47:16+00:00" + "time": "2025-05-02T08:36:00+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" + "reference": "9c46038cd4ed68952166cf7001b54eb539184ccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9c46038cd4ed68952166cf7001b54eb539184ccb", + "reference": "9c46038cd4ed68952166cf7001b54eb539184ccb", "shasum": "" }, "require": { @@ -7459,7 +7463,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.6" }, "funding": [ { @@ -7475,20 +7479,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T11:39:41+00:00" + "time": "2025-04-09T08:14:01+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "4ede73aa7a73d81506002d2caadbbdad1ef5b69a" + "reference": "422b8de94c738830a1e071f59ad14d67417d7007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/4ede73aa7a73d81506002d2caadbbdad1ef5b69a", - "reference": "4ede73aa7a73d81506002d2caadbbdad1ef5b69a", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/422b8de94c738830a1e071f59ad14d67417d7007", + "reference": "422b8de94c738830a1e071f59ad14d67417d7007", "shasum": "" }, "require": { @@ -7535,7 +7539,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.2.4" + "source": "https://github.com/symfony/var-exporter/tree/v7.2.6" }, "funding": [ { @@ -7551,20 +7555,20 @@ "type": "tidelift" } ], - "time": "2025-02-13T10:27:23+00:00" + "time": "2025-05-02T08:36:00+00:00" }, { "name": "symfony/yaml", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ac238f173df0c9c1120f862d0f599e17535a87ec" + "reference": "0feafffb843860624ddfd13478f481f4c3cd8b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ac238f173df0c9c1120f862d0f599e17535a87ec", - "reference": "ac238f173df0c9c1120f862d0f599e17535a87ec", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0feafffb843860624ddfd13478f481f4c3cd8b23", + "reference": "0feafffb843860624ddfd13478f481f4c3cd8b23", "shasum": "" }, "require": { @@ -7607,7 +7611,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.2.3" + "source": "https://github.com/symfony/yaml/tree/v7.2.6" }, "funding": [ { @@ -7623,20 +7627,20 @@ "type": "tidelift" } ], - "time": "2025-01-07T12:55:42+00:00" + "time": "2025-04-04T10:10:11+00:00" }, { "name": "twig/extra-bundle", - "version": "v3.20.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "9df5e1dbb6a68c0665ae5603f6f2c20815647876" + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/9df5e1dbb6a68c0665ae5603f6f2c20815647876", - "reference": "9df5e1dbb6a68c0665ae5603f6f2c20815647876", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/62d1cf47a1aa009cbd07b21045b97d3d5cb79896", + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896", "shasum": "" }, "require": { @@ -7685,7 +7689,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.20.0" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.21.0" }, "funding": [ { @@ -7697,20 +7701,20 @@ "type": "tidelift" } ], - "time": "2025-02-08T09:47:15+00:00" + "time": "2025-02-19T14:29:33+00:00" }, { "name": "twig/html-extra", - "version": "v3.20.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "f7d54d4de1b64182af745cfb66777f699b599734" + "reference": "5442dd707601c83b8cd4233e37bb10ab8489a90f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/f7d54d4de1b64182af745cfb66777f699b599734", - "reference": "f7d54d4de1b64182af745cfb66777f699b599734", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/5442dd707601c83b8cd4233e37bb10ab8489a90f", + "reference": "5442dd707601c83b8cd4233e37bb10ab8489a90f", "shasum": "" }, "require": { @@ -7753,7 +7757,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.20.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.21.0" }, "funding": [ { @@ -7765,11 +7769,11 @@ "type": "tidelift" } ], - "time": "2025-01-31T20:45:36+00:00" + "time": "2025-02-19T14:29:33+00:00" }, { "name": "twig/string-extra", - "version": "v3.20.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/string-extra.git", @@ -7820,7 +7824,7 @@ "unicode" ], "support": { - "source": "https://github.com/twigphp/string-extra/tree/v3.20.0" + "source": "https://github.com/twigphp/string-extra/tree/v3.21.0" }, "funding": [ { @@ -7836,16 +7840,16 @@ }, { "name": "twig/twig", - "version": "v3.20.0", + "version": "v3.21.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "3468920399451a384bef53cf7996965f7cd40183" + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183", - "reference": "3468920399451a384bef53cf7996965f7cd40183", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", "shasum": "" }, "require": { @@ -7899,7 +7903,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.20.0" + "source": "https://github.com/twigphp/Twig/tree/v3.21.1" }, "funding": [ { @@ -7911,7 +7915,7 @@ "type": "tidelift" } ], - "time": "2025-02-13T08:34:43+00:00" + "time": "2025-05-03T07:21:55+00:00" } ], "packages-dev": [ @@ -8047,22 +8051,22 @@ }, { "name": "ergebnis/composer-normalize", - "version": "2.45.0", + "version": "2.47.0", "source": { "type": "git", "url": "https://github.com/ergebnis/composer-normalize.git", - "reference": "bb82b484bed2556da6311b9eff779fa7e73ce937" + "reference": "ed24b9f8901f8fbafeca98f662eaca39427f0544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/bb82b484bed2556da6311b9eff779fa7e73ce937", - "reference": "bb82b484bed2556da6311b9eff779fa7e73ce937", + "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/ed24b9f8901f8fbafeca98f662eaca39427f0544", + "reference": "ed24b9f8901f8fbafeca98f662eaca39427f0544", "shasum": "" }, "require": { "composer-plugin-api": "^2.0.0", "ergebnis/json": "^1.4.0", - "ergebnis/json-normalizer": "^4.8.0", + "ergebnis/json-normalizer": "^4.9.0", "ergebnis/json-printer": "^3.7.0", "ext-json": "*", "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", @@ -8072,17 +8076,17 @@ "require-dev": { "composer/composer": "^2.8.3", "ergebnis/license": "^2.6.0", - "ergebnis/php-cs-fixer-config": "^6.39.0", - "ergebnis/phpunit-slow-test-detector": "^2.17.0", + "ergebnis/php-cs-fixer-config": "^6.46.0", + "ergebnis/phpunit-slow-test-detector": "^2.19.1", "fakerphp/faker": "^1.24.1", "infection/infection": "~0.26.6", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.12", - "phpstan/phpstan-deprecation-rules": "^1.2.1", - "phpstan/phpstan-phpunit": "^1.4.1", - "phpstan/phpstan-strict-rules": "^1.6.1", + "phpstan/phpstan": "^2.1.11", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.6", + "phpstan/phpstan-strict-rules": "^2.0.4", "phpunit/phpunit": "^9.6.20", - "rector/rector": "^1.2.10", + "rector/rector": "^2.0.11", "symfony/filesystem": "^5.4.41" }, "type": "composer-plugin", @@ -8126,7 +8130,7 @@ "security": "https://github.com/ergebnis/composer-normalize/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/composer-normalize" }, - "time": "2024-12-04T18:36:37+00:00" + "time": "2025-04-15T11:09:27+00:00" }, { "name": "ergebnis/json", @@ -8198,16 +8202,16 @@ }, { "name": "ergebnis/json-normalizer", - "version": "4.8.0", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/ergebnis/json-normalizer.git", - "reference": "e3a477b62808f377f4fc69a50f9eb66ec102747b" + "reference": "cc4dcf3890448572a2d9bea97133c4d860e59fb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/json-normalizer/zipball/e3a477b62808f377f4fc69a50f9eb66ec102747b", - "reference": "e3a477b62808f377f4fc69a50f9eb66ec102747b", + "url": "https://api.github.com/repos/ergebnis/json-normalizer/zipball/cc4dcf3890448572a2d9bea97133c4d860e59fb1", + "reference": "cc4dcf3890448572a2d9bea97133c4d860e59fb1", "shasum": "" }, "require": { @@ -8276,7 +8280,7 @@ "security": "https://github.com/ergebnis/json-normalizer/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/json-normalizer" }, - "time": "2024-12-04T16:48:55+00:00" + "time": "2025-04-10T13:13:04+00:00" }, { "name": "ergebnis/json-pointer", @@ -8603,16 +8607,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.73.1", + "version": "v3.75.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "ffcb8200a42045e65049af7910cfd022f631b064" + "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/ffcb8200a42045e65049af7910cfd022f631b064", - "reference": "ffcb8200a42045e65049af7910cfd022f631b064", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c", + "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c", "shasum": "" }, "require": { @@ -8695,7 +8699,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.73.1" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.75.0" }, "funding": [ { @@ -8703,20 +8707,20 @@ "type": "github" } ], - "time": "2025-03-19T23:42:16+00:00" + "time": "2025-03-31T18:40:42+00:00" }, { "name": "justinrainbow/json-schema", - "version": "6.3.1", + "version": "6.4.1", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "c9f00dec766a67bf82c277b71d71d254357db92c" + "reference": "35d262c94959571e8736db1e5c9bc36ab94ae900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/c9f00dec766a67bf82c277b71d71d254357db92c", - "reference": "c9f00dec766a67bf82c277b71d71d254357db92c", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/35d262c94959571e8736db1e5c9bc36ab94ae900", + "reference": "35d262c94959571e8736db1e5c9bc36ab94ae900", "shasum": "" }, "require": { @@ -8776,9 +8780,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.3.1" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.1" }, - "time": "2025-03-18T19:03:56+00:00" + "time": "2025-04-04T13:08:07+00:00" }, { "name": "localheinz/diff", @@ -9016,16 +9020,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.11", + "version": "2.1.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" + "reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9", + "reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9", "shasum": "" }, "require": { @@ -9070,25 +9074,25 @@ "type": "github" } ], - "time": "2025-03-24T13:45:00+00:00" + "time": "2025-05-16T09:40:10+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "a61a04a361b60014ec04881ccb87252d3bf02e94" + "reference": "4497663eb17b9d29211830df5aceaa3a4d256a35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/a61a04a361b60014ec04881ccb87252d3bf02e94", - "reference": "a61a04a361b60014ec04881ccb87252d3bf02e94", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/4497663eb17b9d29211830df5aceaa3a4d256a35", + "reference": "4497663eb17b9d29211830df5aceaa3a4d256a35", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.3" + "phpstan/phpstan": "^2.1.13" }, "conflict": { "doctrine/collections": "<1.0", @@ -9112,6 +9116,7 @@ "gedmo/doctrine-extensions": "^3.8", "nesbot/carbon": "^2.49", "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0.2", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6.20", @@ -9139,9 +9144,9 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.2" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.3" }, - "time": "2025-03-03T09:29:16+00:00" + "time": "2025-05-05T15:28:52+00:00" }, { "name": "react/cache", @@ -9671,21 +9676,21 @@ }, { "name": "rector/rector", - "version": "2.0.10", + "version": "2.0.16", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "5844a718acb40f40afcd110394270afa55509fd0" + "reference": "f1366d1f8c7490541c8f7af6e5c7cef7cca1b5a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/5844a718acb40f40afcd110394270afa55509fd0", - "reference": "5844a718acb40f40afcd110394270afa55509fd0", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/f1366d1f8c7490541c8f7af6e5c7cef7cca1b5a2", + "reference": "f1366d1f8c7490541c8f7af6e5c7cef7cca1b5a2", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.6" + "phpstan/phpstan": "^2.1.14" }, "conflict": { "rector/rector-doctrine": "*", @@ -9718,7 +9723,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.0.10" + "source": "https://github.com/rectorphp/rector/tree/2.0.16" }, "funding": [ { @@ -9726,7 +9731,7 @@ "type": "github" } ], - "time": "2025-03-03T17:35:18+00:00" + "time": "2025-05-12T16:37:16+00:00" }, { "name": "sebastian/diff", @@ -9871,21 +9876,21 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.62.1", + "version": "v1.63.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "468ff2708200c95ebc0d85d3174b6c6711b8a590" + "reference": "69478ab39bc303abfbe3293006a78b09a8512425" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/468ff2708200c95ebc0d85d3174b6c6711b8a590", - "reference": "468ff2708200c95ebc0d85d3174b6c6711b8a590", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/69478ab39bc303abfbe3293006a78b09a8512425", + "reference": "69478ab39bc303abfbe3293006a78b09a8512425", "shasum": "" }, "require": { "doctrine/inflector": "^2.0", - "nikic/php-parser": "^4.18|^5.0", + "nikic/php-parser": "^5.0", "php": ">=8.1", "symfony/config": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", @@ -9943,7 +9948,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.62.1" + "source": "https://github.com/symfony/maker-bundle/tree/v1.63.0" }, "funding": [ { @@ -9959,20 +9964,20 @@ "type": "tidelift" } ], - "time": "2025-01-15T00:21:40+00:00" + "time": "2025-04-26T01:41:37+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.2.0", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "2bbde92ab25a0e2c88160857af7be9db5da0d145" + "reference": "6106ae85a0e3ed509d339b7f924788c9cc4e7cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/2bbde92ab25a0e2c88160857af7be9db5da0d145", - "reference": "2bbde92ab25a0e2c88160857af7be9db5da0d145", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/6106ae85a0e3ed509d339b7f924788c9cc4e7cfb", + "reference": "6106ae85a0e3ed509d339b7f924788c9cc4e7cfb", "shasum": "" }, "require": { @@ -10025,7 +10030,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.2.0" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.2.6" }, "funding": [ { @@ -10041,20 +10046,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T16:15:23+00:00" + "time": "2025-04-09T08:35:42+00:00" }, { "name": "symfony/process", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", "shasum": "" }, "require": { @@ -10086,7 +10091,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.4" + "source": "https://github.com/symfony/process/tree/v7.2.5" }, "funding": [ { @@ -10102,7 +10107,7 @@ "type": "tidelift" } ], - "time": "2025-02-05T08:33:46+00:00" + "time": "2025-03-13T12:21:46+00:00" }, { "name": "symfony/web-profiler-bundle", @@ -10188,16 +10193,16 @@ }, { "name": "vincentlanglet/twig-cs-fixer", - "version": "3.5.1", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/VincentLanglet/Twig-CS-Fixer.git", - "reference": "0ab7a8154f7b3a6a42cbe3a467074a47bc32dcf5" + "reference": "d216db67b63d78cfdefca6a9a7acef4fd0d304bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/VincentLanglet/Twig-CS-Fixer/zipball/0ab7a8154f7b3a6a42cbe3a467074a47bc32dcf5", - "reference": "0ab7a8154f7b3a6a42cbe3a467074a47bc32dcf5", + "url": "https://api.github.com/repos/VincentLanglet/Twig-CS-Fixer/zipball/d216db67b63d78cfdefca6a9a7acef4fd0d304bb", + "reference": "d216db67b63d78cfdefca6a9a7acef4fd0d304bb", "shasum": "" }, "require": { @@ -10217,13 +10222,13 @@ "dereuromark/composer-prefer-lowest": "^0.1.10", "ergebnis/composer-normalize": "^2.29", "friendsofphp/php-cs-fixer": "^3.13.0", - "infection/infection": "^0.26.16 || ^0.27.0", + "infection/infection": "^0.26.16 || ^0.29.14", "phpstan/phpstan": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpstan/phpstan-symfony": "^2.0", "phpstan/phpstan-webmozart-assert": "^2.0", - "phpunit/phpunit": "^9.5.26 || ^10.0.9", + "phpunit/phpunit": "^9.5.26 || ^11.5.18 || ^12.1.3", "rector/rector": "^2.0.0", "shipmonk/composer-dependency-analyser": "^1.6", "symfony/process": "^5.4 || ^6.4 || ^7.0", @@ -10253,7 +10258,7 @@ "homepage": "https://github.com/VincentLanglet/Twig-CS-Fixer", "support": { "issues": "https://github.com/VincentLanglet/Twig-CS-Fixer/issues", - "source": "https://github.com/VincentLanglet/Twig-CS-Fixer/tree/3.5.1" + "source": "https://github.com/VincentLanglet/Twig-CS-Fixer/tree/3.7.1" }, "funding": [ { @@ -10261,7 +10266,7 @@ "type": "github" } ], - "time": "2025-01-16T18:36:36+00:00" + "time": "2025-05-19T12:24:50+00:00" }, { "name": "webmozart/assert", diff --git a/config/packages/cache.yaml b/config/packages/cache.yaml index 6899b72..c3eb53d 100644 --- a/config/packages/cache.yaml +++ b/config/packages/cache.yaml @@ -16,4 +16,4 @@ framework: # Namespaced pools use the above "app" backend by default #pools: - #my.dedicated.cache: null + #my.dedicated.cache: null diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index b085d59..120bd6a 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -1,7 +1,7 @@ doctrine: dbal: - url: '%env(resolve:DATABASE_URL)%' - server_version: '5.7' + url: "%env(resolve:DATABASE_URL)%" + server_version: "5.7" charset: utf8mb4 default_table_options: charset: utf8mb4 @@ -20,25 +20,29 @@ doctrine: mappings: App: is_bundle: false - dir: '%kernel.project_dir%/src/Entity' + dir: "%kernel.project_dir%/src/Entity" prefix: 'App\Entity' alias: App GedmoLoggable: prefix: Gedmo\Loggable\Entity dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Entity" is_bundle: false + filters: + entity_active: + class: App\Doctrine\EntityActiveFilter + enabled: true when@test: doctrine: dbal: # "TEST_TOKEN" is typically set by ParaTest - dbname_suffix: '_test%env(default::TEST_TOKEN)%' + dbname_suffix: "_test%env(default::TEST_TOKEN)%" when@prod: doctrine: orm: auto_generate_proxy_classes: false - proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' + proxy_dir: "%kernel.build_dir%/doctrine/orm/Proxies" query_cache_driver: type: pool pool: doctrine.system_cache_pool diff --git a/config/packages/doctrine_migrations.yaml b/config/packages/doctrine_migrations.yaml index 83222a8..73072ff 100644 --- a/config/packages/doctrine_migrations.yaml +++ b/config/packages/doctrine_migrations.yaml @@ -2,7 +2,5 @@ doctrine_migrations: migrations_paths: # namespace is arbitrary but should be different from App\Migrations # as migrations classes should NOT be autoloaded - 'DoctrineMigrations': '%kernel.project_dir%/migrations' + "DoctrineMigrations": "%kernel.project_dir%/migrations" enable_profiler: false - - diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 7c18397..414eabf 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -1,12 +1,12 @@ # see https://symfony.com/doc/current/reference/configuration/framework.html framework: - secret: '%env(APP_SECRET)%' + secret: "%env(APP_SECRET)%" #csrf_protection: true http_method_override: false handle_all_throwables: true # https://symfony.com/doc/current/deployment/proxies.html - trusted_proxies: '127.0.0.1,REMOTE_ADDR' + trusted_proxies: "127.0.0.1,REMOTE_ADDR" # Enables session support. Note that the session will ONLY be started if you read or write from it. # Remove or comment this section to explicitly disable session support. diff --git a/config/packages/knp_paginator.yaml b/config/packages/knp_paginator.yaml new file mode 100644 index 0000000..49d10eb --- /dev/null +++ b/config/packages/knp_paginator.yaml @@ -0,0 +1,4 @@ +# https://github.com/KnpLabs/KnpPaginatorBundle?tab=readme-ov-file#configuration-example +knp_paginator: + template: + pagination: "@KnpPaginator/Pagination/bootstrap_v5_pagination.html.twig" diff --git a/config/packages/security.yaml b/config/packages/security.yaml index dab1fc5..9d0999b 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,7 +1,7 @@ security: # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: - Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: "auto" # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: app_user_provider: @@ -19,7 +19,6 @@ security: check_path: app_login lazy: true provider: app_user_provider -# custom_authenticator: App\Security\AppCustomAuthenticator logout: path: admin_logout @@ -32,6 +31,7 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: + - { path: ^/dashboard, roles: ROLE_USER } - { path: ^/admin, roles: ROLE_USER } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } diff --git a/config/packages/stof_doctrine_extensions.yaml b/config/packages/stof_doctrine_extensions.yaml index 1392715..77d7207 100644 --- a/config/packages/stof_doctrine_extensions.yaml +++ b/config/packages/stof_doctrine_extensions.yaml @@ -6,4 +6,4 @@ stof_doctrine_extensions: default: timestampable: true blameable: true - loggable: true \ No newline at end of file + loggable: true diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index cbb3482..e298710 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -1,7 +1,7 @@ framework: default_locale: da translator: - default_path: '%kernel.project_dir%/translations' + default_path: "%kernel.project_dir%/translations" fallbacks: - en providers: diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 2af5679..8aa3d07 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -1,5 +1,5 @@ twig: - default_path: '%kernel.project_dir%/templates' + default_path: "%kernel.project_dir%/templates" when@dev: twig: diff --git a/config/packages/twig_component.yaml b/config/packages/twig_component.yaml index fd17ac6..8af2520 100644 --- a/config/packages/twig_component.yaml +++ b/config/packages/twig_component.yaml @@ -1,5 +1,5 @@ twig_component: - anonymous_template_directory: 'components/' + anonymous_template_directory: "components/" defaults: # Namespace & directory for components - App\Twig\Components\: 'components/' + App\Twig\Components\: "components/" diff --git a/config/routes.yaml b/config/routes.yaml index 2d0cc70..41ef814 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -3,5 +3,3 @@ controllers: path: ../src/Controller/ namespace: App\Controller type: attribute - - diff --git a/config/routes/framework.yaml b/config/routes/framework.yaml index 0fc74bb..d53eff1 100644 --- a/config/routes/framework.yaml +++ b/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: "@FrameworkBundle/Resources/config/routing/errors.xml" prefix: /_error diff --git a/config/routes/web_profiler.yaml b/config/routes/web_profiler.yaml index 8d85319..1bf88e5 100644 --- a/config/routes/web_profiler.yaml +++ b/config/routes/web_profiler.yaml @@ -1,8 +1,8 @@ when@dev: web_profiler_wdt: - resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' + resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml" prefix: /_wdt web_profiler_profiler: - resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' + resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml" prefix: /_profiler diff --git a/config/services.yaml b/config/services.yaml index 5327f23..358224c 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -8,21 +8,20 @@ parameters: services: # default configuration for services in *this* file _defaults: - autowire: true # Automatically injects dependencies in your services. + autowire: true # Automatically injects dependencies in your services. autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name App\: - resource: '../src/' + resource: "../src/" exclude: - - '../src/DependencyInjection/' - - '../src/Entity/' - - '../src/Kernel.php' - + - "../src/DependencyInjection/" + - "../src/Entity/" + - "../src/Kernel.php" # add more service definitions when explicit configuration is needed App\Service\DataExporter: arguments: - $basePath: '%kernel.project_dir%' + $basePath: "%kernel.project_dir%" # please note that last definitions always *replace* previous ones diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 907ba44..c772f10 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -10,3 +10,14 @@ services: profiles: [dev] volumes: - .:/md + + prettier: + # Prettier does not (yet, fcf. + # https://github.com/prettier/prettier/issues/15206) have an official + # docker image. + # https://hub.docker.com/r/jauderho/prettier is good candidate (cf. https://hub.docker.com/search?q=prettier&sort=updated_at&order=desc) + image: jauderho/prettier + profiles: + - dev + volumes: + - ./:/work diff --git a/migrations/Version20250519132433.php b/migrations/Version20250519132433.php new file mode 100644 index 0000000..a269c1d --- /dev/null +++ b/migrations/Version20250519132433.php @@ -0,0 +1,35 @@ +addSql(<<<'SQL' + ALTER TABLE theme_category CHANGE theme_id theme_id INT DEFAULT NULL + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE theme_category CHANGE theme_id theme_id INT NOT NULL + SQL); + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3943116..7d82f71 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,37 +1,41 @@ parameters: - ignoreErrors: - - - message: '#^Call to function is_null\(\) with Doctrine\\Common\\Collections\\Collection\ will always evaluate to false\.$#' - identifier: function.impossibleType - count: 2 - path: src/Command/AssignGroupsCommand.php + ignoreErrors: + - message: '#^Call to function is_null\(\) with Doctrine\\Common\\Collections\\Collection\ will always evaluate to false\.$#' + identifier: function.impossibleType + count: 2 + path: src/Command/AssignGroupsCommand.php - - - message: '#^Access to an undefined property App\\Controller\\Admin\\CustomDashboardCrudController\:\:\$entity\.$#' - identifier: property.notFound - count: 1 - path: src/Controller/Admin/CustomDashboardCrudController.php + - message: '#^Access to an undefined property App\\Controller\\Admin\\CustomDashboardCrudController\:\:\$entity\.$#' + identifier: property.notFound + count: 1 + path: src/Controller/Admin/CustomDashboardCrudController.php - - - message: '#^Call to an undefined static method App\\Controller\\Admin\\AbstractSystatusDashboardController\:\:showAction\(\)\.$#' - identifier: staticMethod.notFound - count: 1 - path: src/Controller/Admin/CustomDashboardCrudController.php + - message: '#^Call to an undefined static method App\\Controller\\Admin\\AbstractSystatusDashboardController\:\:showAction\(\)\.$#' + identifier: staticMethod.notFound + count: 1 + path: src/Controller/Admin/CustomDashboardCrudController.php - - - message: '#^Unable to resolve the template type T in call to method Doctrine\\ORM\\EntityManagerInterface\:\:getRepository\(\)$#' - identifier: argument.templateType - count: 1 - path: src/Controller/Admin/CustomDashboardCrudController.php + - message: '#^Unable to resolve the template type T in call to method Doctrine\\ORM\\EntityManagerInterface\:\:getRepository\(\)$#' + identifier: argument.templateType + count: 3 + path: src/Controller/Admin/CustomDashboardCrudController.php - - - message: '#^Call to an undefined method Traversable\\:\:uasort\(\)\.$#' - identifier: method.notFound - count: 1 - path: src/Entity/Theme.php + - message: '#^Unable to resolve the template type T in call to method Doctrine\\ORM\\EntityManagerInterface\:\:getRepository\(\)$#' + identifier: argument.templateType + count: 1 + path: src/Controller/Admin/EntityFilterTrait.php - - - message: '#^Negated boolean expression is always true\.$#' - identifier: booleanNot.alwaysTrue - count: 1 - path: src/Service/DataExporter.php + - message: '#^Call to an undefined method Traversable\\:\:uasort\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Entity/Theme.php + + - message: '#^Negated boolean expression is always true\.$#' + identifier: booleanNot.alwaysTrue + count: 1 + path: src/Service/DataExporter.php + + - message: '#^Call to function method_exists\(\) with ''Symfony\\\\Component\\\\Dotenv\\\\Dotenv'' and ''bootEnv'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: tests/bootstrap.php diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 6b03710..f14bf57 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -1,5 +1,5 @@ includes: - - phpstan-baseline.neon + - phpstan-baseline.neon parameters: level: 6 diff --git a/src/Controller/Admin/AbstractFilterableCrudController.php b/src/Controller/Admin/AbstractFilterableCrudController.php new file mode 100644 index 0000000..d0508bf --- /dev/null +++ b/src/Controller/Admin/AbstractFilterableCrudController.php @@ -0,0 +1,69 @@ +setSearchFields(null) + ->overrideTemplate('crud/index', 'admin/crud/index.html.twig'); + } + + #[\Override] + public function configureResponseParameters(KeyValueStore $responseParameters): KeyValueStore + { + $responseParameters = parent::configureResponseParameters($responseParameters); + if ($pageName = $responseParameters->get('pageName')) { + if (Crud::PAGE_INDEX === $pageName) { + // Add our custom filters on the index page. + $filterFormBuilder = $this->createFilterFormBuilder(); + $this->buildCustomFilters( + $this->getContext()->getRequest(), + $this->getContext()->getEntity()->getFqcn(), + $filterFormBuilder + ); + $responseParameters->set('custom_filters', $filterFormBuilder->getForm()->createView()); + } + } + + return $responseParameters; + } + + #[\Override] + public function createIndexQueryBuilder( + SearchDto $searchDto, + EntityDto $entityDto, + FieldCollection $fields, + FilterCollection $filters, + ): QueryBuilder { + $builder = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters); + $filterParameters = $this->getFilterParameters($searchDto->getRequest()); + + $this->applyFilters($builder, $filterParameters, $entityDto->getFqcn()); + + return $builder; + } +} diff --git a/src/Controller/Admin/AbstractSystatusDashboardController.php b/src/Controller/Admin/AbstractSystatusDashboardController.php index 1958f04..afa47f8 100644 --- a/src/Controller/Admin/AbstractSystatusDashboardController.php +++ b/src/Controller/Admin/AbstractSystatusDashboardController.php @@ -30,6 +30,7 @@ public function configureCrud(): Crud { return Crud::new() ->overrideTemplate('label/null', 'easy_admin_overrides/label_null.html.twig') + ->showEntityActionsInlined() ; } @@ -37,23 +38,23 @@ public function configureCrud(): Crud public function configureMenuItems(): iterable { return [ - MenuItem::section('Dashboard '), + MenuItem::section('menu.dashboards'), MenuItem::linkToUrl('menu.dashboard.reports', 'fas fa-file-alt', $this->generateUrl('dashboard', ['entityType' => 'report'])), MenuItem::linkToUrl('menu.dashboard.systems', 'fas fa-cogs', $this->generateUrl('dashboard', ['entityType' => 'system'])), - MenuItem::section('Sysstatus'), + MenuItem::section('menu.sysstatus'), MenuItem::linkToCrud('menu.list.reports', 'fas fa-list', Report::class), MenuItem::linkToCrud('menu.list.systems', 'fas fa-cog', System::class), - MenuItem::section('Konfiguration'), + MenuItem::section('menu.configuration'), MenuItem::linkToCrud('menu.themes', 'fas fa-th-large', Theme::class), MenuItem::linkToCrud('menu.categories', 'fas fa-list', Category::class), - MenuItem::section('Administration'), - MenuItem::linkToCrud('User', 'fas fa-user', User::class), - MenuItem::linkToCrud('Group', 'fas fa-users', UserGroup::class), - MenuItem::linkToCrud('Import', 'fas fa-file-excel', ImportRun::class), - MenuItem::linkToRoute('Eksport', 'fas fa-file-import', 'export_page'), + MenuItem::section('menu.administration'), + MenuItem::linkToCrud('menu.users', 'fas fa-user', User::class), + MenuItem::linkToCrud('menu.groups', 'fas fa-users', UserGroup::class), + MenuItem::linkToRoute('menu.exports', 'fas fa-file-import', 'export_page'), + MenuItem::linkToCrud('menu.import_runs', 'fas fa-file-excel', ImportRun::class), ]; } } diff --git a/src/Controller/Admin/CategoryCrudController.php b/src/Controller/Admin/CategoryCrudController.php index 2318287..0bc37c4 100644 --- a/src/Controller/Admin/CategoryCrudController.php +++ b/src/Controller/Admin/CategoryCrudController.php @@ -4,13 +4,12 @@ use App\Entity\Category; use App\Form\QuestionType; +use App\Form\ThemeCategoryType; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; -use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; -use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\TimeField; @@ -22,13 +21,18 @@ public static function getEntityFqcn(): string } #[\Override] - public function configureActions(Actions $actions): Actions + public function configureCrud(Crud $crud): Crud { - $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ; + return parent::configureCrud($crud) + // https://symfony.com/bundles/EasyAdminBundle/current/design.html#form-field-templates + ->addFormTheme('admin/form.html.twig'); + } - return parent::configureActions($actions); // TODO: Change the autogenerated stub + #[\Override] + public function configureActions(Actions $actions): Actions + { + return parent::configureActions($actions) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } /** @@ -37,26 +41,21 @@ public function configureActions(Actions $actions): Actions #[\Override] public function configureFields(string $pageName): iterable { - $id = IdField::new('id'); - $name = TextField::new('name'); - $created_by = TextField::new('created_by'); - $updated_by = TextField::new('updated_by'); - $number_of_questions = AssociationField::new('questions'); - $coll_question = CollectionField::new('questions')->setEntryType(QuestionType::class); - - $time_created_at = TimeField::new('created_at'); - $time_updated_at = TimeField::new('time_updated_at'); - - if (Crud::PAGE_INDEX === $pageName) { - return [$id, $name, $number_of_questions]; - } elseif (Crud::PAGE_DETAIL === $pageName) { - return [$id, $name, $coll_question, $created_by, $updated_by, $time_created_at, $time_updated_at]; - } elseif (Crud::PAGE_NEW === $pageName) { - return [$name, $coll_question]; - } elseif (Crud::PAGE_EDIT === $pageName) { - return [$name, $coll_question]; - } else { - throw new \Exception('Invalid page: '.$pageName); - } + yield TextField::new('name')->setLabel('entity.category.name'); + yield TextField::new('createdBy') + ->onlyOnDetail(); + yield TextField::new('updatedBy') + ->onlyOnDetail(); + yield TimeField::new('createdAt') + ->onlyOnDetail(); + yield TimeField::new('updatedAt') + ->onlyOnDetail(); + // See templates/admin/form.html.twig for details on how we show this as a table. + yield CollectionField::new('questions')->setLabel('entity.category.questions') + ->setTemplatePath(Crud::PAGE_DETAIL === $pageName ? 'admin/collection_plain.html.twig' : 'admin/collection.html.twig') + ->setEntryType(QuestionType::class) + ->renderExpanded(); + yield CollectionField::new('themeCategories')->setEntryType(ThemeCategoryType::class) + ->onlyOnDetail(); } } diff --git a/src/Controller/Admin/CustomDashboardCrudController.php b/src/Controller/Admin/CustomDashboardCrudController.php index 03a77b3..7a98930 100644 --- a/src/Controller/Admin/CustomDashboardCrudController.php +++ b/src/Controller/Admin/CustomDashboardCrudController.php @@ -3,20 +3,12 @@ namespace App\Controller\Admin; use App\Entity\Report; -use App\Entity\SelfServiceAvailableFromItem; use App\Entity\System; use App\Entity\UserGroup; -use App\Repository\ReportRepository; -use App\Repository\SystemRepository; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\EntityRepository; use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard; use Knp\Component\Pager\PaginatorInterface; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; -use Symfony\Component\Form\Extension\Core\Type\TextType; -use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -26,6 +18,8 @@ #[AdminDashboard(routePath: '/admin', routeName: 'admin')] class CustomDashboardCrudController extends AbstractSystatusDashboardController { + use EntityFilterTrait; + /** * AdminController constructor. */ @@ -54,47 +48,15 @@ public function dashboard(Request $request, string $entityType): Response { $queryParameters = $request->query; - // Fetch all query parameters as an array. - $queryParams = $request->query->all(); - - // Safely retrieve 'form' as an array. - $formData = $queryParams['form'] ?? []; - - // Ensure `formData` is always an array. - if (!is_array($formData)) { - $formData = []; - } - - $formParameters = array_merge( - [ - 'groups' => [], - 'subowner' => '', - 'theme' => '', - 'category' => '', - 'self_service' => '', - 'search' => '', - ], - $formData - ); - + $filterParameters = $this->getFilterParameters($request); $userGroups = $this->entityManager ->getRepository(UserGroup::class) ->findAll(); - $userGroupsThemesAndCategories = $this->getUserGroupsThemesAndCategories( - $userGroups ?: [], - $entityType - ); // Get a query for the entity type. $repository = $this->getRepository($entityType); $query = $repository->createQueryBuilder('e'); - $query = $this->applyFilters($query, $formParameters, $entityType); - - // Get sub owners if a group has been selected. - $subOwnerOptions = $this->getSubOwnerOptions( - $repository, - $formParameters['groups'] - ); + $query = $this->applyFilters($query, $filterParameters, $this->getEntityClassName($entityType)); $paginator = $this->paginator->paginate( $query, @@ -105,11 +67,11 @@ public function dashboard(Request $request, string $entityType): Response $availableCategories = []; $themes = []; - if (!empty($formParameters['groups'])) { + if (!empty($filterParameters['groups'])) { $groups = $this->entityManager ->getRepository(UserGroup::class) ->findBy([ - 'id' => $formParameters['groups'], + 'id' => $filterParameters['groups'], ]); } else { $groups = $userGroups; @@ -122,8 +84,8 @@ public function dashboard(Request $request, string $entityType): Response : $group->getSystemThemes(); foreach ($groupThemes as $theme) { - if ('' != $formParameters['theme']) { - if ($theme->getId() != $formParameters['theme']) { + if ('' != $filterParameters['theme']) { + if ($theme->getId() != $filterParameters['theme']) { continue; } } @@ -132,10 +94,10 @@ public function dashboard(Request $request, string $entityType): Response $themes[$theme->getId()] = $theme; foreach ($theme->getOrderedCategories() as $category) { - if ('' != $formParameters['category']) { + if ('' != $filterParameters['category']) { if ( $category->getId() != - $formParameters['category'] + $filterParameters['category'] ) { continue; } @@ -147,166 +109,49 @@ public function dashboard(Request $request, string $entityType): Response } } - $selfServiceOptions = $this->getSelfServiceOptions($entityType); - - $filterFormBuilder = $this->getFilterFormBuilder( - $userGroupsThemesAndCategories, - $formParameters, - $subOwnerOptions, - true, - true, - $selfServiceOptions - ); - $filterFormBuilder - ->setMethod(Request::METHOD_GET) + $filterFormBuilder = $this->createFilterFormBuilder() ->setAction( $this->generateUrl('dashboard', ['entityType' => $entityType]) ); + $this->buildCustomFilters( + $request, + $this->getEntityClassName($entityType), + $filterFormBuilder, + filterThemes: true, + filterCategories: true + ); + return $this->render('dashboard.html.twig', [ 'paginator' => $paginator, 'categories' => $availableCategories, - 'filters' => $filterFormBuilder->getForm()->createView(), + 'custom_filters' => $filterFormBuilder->getForm()->createView(), 'entityType' => $entityType, ]); } /** - * @param array $userGroups - * @param string $entityType - * - * @return mixed + * Get repository for entity type. * - * @throws \Exception + * @return EntityRepository */ - private function getUserGroupsThemesAndCategories( - array $userGroups, + private function getRepository( string $entityType, - ): mixed { - return array_reduce( - $userGroups, - function ($carry, UserGroup $group) use ($entityType) { - $carry['groups'][$group->getId()] = $group->getName(); - - $groupThemes = - 'report' == $entityType - ? $group->getReportThemes() - : $group->getSystemThemes(); - - foreach ($groupThemes as $theme) { - $carry['themes'][$theme->getId()] = $theme->getName(); - - foreach ($theme->getOrderedCategories() as $category) { - $carry['categories'][ - $category->getId() - ] = $category->getName(); - } - } + ): EntityRepository { + $className = $this->getEntityClassName($entityType); - return $carry; - }, - [ - 'groups' => [], - 'themes' => [], - 'categories' => [], - ] - ); + return $this->entityManager->getRepository($className); } - /** - * Get repository for entity type. - * - * @param string $entityType - * - * @return ReportRepository|SystemRepository|null - */ - private function getRepository( - string $entityType, - ): ReportRepository|SystemRepository|null { + private function getEntityClassName(string $entityType): ?string + { return match ($entityType) { - 'system' => $this->entityManager->getRepository(System::class), - 'report' => $this->entityManager->getRepository(Report::class), + 'system' => System::class, + 'report' => Report::class, default => null, }; } - /** - * Apply common filters for report and system query. - * - * @param QueryBuilder $query - * @param array $formParameters - * @param string|null $entityType - * - * @return QueryBuilder - */ - private function applyFilters( - QueryBuilder $query, - array $formParameters, - ?string $entityType = null, - ): QueryBuilder { - $query->andWhere('e.archivedAt IS NULL'); - - // Filter inactives out. - if ('report' == $entityType) { - $query->andWhere('e.sysStatus = \'Aktiv\''); - } elseif ('system' == $entityType) { - $query->andWhere('e.sysStatus <> \'Systemet bruges ikke længere\''); - } - - // Get the groups the user can search in. - if (!empty($formParameters['groups'])) { - $groups = $this->entityManager - ->getRepository(UserGroup::class) - ->findBy([ - 'id' => $formParameters['groups'], - ]); - - foreach ($groups as $group) { - $query->andWhere( - $query - ->expr() - ->isMemberOf(':group'.$group->getId(), 'e.groups') - ); - $query->setParameter(':group'.$group->getId(), $group); - } - } - - if ( - isset($formParameters['self_service']) - && '' != $formParameters['self_service'] - ) { - $item = $this->entityManager - ->getRepository(SelfServiceAvailableFromItem::class) - ->findOneBy([ - 'id' => $formParameters['self_service'], - ]); - if (null != $item) { - $query->andWhere( - ':self_service MEMBER OF e.selfServiceAvailableFromItems' - ); - $query->setParameter('self_service', $item); - } - } - - if ( - isset($formParameters['search']) - && '' != $formParameters['search'] - ) { - $query->andWhere('e.name LIKE :name'); - $query->setParameter('name', '%'.$formParameters['search'].'%'); - } - - if ( - isset($formParameters['subowner']) - && '' != $formParameters['subowner'] - ) { - $query->andWhere('e.sysOwnerSub = :subowner'); - $query->setParameter('subowner', $formParameters['subowner']); - } - - return $query; - } - /** * Get sub-owners for selected group. */ @@ -324,18 +169,6 @@ private function getSubOwnerOptions( $subOwnersQueryBuilder->select('DISTINCT e.sysOwnerSub'); $subOwnersQueryBuilder->andWhere('e.sysOwnerSub IS NOT NULL'); - // Filter inactives out. - $subOwnersQueryBuilder->andWhere('e.archivedAt IS NULL'); - - $class = $repository->getClassName(); - if (Report::class === $class) { - $subOwnersQueryBuilder->andWhere('e.sysStatus = \'Aktiv\''); - } elseif (System::class === $class) { - $subOwnersQueryBuilder->andWhere( - 'e.sysStatus <> \'Systemet bruges ikke længere\'' - ); - } - foreach ($groups as $group) { $subOwnersQueryBuilder->andWhere( $subOwnersQueryBuilder @@ -361,139 +194,6 @@ function ($carry, $item) { ); } - /** - * Get options for self-service filter. - * - * @param string $entityType - * - * @return array - */ - private function getSelfServiceOptions(string $entityType): array - { - $selfServiceOptions = []; - if ('system' == $entityType) { - /* @var Collection $selfServiceAvailableFromItems */ - $selfServiceAvailableFromItems = $this->entityManager - ->getRepository(SelfServiceAvailableFromItem::class) - ->findAll(); - /* @var SelfServiceAvailableFromItem $item */ - foreach ($selfServiceAvailableFromItems as $item) { - $selfServiceOptions[$item->getName()] = $item->getId(); - } - } - - return $selfServiceOptions; - } - - /** - * Get filter form builder. - * - * @param mixed $userGroupsThemesAndCategories - * @param array $formParameters - * @param mixed $subownerOptions - * @param bool $filterThemes - * @param bool $filterCategories - * @param array $filterSelfServiceOptions - * - * @return FormBuilderInterface - */ - private function getFilterFormBuilder( - mixed $userGroupsThemesAndCategories, - array $formParameters, - mixed $subownerOptions, - bool $filterThemes = false, - bool $filterCategories = false, - array $filterSelfServiceOptions = [], - ): FormBuilderInterface { - $filterFormBuilder = $this->createFormBuilder(options: ['csrf_protection' => false]); - $filterFormBuilder->add('groups', ChoiceType::class, [ - 'label' => 'filter.groups', - 'placeholder' => 'filter.placeholder.groups', - 'choices' => array_flip($userGroupsThemesAndCategories['groups']), - 'multiple' => true, - 'attr' => [ - 'class' => 'form-control', - // @todo Find documentation reference for why setting data-ea-widget actually works - // (https://github.com/search?q=repo%3AEasyCorp%2FEasyAdminBundle%20data-ea-widget&type=code) - 'data-ea-widget' => 'ea-autocomplete', - 'data-placeholder' => $this->translator->trans( - 'filter.placeholder.groups' - ), - ], - 'required' => false, - 'data' => $formParameters['groups'] ?? null, - ]); - $filterFormBuilder->add('subowner', ChoiceType::class, [ - 'label' => 'filter.subowner', - 'placeholder' => 'filter.placeholder.subowner', - 'choices' => $subownerOptions, - 'attr' => [ - 'class' => 'form-control', - 'data-ea-widget' => 'ea-autocomplete', - ], - 'required' => false, - 'disabled' => 0 == count($subownerOptions), - 'data' => $formParameters['subowner'] ?? null, - ]); - if ($filterThemes) { - $filterFormBuilder->add('theme', ChoiceType::class, [ - 'label' => 'filter.theme', - 'placeholder' => 'filter.placeholder.theme', - 'choices' => array_flip( - $userGroupsThemesAndCategories['themes'] - ), - 'attr' => [ - 'class' => 'form-control', - 'data-ea-widget' => 'ea-autocomplete', - ], - 'required' => false, - 'data' => $formParameters['theme'] ?? null, - ]); - } - if ($filterCategories) { - $filterFormBuilder->add('category', ChoiceType::class, [ - 'label' => 'filter.category', - 'placeholder' => 'filter.placeholder.category', - 'choices' => array_flip( - $userGroupsThemesAndCategories['categories'] - ), - 'attr' => [ - 'class' => 'form-control', - 'data-ea-widget' => 'ea-autocomplete', - ], - 'required' => false, - 'data' => $formParameters['category'] ?? null, - ]); - } - if (count($filterSelfServiceOptions) > 0) { - $filterFormBuilder->add('self_service', ChoiceType::class, [ - 'label' => 'filter.self_service', - 'placeholder' => 'filter.placeholder.self_service', - 'choices' => $filterSelfServiceOptions, - 'attr' => [ - 'class' => 'form-control', - 'data-ea-widget' => 'ea-autocomplete', - ], - 'required' => false, - 'data' => $formParameters['self_service'] ?? null, - ]); - } - $filterFormBuilder->add('search', TextType::class, [ - 'label' => 'filter.search', - 'attr' => [ - 'class' => 'form-control', - 'placeholder' => 'filter.placeholder.search', - ], - 'required' => false, - 'data' => $formParameters['search'] ?? null, - ]); - $filterFormBuilder->add('save', SubmitType::class, [ - 'label' => 'filter.submit', - ]); - - return $filterFormBuilder; - } - /** * Overrides EasyAdmin show action. * diff --git a/src/Controller/Admin/EntityFilterTrait.php b/src/Controller/Admin/EntityFilterTrait.php new file mode 100644 index 0000000..d29e0c3 --- /dev/null +++ b/src/Controller/Admin/EntityFilterTrait.php @@ -0,0 +1,312 @@ +createFormBuilder(options: ['csrf_protection' => false]) + ->setMethod(Request::METHOD_GET); + } + + protected function buildCustomFilters( + Request $request, + string $entityType, + FormBuilderInterface $builder, + bool $filterThemes = false, + bool $filterCategories = false, + ): void { + $userGroups = $this->entityManager->getRepository(UserGroup::class)->findAll(); + $userGroupsThemesAndCategories = $this->getUserGroupsThemesAndCategories($userGroups, $entityType); + + $formParameters = array_merge([ + 'groups' => [], + 'subowner' => '', + 'search' => '', + ], $request->get('form') ?: []); + + $repository = $this->entityManager->getRepository($entityType); + // Get sub owners if a group has been selected. + $subOwnerOptions = $this->getSubOwnerOptions($repository, $formParameters['groups']); + + $filterSelfServiceOptions = $this->getSelfServiceOptions($entityType); + + $placeholder = $this->translator->trans('filter.placeholder.groups'); + $builder->add('groups', ChoiceType::class, [ + 'label' => 'filter.groups', + 'placeholder' => $placeholder, + 'choices' => array_flip($userGroupsThemesAndCategories['groups']), + 'multiple' => true, + 'attr' => [ + // @todo Find documentation reference for why setting data-ea-widget actually works + // (https://github.com/search?q=repo%3AEasyCorp%2FEasyAdminBundle%20data-ea-widget&type=code) + 'data-ea-widget' => 'ea-autocomplete', + 'data-placeholder' => $placeholder, + ], + 'required' => false, + 'data' => $formParameters['groups'] ?? null, + ]); + + $placeholder = $this->translator->trans('filter.placeholder.subowner'); + $builder->add('subowner', ChoiceType::class, [ + 'label' => 'filter.subowner', + 'placeholder' => 'filter.placeholder.subowner', + 'choices' => $subOwnerOptions, + 'attr' => [ + 'data-ea-widget' => 'ea-autocomplete', + 'data-placeholder' => $placeholder, + ], + 'required' => false, + 'disabled' => empty($subOwnerOptions), + 'data' => $formParameters['subowner'] ?? null, + ]); + + if ($filterThemes) { + $placeholder = $this->translator->trans('filter.placeholder.theme'); + $builder->add('theme', ChoiceType::class, [ + 'label' => 'filter.theme', + 'placeholder' => $placeholder, + 'choices' => array_flip($userGroupsThemesAndCategories['themes']), + 'attr' => [ + 'data-ea-widget' => 'ea-autocomplete', + 'data-placeholder' => $placeholder, + ], + 'required' => false, + 'data' => $formParameters['theme'] ?? null, + ]); + } + if ($filterCategories) { + $placeholder = $this->translator->trans('filter.placeholder.category'); + $builder->add('category', ChoiceType::class, [ + 'label' => 'filter.category', + 'placeholder' => $placeholder, + 'choices' => array_flip($userGroupsThemesAndCategories['categories']), + 'attr' => [ + 'data-ea-widget' => 'ea-autocomplete', + 'data-placeholder' => $placeholder, + ], + 'required' => false, + 'data' => $formParameters['category'] ?? null, + ]); + } + if (count($filterSelfServiceOptions) > 0) { + $placeholder = $this->translator->trans('filter.placeholder.self_service'); + $builder->add('self_service', ChoiceType::class, [ + 'label' => 'filter.self_service', + 'placeholder' => $placeholder, + 'choices' => $filterSelfServiceOptions, + 'attr' => [ + 'data-ea-widget' => 'ea-autocomplete', + 'data-placeholder' => $placeholder, + ], + 'required' => false, + 'data' => $formParameters['self_service'] ?? null, + ]); + } + + $placeholder = $this->translator->trans('filter.placeholder.search'); + $builder->add('search', TextType::class, [ + 'label' => 'filter.search', + 'attr' => [ + 'placeholder' => $placeholder, + ], + 'required' => false, + 'data' => $formParameters['search'] ?? null, + ]); + $builder->add('save', SubmitType::class, [ + 'label' => 'filter.submit', + ]); + } + + /** + * Get array of a user's groups, themes and categories. + * + * @param UserGroup[] $userGroups + * + * @return mixed + */ + private function getUserGroupsThemesAndCategories(array $userGroups, string $entityType) + { + return array_reduce($userGroups, + function ($carry, UserGroup $group) use ($entityType) { + $carry['groups'][$group->getId()] = $group->getName(); + + $groupThemes = 'report' == $entityType ? $group->getReportThemes() : $group->getSystemThemes(); + + foreach ($groupThemes as $theme) { + $carry['themes'][$theme->getId()] = $theme->getName(); + + foreach ($theme->getOrderedCategories() as $category) { + $carry['categories'][$category->getId()] = $category->getName(); + } + } + + return $carry; + }, [ + 'groups' => [], + 'themes' => [], + 'categories' => [], + ]); + } + + /** + * Get subowners for selected group. + * + * @param EntityRepository $repository + * @param UserGroup[] $selectedGroups + * + * @return mixed + */ + private function getSubOwnerOptions(EntityRepository $repository, array $selectedGroups) + { + $groups = $this->entityManager->getRepository(UserGroup::class)->findBy([ + 'id' => $selectedGroups, + ]); + + $subOwnersQueryBuilder = $repository->createQueryBuilder('e'); + $subOwnersQueryBuilder->select('DISTINCT e.sysOwnerSub'); + $subOwnersQueryBuilder->andWhere('e.sysOwnerSub IS NOT NULL'); + + foreach ($groups as $group) { + $subOwnersQueryBuilder->andWhere($subOwnersQueryBuilder->expr()->isMemberOf(':group'.$group->getId(), 'e.groups')); + $subOwnersQueryBuilder->setParameter(':group'.$group->getId(), $group); + } + + $subOwners = $subOwnersQueryBuilder->getQuery()->getResult(); + + return array_reduce($subOwners, + function ($carry, $item) { + $carry[$item['sysOwnerSub']] = $item['sysOwnerSub']; + + return $carry; + }, []); + } + + /** + * Get options for self service filter. + * + * @return array + */ + private function getSelfServiceOptions(string $entityType): array + { + $selfServiceOptions = []; + if (System::class === $entityType) { + /* @var \Doctrine\Common\Collections\Collection $selfServiceAvailableFromItems */ + $selfServiceAvailableFromItems = $this->entityManager->getRepository(SelfServiceAvailableFromItem::class)->findAll(); + /* @var SelfServiceAvailableFromItem $item */ + foreach ($selfServiceAvailableFromItems as $item) { + $selfServiceOptions[$item->getName()] = $item->getId(); + } + } + + return $selfServiceOptions; + } + + /** + * @return array + */ + private function getFilterParameters(Request $request): array + { + $formData = (array) ($request->query->all()['form'] ?? null); + + return array_merge( + [ + 'groups' => [], + 'subowner' => '', + 'theme' => '', + 'category' => '', + 'self_service' => '', + 'search' => '', + ], + $formData + ); + } + + /** + * Apply common filters for report and system query. + * + * @param QueryBuilder $query + * @param array $filterParameters + * @param string|null $entityType + * + * @return QueryBuilder + */ + private function applyFilters( + QueryBuilder $query, + array $filterParameters, + ?string $entityType = null, + ?string $alias = null, + ): QueryBuilder { + $alias ??= $query->getRootAliases()[0]; + if (empty($alias)) { + throw new \RuntimeException('Cannot get root alias for query'); + } + + // Get the groups the user can search in. + if (!empty($filterParameters['groups'])) { + $groups = $this->entityManager + ->getRepository(UserGroup::class) + ->findBy([ + 'id' => $filterParameters['groups'], + ]); + + foreach ($groups as $group) { + $query->andWhere( + $query + ->expr() + ->isMemberOf(':group'.$group->getId(), $alias.'.groups') + ); + $query->setParameter(':group'.$group->getId(), $group); + } + } + + if ( + isset($filterParameters['self_service']) + && '' != $filterParameters['self_service'] + ) { + $item = $this->entityManager + ->getRepository(SelfServiceAvailableFromItem::class) + ->findOneBy([ + 'id' => $filterParameters['self_service'], + ]); + if (null != $item) { + $query->andWhere( + ':self_service MEMBER OF '.$alias.'.selfServiceAvailableFromItems' + ); + $query->setParameter('self_service', $item); + } + } + + if ( + isset($filterParameters['search']) + && '' != $filterParameters['search'] + ) { + $query->andWhere($alias.'.name LIKE :name'); + $query->setParameter('name', '%'.$filterParameters['search'].'%'); + } + + if ( + isset($filterParameters['subowner']) + && '' != $filterParameters['subowner'] + ) { + $query->andWhere($alias.'.sysOwnerSub = :subowner'); + $query->setParameter('subowner', $filterParameters['subowner']); + } + + return $query; + } +} diff --git a/src/Controller/Admin/GroupCrudController.php b/src/Controller/Admin/GroupCrudController.php index 3fdff0c..27b7f58 100644 --- a/src/Controller/Admin/GroupCrudController.php +++ b/src/Controller/Admin/GroupCrudController.php @@ -10,9 +10,9 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; -use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; -use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField; +use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; +use Symfony\Component\Translation\TranslatableMessage; class GroupCrudController extends AbstractCrudController { @@ -24,12 +24,17 @@ public static function getEntityFqcn(): string #[\Override] public function configureActions(Actions $actions): Actions { - $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ; + return parent::configureActions($actions) + ->add(Crud::PAGE_INDEX, Action::DETAIL); + } - return $actions; + #[\Override] + public function configureCrud(Crud $crud): Crud + { + return parent::configureCrud($crud) + ->setEntityLabelInSingular(new TranslatableMessage('UserGroup')) + ->setEntityLabelInPlural(new TranslatableMessage('UserGroups')) + ; } /** @@ -38,33 +43,20 @@ public function configureActions(Actions $actions): Actions #[\Override] public function configureFields(string $pageName): iterable { - $id = IdField::new('id'); - $name = TextField::new('name'); - $roles = ArrayField::new('roles'); - $system = ArrayField::new('systems'); - $report = ArrayField::new('reports'); - $systemtheme = ArrayField::new('systemThemes'); - $reporttheme = ArrayField::new('reportTheme'); - $users = IntegerField::new('users'); - - $asoc_report = AssociationField::new('reports'); - $asoc_systems = AssociationField::new('systems'); - - $choice_roles = ChoiceField::new('roles')->setChoices([ + yield TextField::new('name'); + yield ChoiceField::new('roles')->setChoices([ 'User' => 'ROLE_USER', 'Admin' => 'ROLE_ADMIN', - ])->allowMultipleChoices(true)->renderExpanded()->setEmptyData(false); - - if (Crud::PAGE_INDEX === $pageName) { - return [$name, $asoc_report, $asoc_systems]; - } elseif (Crud::PAGE_DETAIL === $pageName) { - return [$id, $name, $roles, $system, $report, $systemtheme, $reporttheme, $users]; - } elseif (Crud::PAGE_NEW === $pageName) { - return [$name, $choice_roles]; - } elseif (Crud::PAGE_EDIT === $pageName) { - return [$id, $name, $roles]; - } else { - throw new \Exception('Invalid page: '.$pageName); - } + ])->allowMultipleChoices()->renderExpanded()->setEmptyData(false); + yield CollectionField::new('reports') + ->setTemplatePath('admin/collection.html.twig'); + yield AssociationField::new('systems') + ->setTemplatePath('admin/collection.html.twig'); + yield ArrayField::new('systemThemes') + ->setTemplatePath('admin/collection.html.twig'); + yield ArrayField::new('reportThemes') + ->setTemplatePath('admin/collection.html.twig'); + yield ArrayField::new('users') + ->setTemplatePath('admin/collection.html.twig'); } } diff --git a/src/Controller/Admin/ReportCrudController.php b/src/Controller/Admin/ReportCrudController.php index 0940164..4a65866 100644 --- a/src/Controller/Admin/ReportCrudController.php +++ b/src/Controller/Admin/ReportCrudController.php @@ -6,16 +6,17 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; -use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField; use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; +use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField; +use Symfony\Component\Translation\TranslatableMessage; -class ReportCrudController extends AbstractCrudController +class ReportCrudController extends AbstractFilterableCrudController { public static function getEntityFqcn(): string { @@ -36,68 +37,88 @@ public function configureActions(Actions $actions): Actions #[\Override] public function configureFields(string $pageName): iterable { - $title = TextField::new('sys_title')->setLabel('entity.report.sys_title'); - $title2 = $title->setFormTypeOption('disabled', 'disabled'); - $internalId = IdField::new('sys_internal_id')->setLabel('entity.report.sys_internal_id'); - $edocUrl = UrlField::new('edoc_url')->setLabel('entity.report.edoc_url'); - $sys_link = UrlField::new('sys_link')->setLabel('entity.report.sys_link'); - $name = TextField::new('name')->setLabel('entity.report.name'); - $text = TextField::new('text')->setLabel('entity.report.text'); - $systemOwner = TextField::new('sys_system_owner')->setLabel('entity.report.sys_system_owner'); - $groups = AssociationField::new('groups')->setLabel('entity.report.groups'); + switch ($pageName) { + case Crud::PAGE_INDEX: + // Cf. https://github.com/itk-dev/sysstatus/blob/5383a3a566ce316c338441ed826ecf3fdcf98815/src/Controller/AdminController.php#L263-L288 + yield IdField::new('id')->setLabel('entity.report.sys_id'); + yield TextField::new('sysTitle')->setLabel('entity.report.sys_title'); + yield CollectionField::new('groups')->setLabel('entity.report.groups') + ->renderExpanded(); + yield TextField::new('sysOwnerSub')->setLabel('entity.report.sys_owner_sub'); + yield TextField::new('sysSystemOwner')->setLabel('entity.report.sys_system_owner'); + yield UrlField::new('sysLink')->setLabel('entity.report.sys_link') + ->formatValue(static fn ($value) => new TranslatableMessage('Link')); + yield BooleanField::new('textSet')->setLabel('entity.report.text') + ->renderAsSwitch(false) + ->hideValueWhenFalse(); - $answerarea = CollectionField::new('answerarea')->setTemplatePath('easy_admin_overrides/answers_show.html.twig')->setLabel('Smileys'); - $answerarea = CollectionField::new('answerarea')->setTemplatePath('easy_admin_overrides/answers_show.html.twig')->setLabel('Smileys'); + return; - $alternativeTitle = TextField::new('sys_alternative_title')->setLabel('entity.report.sys_alternative_title'); - $sys_updated = DateTimeField::new('sys_updated')->setLabel('entity.report.sys_updated'); - $owner = TextField::new('sys_owner')->setLabel('entity.report.sys_owner'); - $afdeling = TextField::new('sys_owner_sub')->setLabel('entity.report.sys_owner'); - $confidentialInformation = BooleanField::new('sys_confidential_information')->setLabel('entity.report.sys_confidential_information'); - $purpose = TextField::new('sys_purpose')->setLabel('entity.report.sys_purpose'); - $classification = TextField::new('sys_classification')->setLabel('entity.report.sys_classification'); - $dateForRevision = DateTimeField::new('sys_date_for_revision')->setLabel('entity.report.sys_date_for_revision'); - $persons = TextField::new('sys_persons')->setLabel('entity.report.sys_persons'); - $informationTypes = TextField::new('sys_information_types')->setLabel('entity.report.sys_information_types'); - $dataSentTo = TextField::new('sys_data_sent_to')->setLabel('entity.report.sys_data_sent_to'); - $dataComeFrom = TextField::new('sys_data_come_from')->setLabel('entity.report.sys_data_come_from'); - $dataLocation = TextField::new('sys_data_location')->setLabel('entity.report.sys_data_location'); - $deletionDate = TextField::new('sys_latest_deletion_date')->setLabel('entity.report.sys_latest_deletion_date'); - $dataWorthSaving = TextField::new('sys_data_worth_saving')->setLabel('entity.report.sys_data_worth_saving'); - $dataProcessors = TextField::new('sys_data_processors')->setLabel('entity.report.sys_data_processors'); - $dataProcessingAgreement = TextField::new('sys_data_processing_agreement')->setLabel('entity.report.sys_data_processing_agreement'); - $dataProcessingAgreementLink = TextField::new('sys_data_processing_agreement_link')->setLabel('entity.report.sys_data_processing_agreement_link'); - $auditorStatement = TextField::new('sys_auditor_statement')->setLabel('entity.report.sys_auditor_statement'); - $auditorStatementLink = TextField::new('sys_auditor_statement_link')->setLabel('entity.report.sys_auditor_statement_link'); - $dataToScience = TextField::new('sys_data_to_science')->setLabel('entity.report.sys_data_to_science'); - $usage = TextField::new('sys_usage')->setLabel('entity.report.sys_usage'); - $requestForInsight = TextField::new('sys_request_for_insight')->setLabel('entity.report.sys_request_for_insight'); - $dateUse = DateTimeField::new('sys_date_use')->setLabel('entity.report.sys_date_use'); - $status = TextField::new('sys_status')->setLabel('entity.report.sys_status'); - $remarks = TextField::new('sys_remarks')->setLabel('entity.report.sys_remarks'); - $internalInformation = TextField::new('sys_internal_information')->setLabel('entity.report.sys_internal_information'); - $obligationToInform = TextField::new('sys_obligation_to_inform')->setLabel('entity.report.sys_obligation_to_inform'); - $legalBasis = TextField::new('sys_legal_basis')->setLabel('entity.report.sys_legal_basis'); - $consent = TextField::new('sys_consent')->setLabel('entity.report.sys_consent'); - $impactAnalysis = TextField::new('sys_impact_analysis')->setLabel('entity.report.sys_impact_analysis'); - $impactAnalysisLink = TextField::new('sys_impact_analysis_link')->setLabel('entity.report.sys_impact_analysis_link'); - $authorizationProcedure = TextField::new('sys_authorization_procedure')->setLabel('entity.report.sys_authorization_procedure'); - $version = TextField::new('sys_version')->setLabel('entity.report.sys_version'); + case Crud::PAGE_DETAIL: + // Cf. https://github.com/itk-dev/sysstatus/blob/5383a3a566ce316c338441ed826ecf3fdcf98815/config/packages/easy_admin.yaml#L100-L143 + yield TextField::new('sysTitle')->setLabel('entity.report.sys_title'); + yield IdField::new('sysInternalId')->setLabel('entity.report.sys_internal_id'); + yield UrlField::new('eDocUrl')->setLabel('entity.report.edoc_url'); + yield UrlField::new('sysLink')->setLabel('entity.report.sys_link'); + yield TextField::new('name')->setLabel('entity.report.name'); + yield TextEditorField::new('text')->setLabel('entity.report.text') + // Show raw value + ->setTemplatePath('admin/text_editor.raw.html.twig'); + yield TextField::new('sysSystemOwner')->setLabel('entity.report.sys_system_owner'); + // @todo Add links to each group? + yield CollectionField::new('groups')->setLabel('entity.report.groups'); + yield CollectionField::new('answerArea')->setLabel('entity.report.answers') + ->setTemplatePath('easy_admin_overrides/answers_show.html.twig'); + yield TextField::new('sysAlternativeTitle')->setLabel('entity.report.sys_alternative_title'); + yield DateTimeField::new('sysUpdated')->setLabel('entity.report.sys_updated'); + yield TextField::new('sysOwner')->setLabel('entity.report.sys_owner'); + yield BooleanField::new('sysConfidentialInformation')->setLabel('entity.report.sys_confidential_information'); + yield TextField::new('sysPurpose')->setLabel('entity.report.sys_purpose'); + yield TextField::new('sysClassification')->setLabel('entity.report.sys_classification'); + yield DateTimeField::new('sysDateForRevision')->setLabel('entity.report.sys_date_for_revision'); + yield TextField::new('sysPersons')->setLabel('entity.report.sys_persons'); + yield TextField::new('sysInformationTypes')->setLabel('entity.report.sys_information_types'); + yield TextField::new('sysDataSentTo')->setLabel('entity.report.sys_data_sent_to'); + yield TextField::new('sysDataComeFrom')->setLabel('entity.report.sys_data_come_from'); + yield TextField::new('sysDataLocation')->setLabel('entity.report.sys_data_location'); + yield TextField::new('sysLatestDeletionDate')->setLabel('entity.report.sys_latest_deletion_date'); + yield TextField::new('sysDataWorthSaving')->setLabel('entity.report.sys_data_worth_saving'); + yield TextField::new('sysDataProcessors')->setLabel('entity.report.sys_data_processors'); + yield TextField::new('sysDataProcessingAgreement')->setLabel('entity.report.sys_data_processing_agreement'); + yield TextField::new('sysDataProcessingAgreementLink')->setLabel('entity.report.sys_data_processing_agreement_link'); + yield TextField::new('sysAuditorStatement')->setLabel('entity.report.sys_auditor_statement'); + yield TextField::new('sysAuditorStatementLink')->setLabel('entity.report.sys_auditor_statement_link'); + yield TextField::new('sysDataToScience')->setLabel('entity.report.sys_data_to_science'); + yield TextField::new('sysUsage')->setLabel('entity.report.sys_usage'); + yield TextField::new('sysRequestForInsight')->setLabel('entity.report.sys_request_for_insight'); + yield DateTimeField::new('sysDateUse')->setLabel('entity.report.sys_date_use'); + yield TextField::new('sysStatus')->setLabel('entity.report.sys_status'); + yield TextField::new('sysRemarks')->setLabel('entity.report.sys_remarks'); + yield TextField::new('sysInternalInformation')->setLabel('entity.report.sys_internal_information'); + yield TextField::new('sysObligationToInform')->setLabel('entity.report.sys_obligation_to_inform'); + yield TextField::new('sysLegalBasis')->setLabel('entity.report.sys_legal_basis'); + yield TextField::new('sysConsent')->setLabel('entity.report.sys_consent'); + yield TextField::new('sysImpactAnalysis')->setLabel('entity.report.sys_impact_analysis'); + yield TextField::new('sysImpactAnalysisLink')->setLabel('entity.report.sys_impact_analysis_link'); + yield TextField::new('sysAuthorizationProcedure')->setLabel('entity.report.sys_authorization_procedure'); + yield TextField::new('sysVersion')->setLabel('entity.report.sys_version'); - if (Crud::PAGE_INDEX === $pageName) { - return [$title, $name, $systemOwner, $afdeling, $edocUrl, $text, $sys_link, $text, $groups]; - } elseif (Crud::PAGE_DETAIL === $pageName) { - return [$title, $internalId, $edocUrl, - $name, $text, $systemOwner, $groups, $answerarea, $alternativeTitle, $sys_updated, $owner, - $confidentialInformation, $purpose, $classification, $dateForRevision, $persons, $informationTypes, - $dataSentTo, $dataComeFrom, $dataLocation, $deletionDate, $dataWorthSaving, $dataProcessors, - $dataProcessingAgreement, $dataProcessingAgreementLink, $auditorStatement, $auditorStatementLink, - $dataToScience, $usage, $requestForInsight, $dateUse, $status, $remarks, $internalInformation, $obligationToInform, - $legalBasis, $consent, $impactAnalysis, $impactAnalysisLink, $authorizationProcedure, $version]; - } elseif (Crud::PAGE_EDIT === $pageName) { - return [$title2, $edocUrl, $groups, $text]; - } else { - throw new \Exception('Invalid page: '.$pageName); + return; + + case Crud::PAGE_NEW: + case Crud::PAGE_EDIT: + // Cf. https://github.com/itk-dev/sysstatus/blob/5383a3a566ce316c338441ed826ecf3fdcf98815/config/packages/easy_admin.yaml#L144-L150 + yield TextField::new('sysTitle')->setLabel('entity.report.sys_title') + ->setDisabled(); + yield TextEditorField::new('text'); + yield AssociationField::new('groups')->setLabel('entity.report.groups') + ->setPermission('ROLE_ADMIN'); + yield UrlField::new('eDocUrl')->setLabel('entity.report.edoc_url'); + + return; + + default: + throw new \Exception('Invalid page: '.$pageName); } } } diff --git a/src/Controller/Admin/SystemCrudController.php b/src/Controller/Admin/SystemCrudController.php index 664fa5e..a30f9ad 100644 --- a/src/Controller/Admin/SystemCrudController.php +++ b/src/Controller/Admin/SystemCrudController.php @@ -6,8 +6,6 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; -use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; -use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateField; @@ -16,21 +14,13 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField; -class SystemCrudController extends AbstractCrudController +class SystemCrudController extends AbstractFilterableCrudController { public static function getEntityFqcn(): string { return System::class; } - #[\Override] - public function configureFilters(Filters $filters): Filters - { - $filters->add('name'); - - return parent::configureFilters($filters); // TODO: Change the autogenerated stub - } - #[\Override] public function configureActions(Actions $actions): Actions { diff --git a/src/Controller/Admin/ThemeCrudController.php b/src/Controller/Admin/ThemeCrudController.php index 9f4513d..2336087 100644 --- a/src/Controller/Admin/ThemeCrudController.php +++ b/src/Controller/Admin/ThemeCrudController.php @@ -4,11 +4,12 @@ use App\Entity\Theme; use App\Form\ThemeCategoryType; +use EasyCorp\Bundle\EasyAdminBundle\Config\Action; +use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; -use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; class ThemeCrudController extends AbstractCrudController @@ -18,40 +19,42 @@ public static function getEntityFqcn(): string return Theme::class; } + #[\Override] + public function configureCrud(Crud $crud): Crud + { + return parent::configureCrud($crud) + // https://symfony.com/bundles/EasyAdminBundle/current/design.html#form-field-templates + ->addFormTheme('admin/form.html.twig'); + } + + #[\Override] + public function configureActions(Actions $actions): Actions + { + return parent::configureActions($actions) + ->add(Crud::PAGE_INDEX, Action::DETAIL); + } + /** * @throws \Exception */ #[\Override] public function configureFields(string $pageName): iterable { - $id = IdField::new('id'); - $name = TextField::new('name'); - - $sysgroups = AssociationField::new('systemGroups') - ->setFormTypeOption('by_reference', false)->setLabel('Systemer') - ; - - $repgroups = AssociationField::new('reportGroups') - ->setFormTypeOption('by_reference', false)->setLabel('Anmeldelser') - ; - - $categoriesField = CollectionField::new('themeCategories')->setLabel('Kategorier') + yield TextField::new('name')->setLabel('entity.theme.name'); + // See templates/admin/form.html.twig for details on how we show this as a table. + yield CollectionField::new('themeCategories')->setLabel('entity.theme.categories') + ->setTemplatePath('admin/collection.html.twig') ->setEntryType(ThemeCategoryType::class) + ->renderExpanded() ->setFormTypeOptions([ 'by_reference' => false, // important for OneToMany associations ]) ; - - if (Crud::PAGE_INDEX === $pageName) { - return [$name, $sysgroups, $repgroups, $categoriesField]; - } elseif (Crud::PAGE_DETAIL === $pageName) { - return [$id]; - } elseif (Crud::PAGE_NEW === $pageName) { - return [$name, $sysgroups, $repgroups, $categoriesField]; - } elseif (Crud::PAGE_EDIT === $pageName) { - return [$name, $sysgroups, $repgroups, $categoriesField]; - } else { - throw new \Exception('Invalid page: '.$pageName); - } + yield AssociationField::new('systemGroups')->setLabel('entity.theme.system_groups') + ->setTemplatePath('admin/collection.html.twig') + ->setFormTypeOption('by_reference', false); + yield AssociationField::new('reportGroups')->setLabel('entity.theme.report_groups') + ->setTemplatePath('admin/collection.html.twig') + ->setFormTypeOption('by_reference', false); } } diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index 77bde18..12579d9 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -7,7 +7,6 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; -use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; @@ -25,13 +24,8 @@ public static function getEntityFqcn(): string #[\Override] public function configureActions(Actions $actions): Actions { - $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ; - - return $actions; + return parent::configureActions($actions) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } /** @@ -40,30 +34,16 @@ public function configureActions(Actions $actions): Actions #[\Override] public function configureFields(string $pageName): iterable { - $username = TextField::new('username'); - $password = TextField::new('password'); - $email = EmailField::new('email'); - $groups = AssociationField::new('groups'); - $enabled = BooleanField::new('enabled'); - $lastLogin = DateTimeField::new('lastLogin'); - - $roles = ArrayField::new('roles'); - - $choice_roles = ChoiceField::new('roles')->setChoices([ + yield TextField::new('username'); + yield EmailField::new('email'); + yield AssociationField::new('groups') + ->hideOnIndex(); + yield BooleanField::new('enabled'); + yield DateTimeField::new('lastLogin') + ->hideOnForm(); + yield ChoiceField::new('roles')->setChoices([ 'User' => 'ROLE_USER', 'Admin' => 'ROLE_ADMIN', - ])->allowMultipleChoices(true)->renderExpanded()->setEmptyData(false); - - if (Crud::PAGE_INDEX === $pageName) { - return [$username, $email, $enabled, $lastLogin, $roles]; - } elseif (Crud::PAGE_DETAIL === $pageName) { - return [$username, $email, $groups, $enabled, $lastLogin, $roles]; - } elseif (Crud::PAGE_NEW === $pageName) { - return [$username, $email, $groups, $enabled, $password, $choice_roles]; - } elseif (Crud::PAGE_EDIT === $pageName) { - return [$username, $email, $groups, $enabled, $password, $choice_roles]; - } else { - throw new \Exception('Invalid page: '.$pageName); - } + ])->allowMultipleChoices()->renderExpanded()->setEmptyData(false); } } diff --git a/src/Doctrine/EntityActiveFilter.php b/src/Doctrine/EntityActiveFilter.php new file mode 100644 index 0000000..d304c87 --- /dev/null +++ b/src/Doctrine/EntityActiveFilter.php @@ -0,0 +1,31 @@ +getName()) { + case Report::class: + return sprintf( + '(%1$s.archived_at IS NULL AND %1$s.sys_status = %2$s)', + $targetTableAlias, + $this->getConnection()->quote(Report::STATUS_ACTIVE) + ); + case System::class: + return sprintf( + '(%1$s.archived_at IS NULL AND %1$s.sys_status <> %2$s)', + $targetTableAlias, + $this->getConnection()->quote(System::STATUS_NOT_ACTIVE) + ); + } + + return ''; + } +} diff --git a/src/Entity/Report.php b/src/Entity/Report.php index c09526b..5523f0d 100644 --- a/src/Entity/Report.php +++ b/src/Entity/Report.php @@ -20,6 +20,7 @@ class Report implements \Stringable use BlameableEntity; use TimestampableEntity; use ArchivableEntity; + public const string STATUS_ACTIVE = 'Aktiv'; #[ORM\Id] #[ORM\GeneratedValue] diff --git a/src/Entity/System.php b/src/Entity/System.php index 4bf4ecc..1ae13d3 100644 --- a/src/Entity/System.php +++ b/src/Entity/System.php @@ -20,6 +20,7 @@ class System implements \Stringable use BlameableEntity; use TimestampableEntity; use ArchivableEntity; + public const string STATUS_NOT_ACTIVE = 'Systemet bruges ikke længere'; #[ORM\Id] #[ORM\GeneratedValue] diff --git a/src/Entity/Theme.php b/src/Entity/Theme.php index a4dbcce..b48422d 100644 --- a/src/Entity/Theme.php +++ b/src/Entity/Theme.php @@ -5,6 +5,7 @@ use App\Repository\ThemeRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; use Gedmo\Blameable\Traits\BlameableEntity; use Gedmo\Mapping\Annotation\Loggable; @@ -29,6 +30,7 @@ class Theme implements \Stringable * @var Collection */ #[ORM\OneToMany(mappedBy: 'theme', targetEntity: ThemeCategory::class, cascade: ['persist'], orphanRemoval: true)] + #[ORM\OrderBy(['sortOrder' => Criteria::ASC])] private Collection $themeCategories; /** @@ -165,7 +167,7 @@ public function addReportGroup(UserGroup $reportGroup): self public function removeReportGroup(UserGroup $reportGroup): self { if ($this->reportGroups->removeElement($reportGroup)) { - $reportGroup->removeSystemTheme($this); + $reportGroup->removeReportTheme($this); } return $this; diff --git a/src/Entity/ThemeCategory.php b/src/Entity/ThemeCategory.php index d58c0b9..7980c35 100644 --- a/src/Entity/ThemeCategory.php +++ b/src/Entity/ThemeCategory.php @@ -8,7 +8,7 @@ use Gedmo\Timestampable\Traits\TimestampableEntity; #[ORM\Entity(repositoryClass: ThemeCategoryRepository::class)] -class ThemeCategory +class ThemeCategory implements \Stringable { use BlameableEntity; use TimestampableEntity; @@ -18,8 +18,8 @@ class ThemeCategory private ?int $id = null; #[ORM\ManyToOne(inversedBy: 'themeCategories')] - #[ORM\JoinColumn(nullable: false)] - private Theme $theme; + #[ORM\JoinColumn(nullable: true)] + private ?Theme $theme = null; #[ORM\ManyToOne(inversedBy: 'themeCategories')] #[ORM\JoinColumn(nullable: false)] @@ -28,6 +28,11 @@ class ThemeCategory #[ORM\Column(type: 'integer', nullable: true, options: ['default' => 0])] private ?int $sortOrder = 0; + public function __toString(): string + { + return sprintf('%d: %s', $this->sortOrder, $this->getCategory()?->getName()); + } + public function getId(): ?int { return $this->id; diff --git a/src/Entity/User.php b/src/Entity/User.php index ba005a3..a9dca53 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -15,7 +15,7 @@ #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])] #[ORM\UniqueConstraint(fields: ['email'])] -class User implements UserInterface, PasswordAuthenticatedUserInterface +class User implements UserInterface, PasswordAuthenticatedUserInterface, \Stringable { use BlameableEntity; use TimestampableEntity; @@ -57,6 +57,11 @@ public function __construct() $this->groups = new ArrayCollection(); } + public function __toString(): string + { + return (string) $this->username; + } + public function getId(): ?int { return $this->id; diff --git a/src/Entity/UserGroup.php b/src/Entity/UserGroup.php index 1cdcd64..5b7d0f4 100644 --- a/src/Entity/UserGroup.php +++ b/src/Entity/UserGroup.php @@ -4,6 +4,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Order; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; @@ -20,6 +21,7 @@ class UserGroup implements \Stringable */ #[ORM\ManyToMany(targetEntity: Theme::class, inversedBy: 'systemGroups')] #[ORM\JoinTable(name: 'group_system_themes')] + #[ORM\OrderBy(['name' => Order::Ascending->value])] private Collection $systemThemes; /** @@ -27,18 +29,21 @@ class UserGroup implements \Stringable */ #[ORM\ManyToMany(targetEntity: Theme::class, inversedBy: 'reportGroups')] #[ORM\JoinTable(name: 'group_report_themes')] + #[ORM\OrderBy(['name' => Order::Ascending->value])] private Collection $reportThemes; /** * @var Collection */ #[ORM\ManyToMany(targetEntity: Report::class, mappedBy: 'groups')] + #[ORM\OrderBy(['name' => Order::Ascending->value])] private Collection $reports; /** * @var Collection */ #[ORM\ManyToMany(targetEntity: System::class, mappedBy: 'groups')] + #[ORM\OrderBy(['name' => Order::Ascending->value])] private Collection $systems; /** diff --git a/src/Service/DataExporter.php b/src/Service/DataExporter.php index a7e5dcf..85b7fde 100644 --- a/src/Service/DataExporter.php +++ b/src/Service/DataExporter.php @@ -474,10 +474,6 @@ public function exportReport( bool $withColor = false, ): void { $qb = $this->reportRepository->createQueryBuilder('e'); - $qb->where($qb->expr()->isNull('e.archivedAt')) - ->andWhere($qb->expr() - ->eq('e.sysStatus', $qb->expr()->literal('Aktiv'))) - ; if (isset($groupId)) { $qb->andWhere($qb->expr()->isMemberOf(':group', 'e.groups')) @@ -515,11 +511,6 @@ public function exportSystem( bool $withColor = false, ): void { $qb = $this->systemRepository->createQueryBuilder('e'); - $qb->where($qb->expr()->isNull('e.archivedAt')) - ->andWhere($qb->expr() - ->neq('e.sysStatus', - $qb->expr()->literal('Systemet bruges ikke længere'))) - ; if (isset($groupId)) { $qb->andWhere($qb->expr()->isMemberOf(':group', 'e.groups')) diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index db9d05b..e87de2b 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -4,17 +4,24 @@ use App\Entity\Question; use Doctrine\Common\Collections\ArrayCollection; +use EasyCorp\Bundle\EasyAdminBundle\Registry\CrudControllerRegistry; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; class AppExtension extends AbstractExtension { + public function __construct( + private readonly CrudControllerRegistry $crudControllerRegistry, + ) { + } + #[\Override] public function getFunctions(): array { return [ - new TwigFunction('getclass', $this->getClass(...)), + new TwigFunction('get_class', $this->getClass(...)), + new TwigFunction('get_crud_fqcn', $this->getCrudFqcn(...)), new TwigFunction('getanswer', $this->getAnswer(...)), new TwigFunction('breakintolines', $this->breakIntoLines(...)), ]; @@ -28,11 +35,16 @@ public function getFilters(): array ]; } - public function getClass(mixed $instance): bool + public function getClass(mixed $instance): string { return $instance::class; } + public function getCrudFqcn(mixed $entity): ?string + { + return $this->crudControllerRegistry->findCrudFqcnByEntityFqcn($entity::class); + } + public function getAnswer(mixed $entity, Question $question): mixed { $answers = $entity->getAnswers(); @@ -113,7 +125,7 @@ public function sortOrder(mixed $item): mixed { $iterator = $item->getIterator(); - $iterator->uasort(static fn ($a, $b) => $a->getSortOrder() <=> $b->getSortOrder()); + $iterator->uasort(static fn ($a, $b) => $a->getSortOrder() > $b->getSortOrder() ? -1 : 1); return new ArrayCollection(iterator_to_array($iterator)); } diff --git a/templates/_partials/custom_filters.html.twig b/templates/_partials/custom_filters.html.twig new file mode 100644 index 0000000..cda39ff --- /dev/null +++ b/templates/_partials/custom_filters.html.twig @@ -0,0 +1,46 @@ +
+ {% form_theme custom_filters 'bootstrap_5_layout.html.twig' %} + {{ form_start(custom_filters, {attr: {class: 'w-100'}}) }} +
+ {% for child in [ + 'groups', + 'subowner', + 'theme', + 'category', + 'self_service', + ] %} + {% if custom_filters[child] is defined %} +
+ {{ form_widget(custom_filters[child]) }} +
+ {% endif %} + {% endfor %} +
+
+
+ {% if custom_filters.search is defined %} + {{ form_widget(custom_filters.search) }} + {% endif %} +
+
+ {# We don't need the submit button name (`form[save]`) when submitting the filters, so we add `full_name: false` remove the name (make it empty) #} + {{ form_widget(custom_filters.save, {full_name: false, attr: {class: 'btn btn-primary w-100 h-100'}}) }} +
+
+ {{ form_end(custom_filters) }} +
+ + diff --git a/templates/admin/collection.html.twig b/templates/admin/collection.html.twig new file mode 100644 index 0000000..b1509ab --- /dev/null +++ b/templates/admin/collection.html.twig @@ -0,0 +1,25 @@ +{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} +{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} +{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} +{% if ea.crud.currentAction == 'detail' %} + {% if field.value|length %} +
    + {% for index, item in field.value %} + {% set crud_fqcn = get_crud_fqcn(item) %} + {% set item_url = crud_fqcn ? ea_url().setController(crud_fqcn).setAction('detail').setEntityId(item.id) %} + +
  • + {% if item_url %} + {{ item }} + {% else %} + {{ item }} + {% endif %} +
  • + {% endfor %} +
+ {% else %} + {{ 'Empty'|trans }} + {% endif %} +{% else %} + {{ field.value|length }} +{% endif %} diff --git a/templates/admin/collection_plain.html.twig b/templates/admin/collection_plain.html.twig new file mode 100644 index 0000000..2ba9f2e --- /dev/null +++ b/templates/admin/collection_plain.html.twig @@ -0,0 +1,21 @@ +{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} +{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} +{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} +{% if ea.crud.currentAction == 'detail' %} + {% if field.value|length %} +
    + {% for index, item in field.value %} + {% set crud_fqcn = get_crud_fqcn(item) %} + {% set item_url = crud_fqcn ? ea_url().setController(crud_fqcn).setAction('detail').setEntityId(item.id) %} + +
  • + {{ item }} +
  • + {% endfor %} +
+ {% else %} + {{ 'Empty'|trans }} + {% endif %} +{% else %} + {{ field.value|length }} + {% endif %} diff --git a/templates/admin/crud/index.html.twig b/templates/admin/crud/index.html.twig new file mode 100644 index 0000000..2d0cd7c --- /dev/null +++ b/templates/admin/crud/index.html.twig @@ -0,0 +1,9 @@ +{% extends '@EasyAdmin/crud/index.html.twig' %} + +{% block main %} + {% if custom_filters is defined %} + {{ include('_partials/custom_filters.html.twig', {custom_filters}) }} + {% endif %} + + {{ parent() }} +{% endblock %} diff --git a/templates/admin/form.html.twig b/templates/admin/form.html.twig new file mode 100644 index 0000000..576970d --- /dev/null +++ b/templates/admin/form.html.twig @@ -0,0 +1,134 @@ +{# @see https://symfony.com/bundles/EasyAdminBundle/current/design.html#form-field-templates #} +{% block _Theme_themeCategories_widget %} + {% set id = 'form-theme-themeCategories' %} + + + + + + + + {{ block('_Theme_themeCategories_collection_widget') }} + +
{{ 'theme.form.sort_order'|trans }}{{ 'theme.form.category'|trans }}
+ {# Pretend that the collection is an array collection to make "add item" not assume that we have an accordion, cf. vendor/easycorp/easyadmin-bundle/assets/js/field-collection.js #} + + + {% if allow_add|default(false) and not disabled %} + + {% endif %} +{% endblock %} + +{% block _Theme_themeCategories_collection_widget %} + {% set isEmptyCollection = value is null or (value is iterable and value is empty) %} + + {% if isEmptyCollection %} + {{ block('_Theme_themeCategories_empty_collection') }} + {% else %} + {{ block('form_widget') }} + {% endif %} +{% endblock %} + +{% block _Theme_themeCategories_empty_collection %} + + + {{ 'No theme categories'|trans }} + + +{% endblock %} + +{% block _Theme_themeCategories_entry_row %} + {# See block collection_entry_row in @EasyAdmin:crud/form_theme.html.twig for details on which bits and pieces are used to stitch this together #} + + + {{ form_widget(form.sortOrder) }} + {{ form_errors(form.sortOrder) }} + + + {{ form_widget(form.category) }} + {{ form_errors(form.category) }} + + + + + +{% endblock %} + +{# ---------------------------------------------------------------------------- #} + +{# @see https://symfony.com/bundles/EasyAdminBundle/current/design.html#form-field-templates #} +{% block _Category_questions_widget %} + {% set id = 'form-category-questions' %} + + + + + + + + + {{ block('_Category_questions_collection_widget') }} + +
{{ 'category.form.sort_order'|trans }}{{ 'category.form.question'|trans }}
+ {# Pretend that the collection is an array collection to make "add item" not assume that we have an accordion, cf. vendor/easycorp/easyadmin-bundle/assets/js/field-collection.js #} + + + {% if allow_add|default(false) and not disabled %} + + {% endif %} +{% endblock %} + +{% block _Category_questions_collection_widget %} + {% set isEmptyCollection = value is null or (value is iterable and value is empty) %} + + {% if isEmptyCollection %} + {{ block('_Category_questions_empty_collection') }} + {% else %} + {{ block('form_widget') }} + {% endif %} +{% endblock %} + +{% block _Category_questions_empty_collection %} + + + {{ 'No questions'|trans }} + + +{% endblock %} + +{% block _Category_questions_entry_row %} + {# See block collection_entry_row in @EasyAdmin:crud/form_theme.html.twig for details on which bits and pieces are used to stitch this together #} + + + {{ form_widget(form.sortOrder) }} + {{ form_errors(form.sortOrder) }} + + + {{ form_widget(form.question) }} + {{ form_errors(form.question) }} + + + + + +{% endblock %} diff --git a/templates/admin/text_editor.raw.html.twig b/templates/admin/text_editor.raw.html.twig new file mode 100644 index 0000000..2b1ee8e --- /dev/null +++ b/templates/admin/text_editor.raw.html.twig @@ -0,0 +1,5 @@ +{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} +{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} +{% if 'detail' == ea.crud.currentAction %} + {{ field.formattedValue|raw }} +{% endif %} diff --git a/templates/dashboard.html.twig b/templates/dashboard.html.twig index 6d22499..9ab09b1 100644 --- a/templates/dashboard.html.twig +++ b/templates/dashboard.html.twig @@ -9,46 +9,18 @@ {% endblock %} {% block main %} -
- {{ form_start(filters, {attr: {class: 'w-100'}}) }} -
-
- {{ form_widget(filters.groups) }} -
-
- {{ form_widget(filters.subowner) }} -
-
- {{ form_widget(filters.theme) }} -
-
- {{ form_widget(filters.category) }} -
- {% if filters.self_service is defined %} -
- {{ form_widget(filters.self_service) }} -
- {% endif %} -
-
-
- {{ form_widget(filters.search) }} -
-
- {# We don't need the submit button name (`form[save]`) when submitting the filters, so we add `full_name: false` remove the name (make it empty) #} - {{ form_widget(filters.save, {full_name: false, attr: {class: 'btn btn-primary w-100 h-100'}}) }} -
-
- {{ form_end(filters) }} -
+ {% if custom_filters is defined %} + {{ include('_partials/custom_filters.html.twig', {custom_filters}) }} + {% endif %}
{% for item in paginator %} - {% endfor %} - + {% if categories|length > 0 %} @@ -106,27 +78,11 @@
+
+ {% if entityType == 'system' %} {{ item.showableName }} @@ -62,7 +34,7 @@
- {% endblock main %} - -{% block body_javascript %} - {{ parent() }} - - -{% endblock %} diff --git a/templates/easy_admin_overrides/answers_show.html.twig b/templates/easy_admin_overrides/answers_show.html.twig index 8835885..552b5d6 100644 --- a/templates/easy_admin_overrides/answers_show.html.twig +++ b/templates/easy_admin_overrides/answers_show.html.twig @@ -2,12 +2,12 @@ {% set alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] %} {% set categoryCounter = 0 %} -{% for theme in entity.instance.getAnswerArea %} +{% for theme in field.value %} {% for themeCategory in theme.themeCategories|sort_order %} {% set category = themeCategory.category %} {% set categoryCounter = categoryCounter + 1 %} - {% set section_id = category.name|slug|lower %} -

{{ category.name }}

+ {% set section_id = category.name|slug|lower %} +

{{ category.name }}

@@ -22,21 +22,14 @@ {% set questionCounter = questionCounter + 1 %} {% set question_id = section_id ~ '-' ~ questionCounter %} - - + + {# Get a list of answers within the system #} {% set answers = question.answers|filter(a => a in entity.instance.answers) %} {% set referer = app.request.pathinfo ~ '#' ~ question_id %} - {# @todo The layout will break if ww have more that one answer #} + {# @todo The layout will break if we have more that one answer #} {% for answer in answers %} {% endfor %} diff --git a/translations/messages.da.yml b/translations/messages.da.yml index 8701df1..1572e97 100644 --- a/translations/messages.da.yml +++ b/translations/messages.da.yml @@ -17,6 +17,8 @@ category: form: sort_order: Sortering question: Spørgsmål + delete_item: Fjern + add_new_item: Tilføj theme: new: @@ -29,14 +31,21 @@ theme: theme_categories: Kategorier sort_order: Sortering category: Kategori + delete_item: Fjern + add_new_item: Tilføj filter: + groups: Grupper + subowner: Undergruppe + theme: Tema + category: Kategori + search: Søgetekst submit: Søg placeholder: - groups: -- Vælg grupper -- - subowner: -- Vælg undergruppe -- - theme: -- Vælg tema -- - category: -- Vælg kategori -- + groups: Vælg grupper + subowner: Vælg undergruppe + theme: Vælg tema + category: Vælg kategori search: Søg på navn self_service: Filtrér på selvbetjening @@ -66,7 +75,7 @@ entity: category: id: ID name: Navn - questions: Antal spørgsmål + questions: Spørgsmål question: id: ID question: Spørgsmål @@ -186,8 +195,9 @@ entity: sys_data_to_science: Videregivelse af oplysninger til forskning sys_system_owner: Systemejer menu: - import_runs: Import kørsler + import_runs: Importkørsler configuration: Konfiguration + administration: Administration notes: Notater systemportal: Fra systemportalen general: Redigerbar Data @@ -213,8 +223,11 @@ menu: theme_category: Tema-Kategori exports: Eksport ImportRun: Import kørsler +Empty: Tom System: System Systems: Systemer +UserGroup: Gruppe +UserGroups: Grupper User: Bruger Report: Anmeldelse Reports: Anmeldelser @@ -250,10 +263,10 @@ answers_show: actions: Handlinger edit: Ret no_active_theme: Intet tilkoblet tema. -'Created by': Oprettet af -'Updated by': Opdateret af -'Created at': Oprettet -'Updated at': Opdateret +'Created By': Oprettet af +'Updated By': Opdateret af +'Created At': Oprettet +'Updated At': Opdateret custom_filters: none: Alle Apply: Udfør
{{ alphabet[categoryCounter - 1] }}{{ questionCounter }} - - {{ question.question }} - - {{ alphabet[(categoryCounter - 1) % alphabet|length] }}{{ questionCounter }}{{ question.question }} {{ include('easy_admin_overrides/field_smiley.html.twig', {value: answer.smiley, title: answer.note, width: 25}) }} @@ -65,7 +58,8 @@ .setAction('new') .setEntityId(null) }}"> - {{ 'Opret Ny'|trans }} + {# {{ 'Opret Ny'|trans }} #} + {{ 'answers_show.edit'|trans }}