mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-05 20:57:12 +00:00
Resolve conflicts between 2.1 and 2.2
This commit is contained in:
commit
197648f0ed
400 changed files with 4433 additions and 1453 deletions
2
.github/workflows/ci__full_1_14.yaml
vendored
2
.github/workflows/ci__full_1_14.yaml
vendored
|
|
@ -78,7 +78,7 @@ jobs:
|
|||
name: "Notify about build status"
|
||||
needs: [static-checks, e2e-mariadb, e2e-mysql, e2e-pgsql, frontend, packages]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: "Process data"
|
||||
|
|
|
|||
2
.github/workflows/ci__full_2_1.yaml
vendored
2
.github/workflows/ci__full_2_1.yaml
vendored
|
|
@ -88,7 +88,7 @@ jobs:
|
|||
name: "Notify about build status"
|
||||
needs: [static-checks, e2e-mariadb, e2e-mysql, e2e-pgsql, e2e-js, frontend, packages]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: "Process data"
|
||||
|
|
|
|||
2
.github/workflows/ci__full_2_2.yaml
vendored
2
.github/workflows/ci__full_2_2.yaml
vendored
|
|
@ -88,7 +88,7 @@ jobs:
|
|||
name: "Notify about build status"
|
||||
needs: [static-checks, e2e-mariadb, e2e-mysql, e2e-pgsql, e2e-js, frontend, packages]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: "Process data"
|
||||
|
|
|
|||
4
.github/workflows/ci_e2e-mariadb.yaml
vendored
4
.github/workflows/ci_e2e-mariadb.yaml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MariaDB ${{ matrix.mariadb }}, State Machine Adapter ${{ matrix.state_machine_adapter }}${{ matrix.api_platform && format(', ApiPlatform {0}', matrix.api_platform) || '' }}"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }}
|
||||
|
|
@ -158,7 +158,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MariaDB ${{ matrix.mariadb }}, State Machine Adapter ${{ matrix.state_machine_adapter }}${{ matrix.api_platform && format(', ApiPlatform {0}', matrix.api_platform) || '' }}"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }}
|
||||
|
|
|
|||
4
.github/workflows/ci_e2e-mysql.yaml
vendored
4
.github/workflows/ci_e2e-mysql.yaml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }}
|
||||
|
|
@ -144,7 +144,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }}
|
||||
|
|
|
|||
4
.github/workflows/ci_e2e-pgsql.yaml
vendored
4
.github/workflows/ci_e2e-pgsql.yaml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "PHPUnit, CLI, API, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, PostgreSQL ${{ matrix.postgres }}"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }}
|
||||
|
|
@ -132,7 +132,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "UI, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, PostgreSQL ${{ matrix.postgres }}"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }}
|
||||
|
|
|
|||
6
.github/workflows/ci_e2e-unstable.yaml
vendored
6
.github/workflows/ci_e2e-unstable.yaml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
behat-no-js-unstable:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Non-JS, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }} (Unstable Dependencies)"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
@ -98,7 +98,7 @@ jobs:
|
|||
behat-ui-js-chromedriver-unstable:
|
||||
runs-on: ubuntu-latest
|
||||
name: "JS with Chromedriver, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }} (${{ matrix.env || 'test_cached' }}), MySQL ${{ matrix.mysql }} (Unstable Dependencies)"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -180,7 +180,7 @@ jobs:
|
|||
behat-ui-js-panther-unstable:
|
||||
runs-on: ubuntu-latest
|
||||
name: "JS with Panther, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }} (${{ matrix.env || 'test_cached' }}), MySQL ${{ matrix.mysql }} (Unstable Dependencies)"
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
|
|||
4
.github/workflows/ci_js.yaml
vendored
4
.github/workflows/ci_js.yaml
vendored
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "[${{ matrix.group }}] [PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}]"
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }}
|
||||
|
|
@ -134,7 +134,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "Panther, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MySQL ${{ matrix.mysql }}"
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.get-matrix.outputs.panther-matrix) }}
|
||||
|
|
|
|||
2
.github/workflows/ci_packages-unstable.yaml
vendored
2
.github/workflows/ci_packages-unstable.yaml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
test_unstable:
|
||||
runs-on: ubuntu-latest
|
||||
name: "PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }} (Unstable Dependencies)"
|
||||
timeout-minutes: 25
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
|
|||
2
.github/workflows/ci_packages.yaml
vendored
2
.github/workflows/ci_packages.yaml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
needs: get-matrix
|
||||
runs-on: ubuntu-latest
|
||||
name: "PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}"
|
||||
timeout-minutes: 25
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
|
|||
13
.github/workflows/matrix.json
vendored
13
.github/workflows/matrix.json
vendored
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
{
|
||||
"php": "8.4",
|
||||
"symfony": "~7.3.0"
|
||||
"symfony": "~7.4.0"
|
||||
},
|
||||
{
|
||||
"php": "8.5",
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
},
|
||||
{
|
||||
"php": "8.5",
|
||||
"symfony": "~7.3.0"
|
||||
"symfony": "~7.4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -139,7 +139,6 @@
|
|||
],
|
||||
"symfony": [
|
||||
"~6.4.0",
|
||||
"~7.3.0",
|
||||
"~7.4.0"
|
||||
]
|
||||
},
|
||||
|
|
@ -151,7 +150,6 @@
|
|||
],
|
||||
"symfony": [
|
||||
"~6.4.0",
|
||||
"~7.3.0",
|
||||
"~7.4.0"
|
||||
],
|
||||
"mariadb": [
|
||||
|
|
@ -163,7 +161,7 @@
|
|||
"include": [
|
||||
{
|
||||
"php": "8.3",
|
||||
"symfony": "~7.3.0",
|
||||
"symfony": "~7.4.0",
|
||||
"mariadb": "11.4.7",
|
||||
"state_machine_adapter": "winzou_state_machine"
|
||||
}
|
||||
|
|
@ -176,7 +174,6 @@
|
|||
],
|
||||
"symfony": [
|
||||
"~6.4.0",
|
||||
"~7.3.0",
|
||||
"~7.4.0"
|
||||
],
|
||||
"mysql": [
|
||||
|
|
@ -188,7 +185,7 @@
|
|||
"include": [
|
||||
{
|
||||
"php": "8.3",
|
||||
"symfony": "~7.3.0",
|
||||
"symfony": "~7.4.0",
|
||||
"mysql": "8.0",
|
||||
"twig": "^3.3"
|
||||
}
|
||||
|
|
@ -250,7 +247,6 @@
|
|||
],
|
||||
"symfony": [
|
||||
"~6.4.0",
|
||||
"~7.3.0",
|
||||
"~7.4.0"
|
||||
],
|
||||
"postgres": [
|
||||
|
|
@ -273,7 +269,6 @@
|
|||
],
|
||||
"symfony": [
|
||||
"~6.4.0",
|
||||
"~7.3.0",
|
||||
"~7.4.0"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
2
.github/workflows/refactor.yaml
vendored
2
.github/workflows/refactor.yaml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
name: "Coding standard refactor"
|
||||
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 15
|
||||
|
||||
if: github.repository == 'Sylius/Sylius'
|
||||
|
||||
|
|
|
|||
2
.github/workflows/upmerge_pr.yaml
vendored
2
.github/workflows/upmerge_pr.yaml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'Sylius/Sylius'
|
||||
name: "Upmerge PR"
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
|
|||
75
AGENTS.md
75
AGENTS.md
|
|
@ -1,18 +1,51 @@
|
|||
# AI Contribution Guidelines
|
||||
|
||||
Welcome, 🤖 AI assistant! Please follow these guidelines when contributing to this Sylius:
|
||||
Guidelines for AI assistants contributing to Sylius.
|
||||
|
||||
## Reference Files
|
||||
|
||||
When working on specific areas, check these files for patterns:
|
||||
|
||||
### Entities & Models
|
||||
- Entity pattern: `src/Sylius/Component/Core/Model/Product.php`
|
||||
- Interface pattern: `src/Sylius/Component/Core/Model/ProductInterface.php`
|
||||
- Doctrine mapping: `src/Sylius/Bundle/CoreBundle/Resources/config/doctrine/model/`
|
||||
|
||||
### API Platform 4.x
|
||||
- Resource definitions: `src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/`
|
||||
- Properties/serialization: `src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/properties/`
|
||||
- Admin resources: `resources/admin/Product.xml`
|
||||
- Shop resources: `resources/shop/Product.xml`
|
||||
|
||||
### Services & Configuration
|
||||
- Service definitions: `src/Sylius/Bundle/CoreBundle/Resources/config/services.xml`
|
||||
- Bundle config: `src/Sylius/Bundle/*/Resources/config/`
|
||||
|
||||
### Templates & Hooks
|
||||
- Admin templates: `src/Sylius/Bundle/AdminBundle/templates/`
|
||||
- Shop templates: `src/Sylius/Bundle/ShopBundle/templates/`
|
||||
- Twig hooks: check existing hooks in templates for naming patterns
|
||||
|
||||
### Tests
|
||||
- PHPUnit functional: `tests/Functional/`
|
||||
- PHPUnit API: `tests/Api/`
|
||||
- Behat contexts: `src/Sylius/Behat/Context/`
|
||||
|
||||
### Migrations
|
||||
- Location: `src/Sylius/Bundle/CoreBundle/Migrations/`
|
||||
- **ALWAYS create TWO migrations**: one for MySQL, one for PostgreSQL
|
||||
- MySQL: extend `Sylius\Bundle\CoreBundle\Doctrine\Migrations\AbstractMigration`
|
||||
- PostgreSQL: extend `Sylius\Bundle\CoreBundle\Doctrine\Migrations\AbstractPostgreSQLMigration`
|
||||
- When reviewing PRs that touch migrations, verify both versions exist
|
||||
|
||||
## General Guidelines
|
||||
|
||||
### Project Structure & Design
|
||||
|
||||
- This is Sylius: e-commerce framework
|
||||
- Sylius is built on top of **Symfony**
|
||||
- Sylius must be easily extendable in the end application
|
||||
- Sylius contains Bundles and Components that can be used independently
|
||||
- Sylius is designed to be modular and flexible
|
||||
- Sylius is designed to be fast and efficient
|
||||
- Sylius is an e-commerce framework built on **Symfony**
|
||||
- Bundles and Components can be used independently
|
||||
- Follow the Sylius Backward Compatibility (BC) policy
|
||||
- When changing interfaces, always provide BC layer
|
||||
|
||||
### Compatibility & Security
|
||||
|
||||
|
|
@ -80,10 +113,29 @@ Welcome, 🤖 AI assistant! Please follow these guidelines when contributing to
|
|||
|
||||
## PHPUnit
|
||||
|
||||
- Place unit tests in `tests/Unit/`
|
||||
- Place functional tests in `tests/Functional/`
|
||||
- Place API tests in `tests/Api/`
|
||||
- Test class names must end with `Test` suffix
|
||||
- Use `#[Covers]` attribute for unit tests
|
||||
- For API tests, extend `ApiTestCase` and use `assertResponse*` methods
|
||||
- Run specific test: `vendor/bin/phpunit --filter TestClassName`
|
||||
|
||||
## Behat
|
||||
|
||||
- Place feature files in `features/` directory
|
||||
- Use existing contexts from `src/Sylius/Behat/Context/`
|
||||
- Follow Given-When-Then pattern strictly
|
||||
- Use `@ui` tag for UI tests, `@api` for API tests
|
||||
- Page objects are in `src/Sylius/Behat/Page/`
|
||||
|
||||
## JavaScript
|
||||
|
||||
- Use TypeScript where possible
|
||||
- Use Stimulus controllers for interactive components
|
||||
- Place controllers in `assets/admin/controllers/` or `assets/shop/controllers/`
|
||||
- Follow existing naming conventions for controller files
|
||||
|
||||
## CSS
|
||||
|
||||
- Use SCSS (`.scss`) syntax – plain CSS files are not allowed
|
||||
|
|
@ -94,3 +146,12 @@ Welcome, 🤖 AI assistant! Please follow these guidelines when contributing to
|
|||
- Avoid `!important` unless absolutely necessary
|
||||
- Prefer `rem` over `px` for spacing, font size, etc.
|
||||
- Use `mixins/` for reusable logic (e.g., `@include icon-size(24px)`)
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
- **BC breaks**: Never change method signatures in interfaces without deprecation layer
|
||||
- **Missing Doctrine mapping**: New entity properties need XML mapping in `Resources/config/doctrine/`
|
||||
- **Hardcoded strings**: Always use translation keys in templates
|
||||
- **Missing serialization groups**: API properties need proper groups in `properties/*.xml`
|
||||
- **Wrong namespace**: Components have no Symfony dependency, Bundles can
|
||||
- **Forgetting tests**: API changes need PHPUnit tests in `tests/Api/`
|
||||
|
|
|
|||
115
CHANGELOG-2.2.md
Normal file
115
CHANGELOG-2.2.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# CHANGELOG FOR `2.2.X`
|
||||
|
||||
## v2.2.5 (2026-04-10)
|
||||
|
||||
#### Details
|
||||
|
||||
- [#18579](https://github.com/Sylius/Sylius/pull/18579) Fix problem with empty taxon product index ([@tomkalon](https://github.com/tomkalon))
|
||||
- [#18932](https://github.com/Sylius/Sylius/pull/18932) TASK: improve german translations for CH, DE, and AT ([@crydotsnake](https://github.com/crydotsnake))
|
||||
- [#18933](https://github.com/Sylius/Sylius/pull/18933) fix: add default filter to breadcrumbs configuration title to prevent ScalarDataBag exception ([@camilleislasse](https://github.com/camilleislasse))
|
||||
- [#18943](https://github.com/Sylius/Sylius/pull/18943) Fix build after APIPlatform 4.3.2 release ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18941](https://github.com/Sylius/Sylius/pull/18941) Add payment enabled in channel validation ([@marekrzytki](https://github.com/marekrzytki))
|
||||
- [#18926](https://github.com/Sylius/Sylius/pull/18926) [BUGFIX] Allow updating provinceName when provinceCode is null in API ([@rust-le](https://github.com/rust-le))
|
||||
- [#18953](https://github.com/Sylius/Sylius/pull/18953) [BUGFIX] Make mailer services public to allow usage in state machine callbacks ([@rust-le](https://github.com/rust-le))
|
||||
- [#18961](https://github.com/Sylius/Sylius/pull/18961) Resolve flash alert correctly without breaking translation ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18940](https://github.com/Sylius/Sylius/pull/18940) Restore missing page titles for admin resource show pages ([@marekrzytki](https://github.com/marekrzytki))
|
||||
- [#18922](https://github.com/Sylius/Sylius/pull/18922) Unified name translations handling in admin grid index ([@pbalcerzak](https://github.com/pbalcerzak))
|
||||
- [#18965](https://github.com/Sylius/Sylius/pull/18965) Allow choosing different payment method with skip payment step when it gets disabled ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18969](https://github.com/Sylius/Sylius/pull/18969) Add missing status code mapping for OrderItemNotFoundException ([@marekrzytki](https://github.com/marekrzytki))
|
||||
- [#18970](https://github.com/Sylius/Sylius/pull/18970) Add email validation constraints to cart update ([@marekrzytki](https://github.com/marekrzytki))
|
||||
- [#18972](https://github.com/Sylius/Sylius/pull/18972) Fix admin templates ([@loic425](https://github.com/loic425))
|
||||
- [#18958](https://github.com/Sylius/Sylius/pull/18958) [API] Make API Platform resource command classes overridable via container parameters ([@Prometee](https://github.com/Prometee))
|
||||
- [#18974](https://github.com/Sylius/Sylius/pull/18974) Unify Tests directory with tests ([@TheMilek](https://github.com/TheMilek))
|
||||
|
||||
## v2.2.4 (2026-03-18)
|
||||
|
||||
#### Details
|
||||
|
||||
- [#18904](https://github.com/sylius/sylius/issues/18904) [BUGFIX] remove redundant `object` from PHPDoc union types
|
||||
- [#18899](https://github.com/sylius/sylius/issues/18899) [CS][DX] Refactor
|
||||
- [#18898](https://github.com/sylius/sylius/issues/18898) [CS][DX] Refactor
|
||||
- [#18895](https://github.com/sylius/sylius/issues/18895) [Admin] Fix product taxon grid `enabled` field always showing `true`
|
||||
- [#18911](https://github.com/sylius/sylius/issues/18911) [BUGFIX] fix build errors
|
||||
- [#18920](https://github.com/sylius/sylius/issues/18920) Telemetry improvements 2.1
|
||||
|
||||
## v2.2.3 (2026-03-09)
|
||||
|
||||
#### Details
|
||||
|
||||
- [#18747](https://github.com/Sylius/Sylius/pull/18747) Fix panther build ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18758](https://github.com/Sylius/Sylius/pull/18758) Remove duplicated serialization group field ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18785](https://github.com/Sylius/Sylius/pull/18785) Try to fix build after ResourceBundle release ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18742](https://github.com/Sylius/Sylius/pull/18742) [Admin] Fix order history address fields not displaying empty values ([@Wojdylak](https://github.com/Wojdylak))
|
||||
- [#18806](https://github.com/Sylius/Sylius/pull/18806) Fix after new release of PayumBundle ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18836](https://github.com/Sylius/Sylius/pull/18836) Upgrade BuildTestAppAction from v3.0.1 to v4 ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18832](https://github.com/Sylius/Sylius/pull/18832) [Admin] Fix images not being emitted with Webpack 5.105+ ([@GSadee](https://github.com/GSadee))
|
||||
- [#18841](https://github.com/Sylius/Sylius/pull/18841) Fix Dutch translation for payment method ([@JordiDekker](https://github.com/JordiDekker))
|
||||
- [#18871](https://github.com/Sylius/Sylius/pull/18871) Add conflict to api-platform/serializer 4.2.17 ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18888](https://github.com/Sylius/Sylius/pull/18888) Remove redundant check with apip4.1.7 in matrix ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18887](https://github.com/Sylius/Sylius/pull/18887) [BUGFIX] Make GitHub actions green again 2.1 ([@rust-le](https://github.com/rust-le))
|
||||
- [#18844](https://github.com/Sylius/Sylius/pull/18844) Fix formatting in UPGRADE-2.0.md ([@LucaGallinari](https://github.com/LucaGallinari))
|
||||
|
||||
## v2.2.2 (2026-01-20)
|
||||
|
||||
#### Details
|
||||
|
||||
- [#18691](https://github.com/Sylius/Sylius/pull/18691) [CS][DX] Refactor
|
||||
- [#18690](https://github.com/Sylius/Sylius/pull/18690) [CS][DX] Refactor
|
||||
- [#18689](https://github.com/Sylius/Sylius/pull/18689) [CS][DX] Refactor
|
||||
- [#18693](https://github.com/Sylius/Sylius/pull/18693) [CI] Standardize job timeouts to 15 minutes ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18703](https://github.com/Sylius/Sylius/pull/18703) [Deps] Allow psr/http-message ^2.0 ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18716](https://github.com/Sylius/Sylius/pull/18716) Try to fix build with PHP 8.5 and 8.4 ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18707](https://github.com/Sylius/Sylius/pull/18707) [DX] Update branch aliases to 1.14-dev ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18721](https://github.com/Sylius/Sylius/pull/18721) Fix tests application error templates ([@loic425](https://github.com/loic425))
|
||||
- [#16892](https://github.com/Sylius/Sylius/pull/16892) [FIXTURES] Fix menu taxon code ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18366](https://github.com/Sylius/Sylius/pull/18366) Fix ro PayumBundle Translation ([@revoltek-daniel](https://github.com/revoltek-daniel))
|
||||
- [#18725](https://github.com/Sylius/Sylius/pull/18725) Bugfix/merged overrides missing operations ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18702](https://github.com/Sylius/Sylius/pull/18702) [Composer] Remove outdated twig/twig conflicts from bundles ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18722](https://github.com/Sylius/Sylius/pull/18722) [API] Payment Request fix default action when IRI is given ([@Prometee](https://github.com/Prometee))
|
||||
- [#18732](https://github.com/Sylius/Sylius/pull/18732) Make PostgreSQL telemetry migration extend dedicated abstract ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18369](https://github.com/Sylius/Sylius/pull/18369) Add form help rendering to forms ([@tomkalon](https://github.com/tomkalon))
|
||||
- [#18735](https://github.com/Sylius/Sylius/pull/18735) [CS][DX] Refactor
|
||||
- [#18726](https://github.com/Sylius/Sylius/pull/18726) [Channel] Resolve localhost equivalents when matching channel by hostname ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18708](https://github.com/Sylius/Sylius/pull/18708) [DX] Improve AI contribution guidelines ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18711](https://github.com/Sylius/Sylius/pull/18711) Fix typo in first_name.html.twig ([@LucaGallinari](https://github.com/LucaGallinari))
|
||||
- [#18736](https://github.com/Sylius/Sylius/pull/18736) TASK: [2.2] [UiBundle] adjust translations for admin password reset page ([@crydotsnake](https://github.com/crydotsnake))
|
||||
- [#18565](https://github.com/Sylius/Sylius/pull/18565) Add ternary operator to fix related with empty key ([@michalkaczmarek-bitbag](https://github.com/michalkaczmarek-bitbag))
|
||||
- [#18572](https://github.com/Sylius/Sylius/pull/18572) Bugfix/fix autocomplete in admin to be case insensitive ([@michalkaczmarek-bitbag](https://github.com/michalkaczmarek-bitbag))
|
||||
|
||||
## v2.2.1 (2025-12-18)
|
||||
|
||||
#### Details
|
||||
|
||||
- [#18680](https://github.com/Sylius/Sylius/pull/18680) [Telemetry] Fixes and improvements 1.14 ([@TheMilek](https://github.com/TheMilek))
|
||||
|
||||
## v2.2.0 (2025-12-17)
|
||||
|
||||
#### Details
|
||||
|
||||
- [#18365](https://github.com/Sylius/Sylius/pull/18365) [Feature] Prioritize default locale in TranslationLocaleProvider output ([@tomkalon](https://github.com/tomkalon))
|
||||
- [#18412](https://github.com/Sylius/Sylius/pull/18412) [Maintenance] Raise minimal version of `api-platform/symfony` to `^4.2.1` ([@tomkalon](https://github.com/tomkalon))
|
||||
- [#18410](https://github.com/Sylius/Sylius/pull/18410) [SHOP][FEATURE] Introduce address form modifiers in checkout component ([@tomkalon](https://github.com/tomkalon))
|
||||
- [#18438](https://github.com/Sylius/Sylius/pull/18438) Remove phpspec/prophecy-phpunit dependency ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18374](https://github.com/Sylius/Sylius/pull/18374) [CORE] Add priority to Zone ([@tomkalon](https://github.com/tomkalon))
|
||||
- [#18546](https://github.com/Sylius/Sylius/pull/18546) [API] Return 422 instead of 400 for missing required fields ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18544](https://github.com/Sylius/Sylius/pull/18544) [FEATURE] Add estimated delivery time functionality ([@Cholin2000](https://github.com/Cholin2000))
|
||||
- [#18508](https://github.com/Sylius/Sylius/pull/18508) FEATURE: [2.2] [AdminBundle] make search menu in admin sidebar component hookable ([@crydotsnake](https://github.com/crydotsnake))
|
||||
- [#18583](https://github.com/Sylius/Sylius/pull/18583) Mark method as deprecated because it is not used already ([@michalkaczmarek-bitbag](https://github.com/michalkaczmarek-bitbag))
|
||||
- [#18614](https://github.com/Sylius/Sylius/pull/18614) [CS][DX] Refactor
|
||||
- [#18594](https://github.com/Sylius/Sylius/pull/18594) [Maintenance] Bump Symfony to `^6.4 || ^7.4` ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18568](https://github.com/Sylius/Sylius/pull/18568) [API][PaymentRequest] Rename payment request operations for shop context ([@Rafikooo](https://github.com/Rafikooo))
|
||||
- [#18554](https://github.com/Sylius/Sylius/pull/18554) [AdminBundle] Allowing to use translation domain on menu ([@loic425](https://github.com/loic425))
|
||||
- [#18454](https://github.com/Sylius/Sylius/pull/18454) FEAT: [2.2] [CoreBundle] extend sylius:install:setup command to set admin first/last name ([@crydotsnake](https://github.com/crydotsnake))
|
||||
- [#15889](https://github.com/Sylius/Sylius/pull/15889) FEATURE: [2.2] [AdminBundle] Introduce sylius:admin-user:delete CLI Command ([@crydotsnake](https://github.com/crydotsnake))
|
||||
- [#15946](https://github.com/Sylius/Sylius/pull/15946) FEATURE: [2.2] [AdminBundle] Introduce sylius:admin-user:list command ([@crydotsnake](https://github.com/crydotsnake))
|
||||
- [#18646](https://github.com/Sylius/Sylius/pull/18646) [Maintenance] validation fixes ([@Cholin2000](https://github.com/Cholin2000))
|
||||
- [#18654](https://github.com/Sylius/Sylius/pull/18654) [CS][DX] Refactor
|
||||
- [#18647](https://github.com/Sylius/Sylius/pull/18647) [Composer] Bump dependencies + remove unused dev dependencies ([@GSadee](https://github.com/GSadee))
|
||||
- [#18655](https://github.com/Sylius/Sylius/pull/18655) Add missing upgrade note to the 2.1 ([@GSadee](https://github.com/GSadee))
|
||||
- [#18652](https://github.com/Sylius/Sylius/pull/18652) [CS][DX] Refactor
|
||||
- [#18659](https://github.com/Sylius/Sylius/pull/18659) Add missing index to Order XML mapping ([@GSadee](https://github.com/GSadee))
|
||||
- [#18661](https://github.com/Sylius/Sylius/pull/18661) Add config/reference.php to .gitignore ([@GSadee](https://github.com/GSadee))
|
||||
- [#18662](https://github.com/Sylius/Sylius/pull/18662) Make new Zone priority field sortable by Gedmo ([@GSadee](https://github.com/GSadee))
|
||||
- [#18669](https://github.com/Sylius/Sylius/pull/18669) Fix migrations skip commands 1.14 ([@TheMilek](https://github.com/TheMilek))
|
||||
- [#18670](https://github.com/Sylius/Sylius/pull/18670) Remove Gedmo sortable-position from new Zone priority field ([@GSadee](https://github.com/GSadee))
|
||||
- [#18672](https://github.com/Sylius/Sylius/pull/18672) Fix migrations skip commands 2.1 ([@TheMilek](https://github.com/TheMilek))
|
||||
|
|
@ -1,6 +1,66 @@
|
|||
# UPGRADE FROM `2.1` TO `2.2`
|
||||
|
||||
### Deprecations
|
||||
## Telemetry
|
||||
|
||||
Sylius 2.2.0 introduces anonymous telemetry to help us understand how Sylius is used and improve the platform.
|
||||
|
||||
**What data is collected:**
|
||||
- Anonymous installation ID (hashed, non-reversible)
|
||||
- Sylius and PHP versions, default locale
|
||||
- Aggregated statistics as segments (broad ranges, not exact values):
|
||||
- Customers/products/variants count (e.g., "1K-10K", "100K-1M")
|
||||
- GMV and AOV ranges per month (e.g., "100K-500K", "50-100")
|
||||
|
||||
**No sensitive data is ever collected** - no customer information, no order details, no personal data.
|
||||
|
||||
**Configuration:**
|
||||
|
||||
Telemetry is enabled by default and uses a default salt for hashing the installation ID.
|
||||
|
||||
To disable telemetry, set the following environment variable in your `.env` file:
|
||||
|
||||
```dotenv
|
||||
SYLIUS_TELEMETRY_ENABLED=0
|
||||
```
|
||||
|
||||
To change the salt, set the `SYLIUS_TELEMETRY_SALT` environment variable:
|
||||
|
||||
```dotenv
|
||||
SYLIUS_TELEMETRY_SALT=your-custom-salt
|
||||
```
|
||||
|
||||
## Deprecations
|
||||
|
||||
1. Not injecting a `tagged_iterator` with the tag `sylius_shop.modifier.address_form_values` into the constructor of `Sylius\Bundle\ShopBundle\Twig\Component\Checkout\Address\FormComponent` is deprecated since Sylius 2.2 and will be required in Sylius 3.0.
|
||||
|
||||
This change enables extending the checkout address form with custom fields or logic by registering services tagged with `sylius_shop.modifier.address_form_values`, which implement the `AddressFormValuesModifierInterface`.
|
||||
|
||||
```php
|
||||
public function __construct(
|
||||
OrderRepositoryInterface $repository,
|
||||
FormFactoryInterface $formFactory,
|
||||
string $resourceClass,
|
||||
string $formClass,
|
||||
protected readonly CustomerContextInterface $customerContext,
|
||||
protected readonly UserRepositoryInterface $shopUserRepository,
|
||||
protected readonly AddressRepositoryInterface $addressRepository,
|
||||
+ protected readonly ?iterable $addressFormValuesModifiers = null,
|
||||
)
|
||||
```
|
||||
|
||||
1. Direct usage of `loader.svg` and `loader.gif` assets is deprecated.
|
||||
Use `@SyliusAdmin/shared/helper/loader.html.twig` or `@SyliusShop/shared/macro/loader.html.twig` instead.
|
||||
|
||||
1. The `Sylius\Bundle\ReviewBundle\Updater\ReviewableRatingUpdaterInterface::updateFromReview()` method has been deprecated and will be removed in Sylius 3.0. Use state machine mechanism implemented by Symfony Workflow instead.
|
||||
|
||||
## Admin UI
|
||||
|
||||
1. A new `modal-portal.js` script has been added to `AdminBundle`.
|
||||
It moves Bootstrap modal elements to `<body>` before they are displayed,
|
||||
preventing them from being rendered behind the Bootstrap backdrop when nested inside a CSS stacking context (e.g. the sticky `.page-header`).
|
||||
|
||||
## Translations
|
||||
|
||||
1. The `TranslationLocaleProvider` now ensures that the default locale (configured as `locale` in `config/parameters.yaml`)
|
||||
is always placed at the beginning of the returned locales array.
|
||||
Other locales remain in the same order as returned by the repository.
|
||||
|
|
|
|||
70
UPGRADE-API-2.2.md
Normal file
70
UPGRADE-API-2.2.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# UPGRADE FROM `2.1` TO `2.2`
|
||||
|
||||
## Modified routes
|
||||
|
||||
1. The routes for payment requests resource have been renamed to follow the shop API naming convention.
|
||||
|
||||
| Old route | New route |
|
||||
|----------------------------------------|----------------------------------------|
|
||||
| `sylius_api_show_payment_request_get` | `sylius_api_shop_payment_request_get` |
|
||||
| `sylius_api_show_payment_request_post` | `sylius_api_shop_payment_request_post` |
|
||||
| `sylius_api_show_payment_request_put` | `sylius_api_shop_payment_request_put` |
|
||||
|
||||
## HTTP Status Code Changes
|
||||
|
||||
### Missing Required Fields Validation
|
||||
|
||||
The HTTP status code for missing required fields in API requests has been changed from `400 Bad Request` to `422 Unprocessable Content` to follow REST API best practices and RFC 9110 semantics.
|
||||
|
||||
Additionally, the redundant `code` field has been removed from the error response body, as the status code is already available in the HTTP response headers.
|
||||
|
||||
**Before:**
|
||||
```json
|
||||
{
|
||||
"@context": "/api/v2/contexts/Error",
|
||||
"@type": "hydra:Error",
|
||||
"status": 400,
|
||||
"detail": "Request does not have the following required fields specified: email."
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```json
|
||||
{
|
||||
"@context": "/api/v2/contexts/Error",
|
||||
"@type": "hydra:Error",
|
||||
"status": 422,
|
||||
"detail": "Request does not have the following required fields specified: email."
|
||||
}
|
||||
```
|
||||
|
||||
**Breaking changes:**
|
||||
1. HTTP status code changed: `400` → `422`
|
||||
2. Response body field `code` removed (was redundant with HTTP status header)
|
||||
|
||||
**Affected endpoints:** All POST/PATCH endpoints that validate required fields (e.g., `/api/v2/shop/customers`, `/api/v2/shop/orders/{token}/items`, etc.)
|
||||
|
||||
**Migration guide:**
|
||||
- If your API client checks `response.status === 400` for missing fields, change it to `response.status === 422`
|
||||
- If your API client reads `response.data.code`, use `response.status` (HTTP header) instead
|
||||
|
||||
**References:** RFC 9110 (400 = syntactic errors, 422 = semantic/validation errors)
|
||||
|
||||
|
||||
## API Platform resource classes as container parameters
|
||||
|
||||
The following API Platform resource classes are now defined as container parameters, allowing you to override them:
|
||||
|
||||
| Parameter | Default class |
|
||||
|---|---|
|
||||
| `sylius_api.command.account.reset_password.class` | `Sylius\Bundle\ApiBundle\Command\Account\ResetPassword` |
|
||||
| `sylius_api.command.account.verify_shop_user.class` | `Sylius\Bundle\ApiBundle\Command\Account\VerifyShopUser` |
|
||||
| `sylius_api.command.admin.account.reset_password.class` | `Sylius\Bundle\ApiBundle\Command\Admin\Account\ResetPassword` |
|
||||
| `sylius_api.command.send_contact_request.class` | `Sylius\Bundle\ApiBundle\Command\SendContactRequest` |
|
||||
|
||||
To override a class, define the parameter in your configuration:
|
||||
|
||||
```yaml
|
||||
parameters:
|
||||
sylius_api.command.account.reset_password.class: App\Api\Command\CustomResetPassword
|
||||
```
|
||||
132
composer.json
132
composer.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "sylius/sylius",
|
||||
"type": "library",
|
||||
"version": "v2.1.16-dev",
|
||||
"version": "v2.2.6-dev",
|
||||
"description": "E-Commerce platform for PHP, based on Symfony framework.",
|
||||
"homepage": "https://sylius.com",
|
||||
"license": "MIT",
|
||||
|
|
@ -31,10 +31,10 @@
|
|||
"ext-json": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-sodium": "*",
|
||||
"api-platform/doctrine-orm": "^4.1.7",
|
||||
"api-platform/state": "^4.1.7",
|
||||
"api-platform/symfony": "^4.1.7",
|
||||
"babdev/pagerfanta-bundle": "^4.4",
|
||||
"api-platform/doctrine-orm": "^4.2.1",
|
||||
"api-platform/state": "^4.2.1",
|
||||
"api-platform/symfony": "^4.2.1",
|
||||
"babdev/pagerfanta-bundle": "^4.6",
|
||||
"behat/transliterator": "^1.5",
|
||||
"doctrine/collections": "^2.2",
|
||||
"doctrine/common": "^3.2",
|
||||
|
|
@ -44,12 +44,12 @@
|
|||
"doctrine/event-manager": "^2.0",
|
||||
"doctrine/inflector": "^2.0",
|
||||
"doctrine/migrations": "^3.8",
|
||||
"doctrine/orm": "^2.18 || ^3.3",
|
||||
"doctrine/orm": "^2.18 || ^3.5",
|
||||
"doctrine/persistence": "^3.3",
|
||||
"egulias/email-validator": "^4.0",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"friendsofphp/proxy-manager-lts": "^1.0",
|
||||
"gedmo/doctrine-extensions": "^3.17.1",
|
||||
"gedmo/doctrine-extensions": "^3.20",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"guzzlehttp/psr7": "^2.5",
|
||||
"knplabs/gaufrette": "^0.11",
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
"laminas/laminas-stdlib": "^3.19",
|
||||
"league/flysystem-bundle": "^3.3",
|
||||
"lexik/jwt-authentication-bundle": "^3.1",
|
||||
"liip/imagine-bundle": "^2.13",
|
||||
"liip/imagine-bundle": "^2.15",
|
||||
"pagerfanta/pagerfanta": "^4.0",
|
||||
"paragonie/halite": "^5.0",
|
||||
"payum/offline": "^1.7.5",
|
||||
|
|
@ -85,65 +85,65 @@
|
|||
"sylius/resource": "^1.12",
|
||||
"sylius/resource-bundle": "^1.12",
|
||||
"sylius/theme-bundle": "^2.4",
|
||||
"sylius/twig-extra": "^0.8",
|
||||
"sylius/twig-hooks": "^0.8",
|
||||
"symfony/asset": "^6.4 || ^7.2",
|
||||
"sylius/twig-extra": "^0.9",
|
||||
"sylius/twig-hooks": "^0.9",
|
||||
"symfony/asset": "^6.4 || ^7.4",
|
||||
"symfony/cache-contracts": "^3.5",
|
||||
"symfony/clock": "^6.4 || ^7.2",
|
||||
"symfony/config": "^6.4 || ^7.2",
|
||||
"symfony/console": "^6.4.1 || ^7.2",
|
||||
"symfony/dependency-injection": "^6.4.1 || ^7.2",
|
||||
"symfony/clock": "^6.4 || ^7.4",
|
||||
"symfony/config": "^6.4 || ^7.4",
|
||||
"symfony/console": "^6.4.1 || ^7.4",
|
||||
"symfony/dependency-injection": "^6.4.1 || ^7.4",
|
||||
"symfony/deprecation-contracts": "^3.5",
|
||||
"symfony/doctrine-bridge": "^6.4 || ^7.2",
|
||||
"symfony/doctrine-messenger": "^6.4.1 || ^7.2",
|
||||
"symfony/event-dispatcher": "^6.4 || ^7.2",
|
||||
"symfony/expression-language": "^6.4 || ^7.2",
|
||||
"symfony/filesystem": "^6.4 || ^7.2",
|
||||
"symfony/finder": "^6.4 || ^7.2",
|
||||
"symfony/form": "^6.4.1 || ^7.2",
|
||||
"symfony/framework-bundle": "^6.4.1 || ^7.2",
|
||||
"symfony/http-client": "^6.4 || ^7.2",
|
||||
"symfony/http-foundation": "^6.4 || ^7.2",
|
||||
"symfony/http-kernel": "^6.4.1 || ^7.2",
|
||||
"symfony/intl": "^6.4 || ^7.2",
|
||||
"symfony/mailer": "^6.4 || ^7.2",
|
||||
"symfony/messenger": "^6.4 || ^7.2",
|
||||
"symfony/doctrine-bridge": "^6.4 || ^7.4",
|
||||
"symfony/doctrine-messenger": "^6.4.1 || ^7.4",
|
||||
"symfony/event-dispatcher": "^6.4 || ^7.4",
|
||||
"symfony/expression-language": "^6.4 || ^7.4",
|
||||
"symfony/filesystem": "^6.4 || ^7.4",
|
||||
"symfony/finder": "^6.4 || ^7.4",
|
||||
"symfony/form": "^6.4.1 || ^7.4",
|
||||
"symfony/framework-bundle": "^6.4.1 || ^7.4",
|
||||
"symfony/http-client": "^6.4 || ^7.4",
|
||||
"symfony/http-foundation": "^6.4 || ^7.4",
|
||||
"symfony/http-kernel": "^6.4.1 || ^7.4",
|
||||
"symfony/intl": "^6.4 || ^7.4",
|
||||
"symfony/mailer": "^6.4 || ^7.4",
|
||||
"symfony/messenger": "^6.4 || ^7.4",
|
||||
"symfony/monolog-bundle": "^3.8.0",
|
||||
"symfony/options-resolver": "^6.4 || ^7.2",
|
||||
"symfony/password-hasher": "^6.4 || ^7.2",
|
||||
"symfony/options-resolver": "^6.4 || ^7.4",
|
||||
"symfony/password-hasher": "^6.4 || ^7.4",
|
||||
"symfony/polyfill-iconv": "^1.31",
|
||||
"symfony/polyfill-intl-icu": "^1.31",
|
||||
"symfony/polyfill-mbstring": "^1.31",
|
||||
"symfony/process": "^6.4 || ^7.2",
|
||||
"symfony/property-access": "^6.4 || ^7.2",
|
||||
"symfony/property-info": "^6.4 || ^7.2",
|
||||
"symfony/proxy-manager-bridge": "^6.4 || ^7.2",
|
||||
"symfony/routing": "^6.4.1 || ^7.2",
|
||||
"symfony/security-bundle": "^6.4 || ^7.2",
|
||||
"symfony/security-core": "^6.4 || ^7.2",
|
||||
"symfony/security-csrf": "^6.4 || ^7.2",
|
||||
"symfony/security-http": "^6.4 || ^7.2",
|
||||
"symfony/serializer": "^6.4 || ^7.2",
|
||||
"symfony/process": "^6.4 || ^7.4",
|
||||
"symfony/property-access": "^6.4 || ^7.4",
|
||||
"symfony/property-info": "^6.4 || ^7.4",
|
||||
"symfony/proxy-manager-bridge": "^6.4 || ^7.4",
|
||||
"symfony/routing": "^6.4.1 || ^7.4",
|
||||
"symfony/security-bundle": "^6.4 || ^7.4",
|
||||
"symfony/security-core": "^6.4 || ^7.4",
|
||||
"symfony/security-csrf": "^6.4 || ^7.4",
|
||||
"symfony/security-http": "^6.4 || ^7.4",
|
||||
"symfony/serializer": "^6.4 || ^7.4",
|
||||
"symfony/service-contracts": "^3.5",
|
||||
"symfony/stimulus-bundle": "^2.20",
|
||||
"symfony/string": "^6.4 || ^7.2",
|
||||
"symfony/translation": "^6.4 || ^7.2",
|
||||
"symfony/stimulus-bundle": "^2.25",
|
||||
"symfony/string": "^6.4 || ^7.4",
|
||||
"symfony/translation": "^6.4 || ^7.4",
|
||||
"symfony/translation-contracts": "^3.3",
|
||||
"symfony/twig-bundle": "^6.4 || ^7.2",
|
||||
"symfony/uid": "^6.4 || ^7.2",
|
||||
"symfony/ux-autocomplete": "^2.20",
|
||||
"symfony/ux-icons": "^2.20",
|
||||
"symfony/ux-live-component": "^2.20",
|
||||
"symfony/ux-twig-component": "^2.20",
|
||||
"symfony/validator": "^6.4 || ^7.2",
|
||||
"symfony/twig-bundle": "^6.4 || ^7.4",
|
||||
"symfony/uid": "^6.4 || ^7.4",
|
||||
"symfony/ux-autocomplete": "^2.25",
|
||||
"symfony/ux-icons": "^2.25",
|
||||
"symfony/ux-live-component": "^2.25",
|
||||
"symfony/ux-twig-component": "^2.25",
|
||||
"symfony/validator": "^6.4 || ^7.4",
|
||||
"symfony/webpack-encore-bundle": "^2.2",
|
||||
"symfony/workflow": "^6.4 || ^7.2",
|
||||
"symfony/yaml": "^6.4 || ^7.2",
|
||||
"symfony/workflow": "^6.4 || ^7.4",
|
||||
"symfony/yaml": "^6.4 || ^7.4",
|
||||
"symfonycasts/dynamic-forms": "^0.1",
|
||||
"twig/extra-bundle": "^3.13",
|
||||
"twig/intl-extra": "^3.13",
|
||||
"twig/string-extra": "^3.13",
|
||||
"twig/twig": "^3.14",
|
||||
"twig/extra-bundle": "^3.16",
|
||||
"twig/intl-extra": "^3.16",
|
||||
"twig/string-extra": "^3.16",
|
||||
"twig/twig": "^3.16",
|
||||
"webmozart/assert": "^1.11"
|
||||
},
|
||||
"replace": {
|
||||
|
|
@ -206,15 +206,11 @@
|
|||
"friends-of-behat/symfony-extension": "^2.6.2",
|
||||
"friends-of-behat/variadic-extension": "^1.6",
|
||||
"hwi/oauth-bundle": "^2.2",
|
||||
"jangregor/phpstan-prophecy": "^2.0",
|
||||
"lchrusciel/api-test-case": "^5.3",
|
||||
"matthiasnoback/symfony-config-test": "^6.0",
|
||||
"matthiasnoback/symfony-dependency-injection-test": "^6.0",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nyholm/psr7": "^1.8",
|
||||
"phparkitect/phparkitect": "^0.6",
|
||||
"phpspec/prophecy-phpunit": "^2.2",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpstan-doctrine": "^2.0",
|
||||
|
|
@ -226,12 +222,12 @@
|
|||
"robertfausk/behat-panther-extension": "^1.1",
|
||||
"sylius-labs/coding-standard": "^4.4",
|
||||
"sylius-labs/suite-tags-extension": "~0.2",
|
||||
"symfony/browser-kit": "^6.4 || ^7.2",
|
||||
"symfony/debug-bundle": "^6.4 || ^7.2",
|
||||
"symfony/dotenv": "^6.4 || ^7.2",
|
||||
"symfony/browser-kit": "^6.4 || ^7.4",
|
||||
"symfony/debug-bundle": "^6.4 || ^7.4",
|
||||
"symfony/dotenv": "^6.4 || ^7.4",
|
||||
"symfony/flex": "^2.4",
|
||||
"symfony/runtime": "^6.4 || ^7.2",
|
||||
"symfony/web-profiler-bundle": "^6.4 || ^7.2",
|
||||
"symfony/runtime": "^6.4 || ^7.4",
|
||||
"symfony/web-profiler-bundle": "^6.4 || ^7.4",
|
||||
"symplify/monorepo-builder": "^11.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
|
@ -263,10 +259,10 @@
|
|||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "^7.2"
|
||||
"require": "^7.4"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "2.0-dev"
|
||||
"dev-main": "2.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
|||
|
|
@ -67,3 +67,13 @@ Feature: Editing a zone
|
|||
And I save my changes
|
||||
Then I should be notified that it has been successfully edited
|
||||
And this zone name should be "EU"
|
||||
|
||||
@api @ui
|
||||
Scenario: Setting zone to the highest priority
|
||||
Given the store has a zone "European Union" with code "EU" and priority 0
|
||||
And it has the "France" country member
|
||||
When I want to modify the zone named "European Union"
|
||||
And I set its priority to "5"
|
||||
And I save my changes
|
||||
Then I should be notified that it has been successfully edited
|
||||
And the "European Union" zone should have priority 5
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
@managing_zones
|
||||
Feature: Editing a zone
|
||||
In order to change my my tax and shipping configuration
|
||||
As an Administrator
|
||||
I want to be able to edit a zone
|
||||
|
||||
Background:
|
||||
Given the store also has country "France"
|
||||
And the store has a zone "North America" with code "NA" and priority 2
|
||||
And the store has a zone "South America" with code "SA" and priority 1
|
||||
And the store has a zone "Australia" with code "AU" and priority 0
|
||||
And I am logged in as an administrator
|
||||
|
||||
@api @ui
|
||||
Scenario: Zones are sorted by priority in descending order by default
|
||||
When I want to see all zones in store
|
||||
Then I should see 3 zones in the list
|
||||
Then the first zone on the list should have name "North America"
|
||||
Then the last zone on the list should have name "Australia"
|
||||
|
||||
@api @ui
|
||||
Scenario: Zone's default priority is 0 which puts it at the bottom of the list
|
||||
Given the store has a zone "European Union" with code "EU"
|
||||
When I want to see all zones in store
|
||||
Then I should see 4 zones in the list
|
||||
And the last zone on the list should have name "European Union"
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
@managing_shipping_methods
|
||||
Feature: Validating delivery time range on shipping method form
|
||||
In order to avoid invalid delivery time configuration
|
||||
As an Administrator
|
||||
I want to be prevented from saving a shipping method when maximum delivery time is less than minimum
|
||||
|
||||
Background:
|
||||
Given the store operates on a channel named "Web-US" in "USD" currency
|
||||
And the store is available in "English (United States)"
|
||||
And the store has a zone "United States" with code "US"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @mink:chromedriver
|
||||
Scenario: Trying to add a shipping method with invalid delivery time range
|
||||
When I want to create a new shipping method
|
||||
And I specify its code as "DELIVERY_TIME_INVALID"
|
||||
And I name it "Delivery Time Invalid" in "English (United States)"
|
||||
And I define it for the zone named "United States"
|
||||
And I choose "Flat rate per shipment" calculator
|
||||
And I specify its amount as 10 for "Web-US" channel
|
||||
And I fill in "Minimum delivery time (days)" with "5"
|
||||
And I fill in "Maximum delivery time (days)" with "3"
|
||||
And I try to add it
|
||||
Then I should be notified that Maximum delivery time must be greater than or equal to the minimum.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
@applying_taxes
|
||||
Feature: Apply taxes based on zone priority
|
||||
In order to pay correct taxes when a country belongs to multiple zones
|
||||
As a Customer
|
||||
I want the zone with higher priority to be used for tax calculation
|
||||
|
||||
Background:
|
||||
Given the store operates on a single channel
|
||||
Given the store operates in "France" and "Belgium"
|
||||
And there is a zone "The Rest of the World" containing all other countries
|
||||
And the store has a zone "European Union" with code "EU" and priority 2
|
||||
And it has the "France" country member
|
||||
And it also has the "Belgium" country member
|
||||
And default tax zone is "RoW"
|
||||
And the store has "No tax" tax rate of 0% for "Clothes" for the rest of the world
|
||||
And the store has "VAT" tax rate of 23% for "Clothes" within the "European Union" zone
|
||||
And the store has a product "T-Shirt" priced at "$100.00"
|
||||
And it belongs to "Clothes" tax category
|
||||
And I am a logged in customer
|
||||
|
||||
@api @ui
|
||||
Scenario: Applying tax from the higher priority zone
|
||||
Given I added product "T-Shirt" to the cart
|
||||
And I addressed the cart to "Belgium"
|
||||
When I check the details of my cart
|
||||
Then my cart total should be "$123.00"
|
||||
And my cart taxes should be "$23.00"
|
||||
|
||||
@api @ui
|
||||
Scenario: Applying tax from new zone zone when it has higher priority
|
||||
Given the store has a zone "Benelux countries" with code "BC" and priority 3
|
||||
And it also has the "Belgium" country member
|
||||
And the store has "VAT" tax rate of 0% for "Clothes" within the "Benelux countries" zone
|
||||
And I added product "T-Shirt" to the cart
|
||||
And I addressed the cart to "Belgium"
|
||||
When I check the details of my cart
|
||||
Then my cart total should be "$100.00"
|
||||
And there should be no taxes charged
|
||||
|
||||
@api @ui
|
||||
Scenario: Applying tax from current zone when the new one has lower priority
|
||||
Given the store has a zone "Benelux countries" with code "GU" and priority 1
|
||||
And it also has the "Belgium" country member
|
||||
And the store has "VAT" tax rate of 0% for "Clothes" within the "Benelux countries" zone
|
||||
And I added product "T-Shirt" to the cart
|
||||
And I addressed the cart to "Belgium"
|
||||
When I check the details of my cart
|
||||
Then my cart total should be "$123.00"
|
||||
And my cart taxes should be "$23.00"
|
||||
|
|
@ -21,9 +21,9 @@
|
|||
},
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"symfony/dependency-injection": "^6.4.1 || ^7.2",
|
||||
"symfony/http-kernel": "^6.4.1 || ^7.2",
|
||||
"symfony/workflow": "^6.4 || ^7.2"
|
||||
"symfony/dependency-injection": "^6.4.1 || ^7.4",
|
||||
"symfony/http-kernel": "^6.4.1 || ^7.4",
|
||||
"symfony/workflow": "^6.4 || ^7.4"
|
||||
},
|
||||
"suggest": {
|
||||
"twig/twig": "Access the state machine in your twig templates (^2.12|^3.3)",
|
||||
|
|
@ -37,10 +37,10 @@
|
|||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.0-dev"
|
||||
"dev-main": "2.2-dev"
|
||||
},
|
||||
"symfony": {
|
||||
"require": "^7.2"
|
||||
"require": "^7.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ interface ResponseCheckerInterface
|
|||
|
||||
public function hasViolationWithMessage(Response $response, string $message, ?string $property = null): bool;
|
||||
|
||||
public function appendError(Response $response): ResponseCheckerInterface;
|
||||
|
||||
public function cleanErrors(): void;
|
||||
|
||||
/** @return array{
|
||||
|
|
|
|||
|
|
@ -129,6 +129,14 @@ final readonly class ManagingZonesContext implements Context
|
|||
$this->client->addRequestData('scope', $scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set its priority to :priority
|
||||
*/
|
||||
public function iSetsItsPriorityTo(int $priority): void
|
||||
{
|
||||
$this->client->addRequestData('priority', $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I (try to) add it
|
||||
*/
|
||||
|
|
@ -496,6 +504,37 @@ final readonly class ManagingZonesContext implements Context
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the (first|last) zone on the list should have ([^"]+) "([^"]+)"$/
|
||||
*/
|
||||
public function theFirstZoneOnTheListShouldHave(string $togglePosition, string $field, string $value): void
|
||||
{
|
||||
$items = $this->responseChecker->getValue($this->client->getLastResponse(), 'hydra:member');
|
||||
if ('first' === $togglePosition) {
|
||||
$item = reset($items);
|
||||
} else {
|
||||
$item = end($items);
|
||||
}
|
||||
|
||||
Assert::same($item[$field], $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the :zone zone should have priority :priority
|
||||
*/
|
||||
public function theZoneShouldHavePriority(ZoneInterface $zone, int $priority): void
|
||||
{
|
||||
Assert::true(
|
||||
$this->responseChecker->hasItemWithValues(
|
||||
$this->client->index(Resources::ZONES),
|
||||
[
|
||||
'name' => $zone->getName(),
|
||||
'priority' => $priority,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private function removeZoneMember(CountryInterface|ProvinceInterface|ZoneInterface $objectToRemove): void
|
||||
{
|
||||
$members = $this->client->getContent()['members'];
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use Sylius\Behat\Context\Ui\Admin\Helper\SecurePasswordTrait;
|
|||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class RegistrationContext implements Context
|
||||
|
|
@ -206,7 +207,7 @@ final class RegistrationContext implements Context
|
|||
$content['hydra:description'],
|
||||
'Request does not have the following required fields specified: ' . implode(', ', $fields) . '.',
|
||||
);
|
||||
Assert::same($this->shopClient->getLastResponse()->getStatusCode(), 400);
|
||||
Assert::same($this->shopClient->getLastResponse()->getStatusCode(), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ final class InstallerContext implements Context
|
|||
'locale' => 'en_US',
|
||||
'e-mail' => 'test@email.com',
|
||||
'username' => 'test',
|
||||
'firstName' => '',
|
||||
'lastName' => '',
|
||||
'password' => 'pswd',
|
||||
'confirmation' => 'pswd',
|
||||
];
|
||||
|
|
@ -146,6 +148,8 @@ final class InstallerContext implements Context
|
|||
{
|
||||
$this->inputChoices['e-mail'] = 'test@admin.com';
|
||||
$this->inputChoices['username'] = 'test';
|
||||
$this->inputChoices['firstName'] = 'John';
|
||||
$this->inputChoices['lastName'] = 'Doe';
|
||||
$this->inputChoices['password'] = 'pswd1$';
|
||||
$this->inputChoices['confirmation'] = $this->inputChoices['password'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,10 +84,14 @@ final readonly class ZoneContext implements Context
|
|||
* @Given the store has (also) a zone :zoneName
|
||||
* @Given the store has a zone :zoneName with code :code
|
||||
* @Given the store also has a zone :zoneName with code :code
|
||||
* @Given the store has a zone :zoneName with code :code and priority :priority
|
||||
*/
|
||||
public function theStoreHasAZoneWithCode(string $zoneName, ?string $code = null): void
|
||||
public function theStoreHasAZoneWithCode(string $zoneName, ?string $code = null, ?int $priority = 0): void
|
||||
{
|
||||
$this->saveZone($this->createZone($zoneName, $code, Scope::ALL), 'zone');
|
||||
$zone = $this->createZone($zoneName, $code, Scope::ALL);
|
||||
$zone->setPriority($priority);
|
||||
|
||||
$this->saveZone($zone, 'zone');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ declare(strict_types=1);
|
|||
namespace Sylius\Behat\Context\Ui\Admin;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Step\When;
|
||||
use Sylius\Behat\Element\Admin\ShippingMethod\FormElementInterface;
|
||||
use Sylius\Behat\Page\Admin\ShippingMethod\CreatePageInterface;
|
||||
use Sylius\Behat\Page\Admin\ShippingMethod\IndexPageInterface;
|
||||
|
|
@ -124,6 +125,12 @@ final readonly class ManagingShippingMethodsContext implements Context
|
|||
$this->shippingMethodFormElement->chooseCalculator($calculatorName);
|
||||
}
|
||||
|
||||
#[When('I fill in :label with :value')]
|
||||
public function iFillInWith(string $label, string $value): void
|
||||
{
|
||||
$this->shippingMethodFormElement->setField($label, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I check (also) the :shippingMethodName shipping method
|
||||
*/
|
||||
|
|
@ -625,4 +632,12 @@ final readonly class ManagingShippingMethodsContext implements Context
|
|||
{
|
||||
Assert::same($this->shippingMethodFormElement->getValidationMessage($element), $expectedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that Maximum delivery time must be greater than or equal to the minimum.
|
||||
*/
|
||||
public function iShouldBeNotifiedThatMaxDeliveryIsGreaterOrEqualMin(): void
|
||||
{
|
||||
$this->assertFieldValidationMessage('max_delivery_time_days', 'Maximum delivery time must be greater than or equal to the minimum.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,14 @@ final class ManagingZonesContext implements Context
|
|||
$this->formElement->selectScope($scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set its priority to :priority
|
||||
*/
|
||||
public function iSetItsPriorityTo(int $priority): void
|
||||
{
|
||||
$this->formElement->prioritizeIt($priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I add it
|
||||
* @When I try to add it
|
||||
|
|
@ -338,4 +346,44 @@ final class ManagingZonesContext implements Context
|
|||
|
||||
throw new \InvalidArgumentException(sprintf('Member "%s" should not be selectable.', $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the first zone on the list should have :field :value
|
||||
*/
|
||||
public function theFirstZoneOnTheListShouldHave(string $field, string $value): void
|
||||
{
|
||||
$fields = $this->indexPage->getColumnFields($field);
|
||||
$actualValue = reset($fields);
|
||||
|
||||
Assert::same(
|
||||
$actualValue,
|
||||
$value,
|
||||
sprintf('Expected first zone\'s %s to be "%s", but it is "%s".', $field, $value, $actualValue),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the last zone on the list should have :field :value
|
||||
*/
|
||||
public function theLastZoneOnTheListShouldHave(string $field, string $value): void
|
||||
{
|
||||
$fields = $this->indexPage->getColumnFields($field);
|
||||
$actualValue = end($fields);
|
||||
|
||||
Assert::same(
|
||||
$actualValue,
|
||||
$value,
|
||||
sprintf('Expected last zone\'s %s to be "%s", but it is "%s".', $field, $value, $actualValue),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the :zone zone should have priority :priority
|
||||
*/
|
||||
public function theZoneShouldHavePriority(ZoneInterface $zone, int $priority)
|
||||
{
|
||||
$this->iWantToModifyAZoneNamed($zone);
|
||||
|
||||
Assert::same($this->formElement->getPriority(), $priority);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,8 @@ class FormElement extends BaseFormElement implements FormElementInterface
|
|||
'last_rule' => '[data-test-rules] [data-test-entry-row]:last-child',
|
||||
'last_rule_amount' => '[data-test-rules] [data-test-entry-row]:last-child [id$="_configuration_%channelCode%_amount"]',
|
||||
'last_rule_weight' => '[data-test-rules] [data-test-entry-row]:last-child [id$="_configuration_weight"]',
|
||||
'max_delivery_time_days' => '#sylius_admin_shipping_method_maxDeliveryTimeDays',
|
||||
'min_delivery_time_days' => '#sylius_admin_shipping_method_minDeliveryTimeDays',
|
||||
'name' => '#sylius_admin_shipping_method_translations_%localeCode%_name',
|
||||
'position' => '#sylius_admin_shipping_method_position',
|
||||
'zone' => '#sylius_admin_shipping_method_zone',
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class TreeElement extends SyliusElement implements TreeElementInterface
|
|||
protected function getDefinedElements(): array
|
||||
{
|
||||
return array_merge(parent::getDefinedElements(), [
|
||||
'confirm_delete_button' => '[data-test-tree-taxons] [data-test-tree-taxon="%name%"] [data-test-delete-modal] [data-test-confirm-button]',
|
||||
'confirm_delete_button' => '.modal.show[data-test-delete-modal] [data-test-confirm-button]',
|
||||
'first_tree_taxon' => '[data-test-tree-taxons] [data-test-tree-taxon]:first-child',
|
||||
'last_tree_taxon' => '[data-test-tree-taxons] [data-test-tree-taxon]:last-child',
|
||||
'tree_taxons' => '[data-test-tree-taxons]',
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ class FormElement extends BaseFormElement implements FormElementInterface
|
|||
return $this->getElement('name')->getValue();
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return (int) $this->getElement('priority')->getValue();
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->getElement('type')->getValue();
|
||||
|
|
@ -67,6 +72,11 @@ class FormElement extends BaseFormElement implements FormElementInterface
|
|||
$this->waitForElement(5, 'zone_member_added');
|
||||
}
|
||||
|
||||
public function prioritizeIt(int $priority): void
|
||||
{
|
||||
$this->getElement('priority')->setValue($priority);
|
||||
}
|
||||
|
||||
public function removeMember(string $member): void
|
||||
{
|
||||
$this->getElement('zone_member_delete', ['%name%' => $member])->click();
|
||||
|
|
@ -100,6 +110,7 @@ class FormElement extends BaseFormElement implements FormElementInterface
|
|||
'code' => '[data-test-code]',
|
||||
'form_validation_message' => 'form > div.alert.alert-danger.d-block',
|
||||
'name' => '[data-test-name]',
|
||||
'priority' => '#sylius_admin_zone_priority',
|
||||
'scope' => '[data-test-scope]',
|
||||
'type' => '[data-test-type]',
|
||||
'zone_member' => '[data-test-zone-member]:contains("%name%")',
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ interface FormElementInterface extends BaseFormElementInterface
|
|||
{
|
||||
public function getName(): string;
|
||||
|
||||
public function getPriority(): int;
|
||||
|
||||
public function nameIt(string $name): void;
|
||||
|
||||
public function getType(): string;
|
||||
|
|
@ -31,6 +33,8 @@ interface FormElementInterface extends BaseFormElementInterface
|
|||
|
||||
public function addMember(): void;
|
||||
|
||||
public function prioritizeIt(int $priority): void;
|
||||
|
||||
public function getScope(): string;
|
||||
|
||||
public function selectScope(string $scope): void;
|
||||
|
|
|
|||
|
|
@ -39,17 +39,15 @@ class IndexPage extends BaseIndexPage implements IndexPageInterface
|
|||
public function archiveShippingMethod(string $name): void
|
||||
{
|
||||
$actions = $this->getActionsForResource(['name' => $name]);
|
||||
$archiveRestoreModal = $actions->find('css', '[data-test-modal="archive-restore"]');
|
||||
$archiveRestoreModal->find('css', '[data-test-trigger-button="Archive"]')->press();
|
||||
$archiveRestoreModal->find('css', '[data-test-confirm-button]')->press();
|
||||
$actions->find('css', '[data-test-trigger-button="Archive"]')->press();
|
||||
$this->getDocument()->find('css', '.modal.show[data-test-modal="archive-restore"] [data-test-confirm-button]')->press();
|
||||
}
|
||||
|
||||
public function restoreShippingMethod(string $name): void
|
||||
{
|
||||
$actions = $this->getActionsForResource(['name' => $name]);
|
||||
$archiveRestoreModal = $actions->find('css', '[data-test-modal="archive-restore"]');
|
||||
$archiveRestoreModal->find('css', '[data-test-trigger-button="Restore"]')->press();
|
||||
$archiveRestoreModal->find('css', '[data-test-confirm-button]')->press();
|
||||
$actions->find('css', '[data-test-trigger-button="Restore"]')->press();
|
||||
$this->getDocument()->find('css', '.modal.show[data-test-modal="archive-restore"] [data-test-confirm-button]')->press();
|
||||
}
|
||||
|
||||
public function isShippingMethodDisabled(ShippingMethodInterface $shippingMethod): bool
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
|
|||
use Sylius\Component\Addressing\Model\ZoneInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
|
|
@ -46,6 +47,10 @@ final class ZoneType extends AbstractResourceType
|
|||
->add('type', ZoneTypeChoiceType::class, [
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('priority', IntegerType::class, [
|
||||
'label' => 'sylius.form.zone.priority',
|
||||
'required' => true,
|
||||
])
|
||||
;
|
||||
|
||||
if (!empty($this->scopeChoices)) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class ZoneRepository extends EntityRepository implements ZoneRepositoryInterface
|
|||
->andWhere($queryBuilder->expr()->eq('o.type', ':type'))
|
||||
->setParameter('type', $type)
|
||||
->setMaxResults(1)
|
||||
->addOrderBy('o.priority', 'DESC')
|
||||
;
|
||||
|
||||
return $queryBuilder->getQuery()->getOneOrNullResult();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@
|
|||
<field name="type" column="type" type="string" length="8" />
|
||||
<field name="scope" column="scope" type="string" nullable="true"/>
|
||||
|
||||
<field name="priority" type="integer">
|
||||
<options>
|
||||
<option name="default">0</option>
|
||||
</options>
|
||||
</field>
|
||||
|
||||
<one-to-many field="members" target-entity="Sylius\Component\Addressing\Model\ZoneMemberInterface" mapped-by="belongsTo" orphan-removal="true">
|
||||
<cascade>
|
||||
<cascade-all/>
|
||||
|
|
|
|||
|
|
@ -30,5 +30,6 @@ sylius:
|
|||
zone: Area
|
||||
scope: Gebied
|
||||
select: Kies
|
||||
priority: Prioriteit
|
||||
zone_member:
|
||||
select: Kies
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: الكل
|
||||
select: اختر
|
||||
select_scope: اختر النطاق
|
||||
priority: الأولوية
|
||||
zone_member:
|
||||
select: اختر
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Усе
|
||||
select: Выберыце
|
||||
select_scope: Выбярыце сферу ўжывання
|
||||
priority: Прыярытэт
|
||||
zone_member:
|
||||
select: Выберыце
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Всички
|
||||
select: Изберете
|
||||
select_scope: Изберете обхват
|
||||
priority: Приоритет
|
||||
zone_member:
|
||||
select: Изберете
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Totes
|
||||
select: Tria
|
||||
select_scope: Seleccioneu l'àmbit
|
||||
priority: Prioritat
|
||||
zone_member:
|
||||
select: Tria
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: 'Všechny'
|
||||
select: 'Vybrat'
|
||||
priority: 'Priorita'
|
||||
zone_member:
|
||||
select: 'Vybrat'
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Alle
|
||||
select: Vælg
|
||||
select_scope: Vælg omfang
|
||||
priority: Prioritet
|
||||
zone_member:
|
||||
select: Vælg
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: 'Alle'
|
||||
select: 'Zone auswählen'
|
||||
select_scope: 'Geltungsbereich auswählen'
|
||||
priority: 'Priorität'
|
||||
zone_member:
|
||||
select: 'Mitglied auswählen'
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: 'Alle'
|
||||
select: 'Auswählen'
|
||||
priority: 'Priorität'
|
||||
zone_member:
|
||||
select: 'Auswählen'
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: 'Alle'
|
||||
select: 'Auswählen'
|
||||
priority: 'Priorität'
|
||||
zone_member:
|
||||
select: 'Auswählen'
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Όλα
|
||||
select: Επιλέξτε
|
||||
select_scope: Επιλέξτε πεδίο
|
||||
priority: Προτεραιότητα
|
||||
zone_member:
|
||||
select: Επιλέξτε
|
||||
|
|
|
|||
|
|
@ -34,5 +34,6 @@ sylius:
|
|||
scopes:
|
||||
all: All
|
||||
select: Select
|
||||
priority: Priority
|
||||
zone_member:
|
||||
select: Select
|
||||
|
|
|
|||
|
|
@ -36,5 +36,6 @@ sylius:
|
|||
scopes:
|
||||
all: All
|
||||
select: Choose
|
||||
priority: Priority
|
||||
zone_member:
|
||||
select: Choose
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Todos
|
||||
select: Seleccionar
|
||||
select_scope: Seleccione ámbito
|
||||
priority: Prioridad
|
||||
zone_member:
|
||||
select: Selección
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Kõik
|
||||
select: Vali
|
||||
select_scope: Valige
|
||||
priority: Prioriteet
|
||||
zone_member:
|
||||
select: Vali
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: همه
|
||||
select: انتخاب
|
||||
select_scope: انتخاب محدوده
|
||||
priority: اولویت
|
||||
zone_member:
|
||||
select: انتخاب
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Kaikki
|
||||
select: Valitse
|
||||
select_scope: Valitse alue
|
||||
priority: Prioriteetti
|
||||
zone_member:
|
||||
select: Valitse
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: Tous
|
||||
select: Sélectionner
|
||||
priority: Priorité
|
||||
zone_member:
|
||||
select: Sélectionner
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Tout
|
||||
select: Sélectionner
|
||||
select_scope: Sélectionner la portée
|
||||
priority: Priorité
|
||||
zone_member:
|
||||
select: Sélectionner
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Tous
|
||||
select: Choisir
|
||||
select_scope: Choisissez la portée
|
||||
priority: Priorité
|
||||
zone_member:
|
||||
select: Choisir
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: הכל
|
||||
select: בחירה
|
||||
select_scope: בחרו תחום
|
||||
priority: עדיפות
|
||||
zone_member:
|
||||
select: בחירה
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: 'सभी'
|
||||
select: 'चयन करें'
|
||||
priority: 'प्राथमिकता'
|
||||
zone_member:
|
||||
select: 'चयन करें'
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Sve
|
||||
select: Odaberite
|
||||
select_scope: Odaberite doseg
|
||||
priority: Prioritet
|
||||
zone_member:
|
||||
select: Odaberite
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Mind
|
||||
select: Válassza ki
|
||||
select_scope: Hatókör kiválasztása
|
||||
priority: Prioritás
|
||||
zone_member:
|
||||
select: Válassza ki
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Semua
|
||||
select: Pilih
|
||||
select_scope: Pilih lingkup
|
||||
priority: Prioritas
|
||||
zone_member:
|
||||
select: Pilih
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Allt
|
||||
select: Vallisti
|
||||
select_scope: Velja umfang
|
||||
priority: Forgangur
|
||||
zone_member:
|
||||
select: Vallisti
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: 'Tutti'
|
||||
select: 'Seleziona'
|
||||
priority: 'Priorità'
|
||||
zone_member:
|
||||
select: 'Seleziona'
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ sylius:
|
|||
scopes:
|
||||
all: 'すべて'
|
||||
select: '選択'
|
||||
priority: '優先度'
|
||||
zone_member:
|
||||
select: '選択'
|
||||
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: '전체'
|
||||
select: '선택'
|
||||
priority: '우선순위'
|
||||
zone_member:
|
||||
select: '선택'
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Visi
|
||||
select: Pasirinkite
|
||||
select_scope: Pasirinkite sritį
|
||||
priority: Prioritetas
|
||||
zone_member:
|
||||
select: Pasirinkite
|
||||
|
|
|
|||
|
|
@ -34,3 +34,4 @@ sylius:
|
|||
scopes:
|
||||
all: Visas
|
||||
select_scope: Izvēlēties tvērumu
|
||||
priority: Prioritāte
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Сите
|
||||
select: Изберете
|
||||
select_scope: Изберете опсег
|
||||
priority: Приоритет
|
||||
zone_member:
|
||||
select: Изберете
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Бүгд
|
||||
select: Сонгох
|
||||
select_scope: Бүс сонгох
|
||||
priority: Эрэмбэ
|
||||
zone_member:
|
||||
select: Сонгох
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Semua
|
||||
select: Pilih
|
||||
select_scope: Pilih skop
|
||||
priority: Keutamaan
|
||||
zone_member:
|
||||
select: Pilih
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: 'Alle'
|
||||
select: 'Selecteer'
|
||||
priority: 'Prioriteit'
|
||||
zone_member:
|
||||
select: 'Selecteer'
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ sylius:
|
|||
scopes:
|
||||
all: 'Alle'
|
||||
select: 'Velg'
|
||||
priority: 'Prioritet'
|
||||
zone_member:
|
||||
select: 'Velg'
|
||||
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: Wszystko
|
||||
select: Wybierz
|
||||
priority: Priorytet
|
||||
zone_member:
|
||||
select: Wybierz
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ sylius:
|
|||
scopes:
|
||||
all: 'Todos'
|
||||
select: 'Selecionar'
|
||||
priority: 'Prioridade'
|
||||
zone_member:
|
||||
select: 'Selecionar'
|
||||
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Todos(as)
|
||||
select: Selecione
|
||||
select_scope: Selecione o escopo
|
||||
priority: Prioridade
|
||||
zone_member:
|
||||
select: Selecione
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: 'Toate'
|
||||
select: 'Selectați'
|
||||
priority: 'Prioritate'
|
||||
zone_member:
|
||||
select: 'Selectați'
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Все
|
||||
select: Выбрать
|
||||
select_scope: Выберите область
|
||||
priority: Приоритет
|
||||
zone_member:
|
||||
select: Выбрать
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: 'Všetko'
|
||||
select: 'Vybrať'
|
||||
priority: 'Priorita'
|
||||
zone_member:
|
||||
select: 'Vybrať'
|
||||
|
|
|
|||
|
|
@ -36,5 +36,6 @@ sylius:
|
|||
scope: Področje
|
||||
select: Izberite
|
||||
select_scope: Izberite področje
|
||||
priority: Prednost
|
||||
zone_member:
|
||||
select: Izberite
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
all: Të gjitha
|
||||
select: Zgjidh
|
||||
select_scope: Zgjidh zonën
|
||||
priority: Prioriteti
|
||||
zone_member:
|
||||
select: Zgjidh
|
||||
|
|
|
|||
|
|
@ -35,5 +35,6 @@ sylius:
|
|||
scope: Обухват
|
||||
select: Изаберите
|
||||
select_scope: Изабери обухват
|
||||
priority: Приоритет
|
||||
zone_member:
|
||||
select: Изаберите
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Sve
|
||||
select: Izaberite
|
||||
select_scope: Izaberite adresu isporuke
|
||||
priority: Prioritet
|
||||
zone_member:
|
||||
select: Izaberite
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: Alla
|
||||
select: Välj
|
||||
priority: Prioritet
|
||||
zone_member:
|
||||
select: Välj
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: ทั้งหมด
|
||||
select: เลือก
|
||||
select_scope: เลือกขอบเขต
|
||||
priority: ลำดับความสำคัญ
|
||||
zone_member:
|
||||
select: เลือก
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Tümü
|
||||
select: Seçiniz
|
||||
select_scope: Ölçek seçiniz
|
||||
priority: Öncelik
|
||||
zone_member:
|
||||
select: Seçiniz
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Всі(-е)
|
||||
select: Виберіть
|
||||
select_scope: Виберіть покриття
|
||||
priority: Пріоритет
|
||||
zone_member:
|
||||
select: Виберіть
|
||||
|
|
|
|||
|
|
@ -38,5 +38,6 @@ sylius:
|
|||
all: Tất cả
|
||||
select: Chọn
|
||||
select_scope: Chọn phạm vi
|
||||
priority: Ưu tiên
|
||||
zone_member:
|
||||
select: Chọn
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
scopes:
|
||||
all: '所有'
|
||||
select: '选择'
|
||||
priority: '优先级'
|
||||
zone_member:
|
||||
select: '选择'
|
||||
|
|
|
|||
|
|
@ -37,5 +37,6 @@ sylius:
|
|||
all: 全部
|
||||
select: 請選擇
|
||||
select_scope: 選擇範圍
|
||||
priority: 優先順序
|
||||
zone_member:
|
||||
select: 請選擇
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@
|
|||
"stof/doctrine-extensions-bundle": "^1.12",
|
||||
"sylius/addressing": "^2.0",
|
||||
"sylius/resource-bundle": "^1.12",
|
||||
"symfony/framework-bundle": "^6.4.1 || ^7.2",
|
||||
"symfony/intl": "^6.4 || ^7.2"
|
||||
"symfony/framework-bundle": "^6.4.1 || ^7.4",
|
||||
"symfony/intl": "^6.4 || ^7.4"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpdoc-parser": ">= 2.0"
|
||||
|
|
@ -39,14 +39,13 @@
|
|||
"require-dev": {
|
||||
"doctrine/data-fixtures": "^1.7",
|
||||
"doctrine/doctrine-bundle": "^2.13",
|
||||
"doctrine/orm": "^2.18 || ^3.3",
|
||||
"doctrine/orm": "^2.18 || ^3.5",
|
||||
"matthiasnoback/symfony-dependency-injection-test": "^6.0",
|
||||
"phpspec/prophecy-phpunit": "^2.2",
|
||||
"phpunit/phpunit": "^11.5",
|
||||
"symfony/browser-kit": "^6.4 || ^7.2",
|
||||
"symfony/dependency-injection": "^6.4.1 || ^7.2",
|
||||
"symfony/form": "^6.4.1 || ^7.2",
|
||||
"symfony/validator": "^6.4 || ^7.2",
|
||||
"symfony/browser-kit": "^6.4 || ^7.4",
|
||||
"symfony/dependency-injection": "^6.4.1 || ^7.4",
|
||||
"symfony/form": "^6.4.1 || ^7.4",
|
||||
"symfony/validator": "^6.4 || ^7.4",
|
||||
"theofidry/alice-data-fixtures": "^1.7"
|
||||
},
|
||||
"config": {
|
||||
|
|
@ -56,10 +55,10 @@
|
|||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.0-dev"
|
||||
"dev-main": "2.2-dev"
|
||||
},
|
||||
"symfony": {
|
||||
"require": "^7.2"
|
||||
"require": "^7.4"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -15,9 +15,7 @@ namespace Tests\Sylius\Bundle\AddressingBundle\Form\Type;
|
|||
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Prophecy\Prophecy\ProphecyInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sylius\Bundle\AddressingBundle\Form\Type\CountryChoiceType;
|
||||
use Sylius\Component\Addressing\Model\CountryInterface;
|
||||
use Sylius\Resource\Doctrine\Persistence\RepositoryInterface;
|
||||
|
|
@ -27,48 +25,37 @@ use Symfony\Component\Form\Test\TypeTestCase;
|
|||
|
||||
final class CountryChoiceTypeTest extends TypeTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
private MockObject&RepositoryInterface $countryRepository;
|
||||
|
||||
private ObjectProphecy $countryRepository;
|
||||
private CountryInterface&MockObject $france;
|
||||
|
||||
/** @var ProphecyInterface|CountryInterface */
|
||||
private $france;
|
||||
private CountryInterface&MockObject $poland;
|
||||
|
||||
/** @var ProphecyInterface|CountryInterface */
|
||||
private $poland;
|
||||
|
||||
/** @var ProphecyInterface|CountryInterface */
|
||||
private $austria;
|
||||
private CountryInterface&MockObject $austria;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->countryRepository = $this->prophesize(RepositoryInterface::class);
|
||||
$this->countryRepository = $this->createMock(RepositoryInterface::class);
|
||||
|
||||
/** @var ProphecyInterface|CountryInterface $france */
|
||||
$france = $this->prophesize(CountryInterface::class);
|
||||
$france->getCode()->willReturn('FR');
|
||||
$france->getName()->willReturn('France');
|
||||
$this->france = $france;
|
||||
$this->france = $this->createMock(CountryInterface::class);
|
||||
$this->france->method('getCode')->willReturn('FR');
|
||||
$this->france->method('getName')->willReturn('France');
|
||||
|
||||
/** @var ProphecyInterface|CountryInterface $poland */
|
||||
$poland = $this->prophesize(CountryInterface::class);
|
||||
$poland->getCode()->willReturn('PL');
|
||||
$poland->getName()->willReturn('Poland');
|
||||
$this->poland = $poland;
|
||||
$this->poland = $this->createMock(CountryInterface::class);
|
||||
$this->poland->method('getCode')->willReturn('PL');
|
||||
$this->poland->method('getName')->willReturn('Poland');
|
||||
|
||||
/** @var ProphecyInterface|CountryInterface $austria */
|
||||
$austria = $this->prophesize(CountryInterface::class);
|
||||
$austria->getCode()->willReturn('AT');
|
||||
$austria->getName()->willReturn('Austria');
|
||||
$austria->isEnabled()->willReturn(false);
|
||||
$this->austria = $austria;
|
||||
$this->austria = $this->createMock(CountryInterface::class);
|
||||
$this->austria->method('getCode')->willReturn('AT');
|
||||
$this->austria->method('getName')->willReturn('Austria');
|
||||
$this->austria->method('isEnabled')->willReturn(false);
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
protected function getExtensions(): array
|
||||
{
|
||||
$type = new CountryChoiceType($this->countryRepository->reveal());
|
||||
$type = new CountryChoiceType($this->countryRepository);
|
||||
|
||||
return [
|
||||
new PreloadedExtension([$type], []),
|
||||
|
|
@ -78,10 +65,14 @@ final class CountryChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_only_enabled_countries_by_default(): void
|
||||
{
|
||||
$this->countryRepository->findBy(['enabled' => true])->willReturn([
|
||||
$this->france->reveal(),
|
||||
$this->poland->reveal(),
|
||||
]);
|
||||
$this->countryRepository
|
||||
->expects($this->once())
|
||||
->method('findBy')
|
||||
->with(['enabled' => true])
|
||||
->willReturn([
|
||||
$this->france,
|
||||
$this->poland,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['France', 'Poland']);
|
||||
}
|
||||
|
|
@ -89,11 +80,14 @@ final class CountryChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_all_countries_when_option_enabled_is_false(): void
|
||||
{
|
||||
$this->countryRepository->findAll()->willReturn([
|
||||
$this->france->reveal(),
|
||||
$this->poland->reveal(),
|
||||
$this->austria->reveal(),
|
||||
]);
|
||||
$this->countryRepository
|
||||
->expects($this->once())
|
||||
->method('findAll')
|
||||
->willReturn([
|
||||
$this->france,
|
||||
$this->poland,
|
||||
$this->austria,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['Austria', 'France', 'Poland'], ['enabled' => false]);
|
||||
}
|
||||
|
|
@ -101,10 +95,14 @@ final class CountryChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_enabled_countries_in_an_alphabetical_order(): void
|
||||
{
|
||||
$this->countryRepository->findBy(['enabled' => true])->willReturn([
|
||||
$this->poland->reveal(),
|
||||
$this->france->reveal(),
|
||||
]);
|
||||
$this->countryRepository
|
||||
->expects($this->once())
|
||||
->method('findBy')
|
||||
->with(['enabled' => true])
|
||||
->willReturn([
|
||||
$this->poland,
|
||||
$this->france,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['France', 'Poland']);
|
||||
}
|
||||
|
|
@ -112,11 +110,14 @@ final class CountryChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_all_countries_in_an_alphabetical_order(): void
|
||||
{
|
||||
$this->countryRepository->findAll()->willReturn([
|
||||
$this->poland->reveal(),
|
||||
$this->france->reveal(),
|
||||
$this->austria->reveal(),
|
||||
]);
|
||||
$this->countryRepository
|
||||
->expects($this->once())
|
||||
->method('findAll')
|
||||
->willReturn([
|
||||
$this->poland,
|
||||
$this->france,
|
||||
$this->austria,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['Austria', 'France', 'Poland'], ['enabled' => false]);
|
||||
}
|
||||
|
|
@ -124,11 +125,14 @@ final class CountryChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_all_filtered_out_countries(): void
|
||||
{
|
||||
$this->countryRepository->findAll()->willReturn([
|
||||
$this->france->reveal(),
|
||||
$this->poland->reveal(),
|
||||
$this->austria->reveal(),
|
||||
]);
|
||||
$this->countryRepository
|
||||
->expects($this->once())
|
||||
->method('findAll')
|
||||
->willReturn([
|
||||
$this->france,
|
||||
$this->poland,
|
||||
$this->austria,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['Poland'], ['choice_filter' => static fn (?CountryInterface $country): bool => $country !== null && $country->getName() === 'Poland', 'enabled' => false]);
|
||||
}
|
||||
|
|
@ -136,10 +140,14 @@ final class CountryChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_enabled_filtered_out_countries(): void
|
||||
{
|
||||
$this->countryRepository->findBy(['enabled' => true])->willReturn([
|
||||
$this->france->reveal(),
|
||||
$this->poland->reveal(),
|
||||
]);
|
||||
$this->countryRepository
|
||||
->expects($this->once())
|
||||
->method('findBy')
|
||||
->with(['enabled' => true])
|
||||
->willReturn([
|
||||
$this->france,
|
||||
$this->poland,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['Poland'], ['choice_filter' => static fn (?CountryInterface $country): bool => $country !== null && $country->getName() === 'Poland']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ namespace Tests\Sylius\Bundle\AddressingBundle\Form\Type;
|
|||
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Prophecy\Prophecy\ProphecyInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sylius\Bundle\AddressingBundle\Form\Type\ZoneChoiceType;
|
||||
use Sylius\Component\Addressing\Model\Scope as AddressingScope;
|
||||
use Sylius\Component\Addressing\Model\ZoneInterface;
|
||||
|
|
@ -28,39 +26,31 @@ use Symfony\Component\Form\Test\TypeTestCase;
|
|||
|
||||
final class ZoneChoiceTypeTest extends TypeTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
private MockObject&RepositoryInterface $zoneRepository;
|
||||
|
||||
private ObjectProphecy $zoneRepository;
|
||||
private MockObject&ZoneInterface $zoneAllScopes;
|
||||
|
||||
/** @var ProphecyInterface|ZoneInterface */
|
||||
private $zoneAllScopes;
|
||||
private MockObject&ZoneInterface $zoneTaxScope;
|
||||
|
||||
/** @var ProphecyInterface|ZoneInterface */
|
||||
private $zoneTaxScope;
|
||||
|
||||
/** @var ProphecyInterface|ZoneInterface */
|
||||
private $zoneShippingScope;
|
||||
private MockObject&ZoneInterface $zoneShippingScope;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->zoneRepository = $this->prophesize(RepositoryInterface::class);
|
||||
$this->zoneRepository = $this->createMock(RepositoryInterface::class);
|
||||
|
||||
/** @var ProphecyInterface|ZoneInterface $zoneAllScopes */
|
||||
$zoneAllScopes = $this->prophesize(ZoneInterface::class);
|
||||
$zoneAllScopes->getCode()->willReturn('all');
|
||||
$zoneAllScopes->getName()->willReturn('All');
|
||||
$zoneAllScopes = $this->createMock(ZoneInterface::class);
|
||||
$zoneAllScopes->method('getCode')->willReturn('all');
|
||||
$zoneAllScopes->method('getName')->willReturn('All');
|
||||
$this->zoneAllScopes = $zoneAllScopes;
|
||||
|
||||
/** @var ProphecyInterface|ZoneInterface $zoneTaxScope */
|
||||
$zoneTaxScope = $this->prophesize(ZoneInterface::class);
|
||||
$zoneTaxScope->getCode()->willReturn('tax');
|
||||
$zoneTaxScope->getName()->willReturn('Tax');
|
||||
$zoneTaxScope = $this->createMock(ZoneInterface::class);
|
||||
$zoneTaxScope->method('getCode')->willReturn('tax');
|
||||
$zoneTaxScope->method('getName')->willReturn('Tax');
|
||||
$this->zoneTaxScope = $zoneTaxScope;
|
||||
|
||||
/** @var ProphecyInterface|ZoneInterface $zoneShippingScope */
|
||||
$zoneShippingScope = $this->prophesize(ZoneInterface::class);
|
||||
$zoneShippingScope->getCode()->willReturn('shipping');
|
||||
$zoneShippingScope->getName()->willReturn('Shipping');
|
||||
$zoneShippingScope = $this->createMock(ZoneInterface::class);
|
||||
$zoneShippingScope->method('getCode')->willReturn('shipping');
|
||||
$zoneShippingScope->method('getName')->willReturn('Shipping');
|
||||
$this->zoneShippingScope = $zoneShippingScope;
|
||||
|
||||
parent::setUp();
|
||||
|
|
@ -74,7 +64,7 @@ final class ZoneChoiceTypeTest extends TypeTestCase
|
|||
'shipping' => 'Shipping',
|
||||
];
|
||||
|
||||
$type = new ZoneChoiceType($this->zoneRepository->reveal(), $scopeTypes);
|
||||
$type = new ZoneChoiceType($this->zoneRepository, $scopeTypes);
|
||||
|
||||
return [
|
||||
new PreloadedExtension([$type], []),
|
||||
|
|
@ -84,10 +74,10 @@ final class ZoneChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_all_scopes_by_default(): void
|
||||
{
|
||||
$this->zoneRepository->findBy([])->willReturn([
|
||||
$this->zoneAllScopes->reveal(),
|
||||
$this->zoneTaxScope->reveal(),
|
||||
$this->zoneShippingScope->reveal(),
|
||||
$this->zoneRepository->method('findBy')->with([])->willReturn([
|
||||
$this->zoneAllScopes,
|
||||
$this->zoneTaxScope,
|
||||
$this->zoneShippingScope,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['All', 'Tax', 'Shipping']);
|
||||
|
|
@ -96,10 +86,10 @@ final class ZoneChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_all_scopes_when_zone_scope_set_to_all(): void
|
||||
{
|
||||
$this->zoneRepository->findBy([])->willReturn([
|
||||
$this->zoneAllScopes->reveal(),
|
||||
$this->zoneTaxScope->reveal(),
|
||||
$this->zoneShippingScope->reveal(),
|
||||
$this->zoneRepository->method('findBy')->with([])->willReturn([
|
||||
$this->zoneAllScopes,
|
||||
$this->zoneTaxScope,
|
||||
$this->zoneShippingScope,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['All', 'Tax', 'Shipping'], ['zone_scope' => AddressingScope::ALL]);
|
||||
|
|
@ -108,9 +98,9 @@ final class ZoneChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_tax_scopes_when_zone_scope_set_to_tax(): void
|
||||
{
|
||||
$this->zoneRepository->findBy(['scope' => ['tax', AddressingScope::ALL]])->willReturn([
|
||||
$this->zoneAllScopes->reveal(),
|
||||
$this->zoneTaxScope->reveal(),
|
||||
$this->zoneRepository->method('findBy')->with(['scope' => ['tax', AddressingScope::ALL]])->willReturn([
|
||||
$this->zoneAllScopes,
|
||||
$this->zoneTaxScope,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['All', 'Tax'], ['zone_scope' => 'tax']);
|
||||
|
|
@ -119,9 +109,9 @@ final class ZoneChoiceTypeTest extends TypeTestCase
|
|||
#[Test]
|
||||
public function it_returns_shipping_scopes_when_zone_scope_set_to_shipping(): void
|
||||
{
|
||||
$this->zoneRepository->findBy(['scope' => ['shipping', AddressingScope::ALL]])->willReturn([
|
||||
$this->zoneAllScopes->reveal(),
|
||||
$this->zoneShippingScope->reveal(),
|
||||
$this->zoneRepository->method('findBy')->with(['scope' => ['shipping', AddressingScope::ALL]])->willReturn([
|
||||
$this->zoneAllScopes,
|
||||
$this->zoneShippingScope,
|
||||
]);
|
||||
|
||||
$this->assertChoicesLabels(['All', 'Shipping'], ['zone_scope' => 'shipping']);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ final class RemoveAvatarAction
|
|||
public function __construct(
|
||||
private AvatarImageRepositoryInterface $avatarRepository,
|
||||
private RouterInterface $router,
|
||||
private CsrfTokenManagerInterface $csrfTokenManager,
|
||||
private ?CsrfTokenManagerInterface $csrfTokenManager,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ final class RemoveAvatarAction
|
|||
{
|
||||
$userId = $request->attributes->get('id', '');
|
||||
|
||||
if (!$this->csrfTokenManager->isTokenValid(
|
||||
if ($this->csrfTokenManager && !$this->csrfTokenManager->isTokenValid(
|
||||
new CsrfToken($userId, (string) $request->query->get('_csrf_token', '')),
|
||||
)) {
|
||||
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ final readonly class ResendOrderConfirmationEmailAction
|
|||
public function __construct(
|
||||
private OrderRepositoryInterface $orderRepository,
|
||||
private ResendOrderConfirmationEmailDispatcherInterface $resendOrderConfirmationEmailDispatcher,
|
||||
private CsrfTokenManagerInterface $csrfTokenManager,
|
||||
private ?CsrfTokenManagerInterface $csrfTokenManager,
|
||||
private RequestStack $requestStack,
|
||||
private RouterInterface $router,
|
||||
) {
|
||||
|
|
@ -42,7 +42,7 @@ final readonly class ResendOrderConfirmationEmailAction
|
|||
{
|
||||
$orderId = $request->attributes->get('id', '');
|
||||
|
||||
if (!$this->csrfTokenManager->isTokenValid(
|
||||
if ($this->csrfTokenManager && !$this->csrfTokenManager->isTokenValid(
|
||||
new CsrfToken($orderId, (string) $request->query->get('_csrf_token', '')),
|
||||
)) {
|
||||
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ final readonly class ResendShipmentConfirmationEmailAction
|
|||
public function __construct(
|
||||
private ShipmentRepositoryInterface $shipmentRepository,
|
||||
private ResendShipmentConfirmationEmailDispatcherInterface $resendShipmentConfirmationDispatcher,
|
||||
private CsrfTokenManagerInterface $csrfTokenManager,
|
||||
private ?CsrfTokenManagerInterface $csrfTokenManager,
|
||||
private RequestStack $requestStack,
|
||||
) {
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ final readonly class ResendShipmentConfirmationEmailAction
|
|||
{
|
||||
$shipmentId = $request->attributes->get('id', '');
|
||||
|
||||
if (!$this->csrfTokenManager->isTokenValid(
|
||||
if ($this->csrfTokenManager && !$this->csrfTokenManager->isTokenValid(
|
||||
new CsrfToken($shipmentId, (string) $request->query->get('_csrf_token', '')),
|
||||
)) {
|
||||
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Console\Command;
|
||||
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'sylius:admin-user:delete',
|
||||
description: 'Deletes an admin user account by the given email',
|
||||
)]
|
||||
final class DeleteAdminUserCommand extends Command
|
||||
{
|
||||
protected SymfonyStyle $io;
|
||||
|
||||
/** @param UserRepositoryInterface<AdminUserInterface> $userRepository */
|
||||
public function __construct(
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Deletes an admin user')
|
||||
->addArgument('email', InputArgument::REQUIRED, 'The email of the admin user to delete');
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->io->title('Delete admin user');
|
||||
|
||||
$email = $input->getArgument('email');
|
||||
|
||||
$adminUser = $this->userRepository->findOneBy(['email' => $email]);
|
||||
if ($adminUser === null) {
|
||||
$this->io->error(sprintf('Admin Account with the email "%s" does not exist', $email));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($input->isInteractive() && !$this->io->confirm(
|
||||
sprintf('Are you sure you want to delete the admin user "%s"?', $email),
|
||||
false,
|
||||
)) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->userRepository->remove($adminUser);
|
||||
$this->io->success(sprintf('Admin Account with the email "%s" has been deleted successfully', $email));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Console\Command;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'sylius:admin-user:list',
|
||||
description: 'Displays a list of all admin users along with their details.',
|
||||
)]
|
||||
final class ListAdminUsersCommand extends Command
|
||||
{
|
||||
protected SymfonyStyle $io;
|
||||
|
||||
/** @param UserRepositoryInterface<AdminUserInterface> $adminUserRepository */
|
||||
public function __construct(
|
||||
private readonly UserRepositoryInterface $adminUserRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setAliases(['sylius:admin-users:list']);
|
||||
$this->addOption(
|
||||
'search',
|
||||
's',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Filter users based on a search term. If not provided, all users will be displayed.',
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->io->title('List available admin users');
|
||||
|
||||
$searchTerm = $input->getOption('search');
|
||||
if ($searchTerm === null || $searchTerm === '') {
|
||||
$this->renderUsersTable($output, $this->adminUserRepository->findAll());
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$users = $this->searchUsers($searchTerm);
|
||||
if (empty($users)) {
|
||||
$this->io->warning(sprintf('No users found matching "%s"', $searchTerm));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->io->success(sprintf('Found %d user(s) matching "%s"', count($users), $searchTerm));
|
||||
$this->renderUsersTable($output, $users);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<AdminUserInterface>
|
||||
*/
|
||||
private function searchUsers(string $searchTerm): array
|
||||
{
|
||||
$criteria = '%' . mb_strtolower($searchTerm) . '%';
|
||||
|
||||
return $this->entityManager->createQueryBuilder()
|
||||
->from(AdminUserInterface::class, 'u')
|
||||
->select('u')
|
||||
->where('LOWER(u.email) LIKE :criteria
|
||||
OR LOWER(u.username) LIKE :criteria
|
||||
OR LOWER(u.firstName) LIKE :criteria
|
||||
OR LOWER(u.lastName) LIKE :criteria')
|
||||
->setParameter('criteria', $criteria)
|
||||
->orderBy('u.createdAt', 'DESC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<AdminUserInterface> $adminUsers
|
||||
*/
|
||||
private function renderUsersTable(OutputInterface $output, array $adminUsers): void
|
||||
{
|
||||
$table = new Table($output);
|
||||
$table->setHeaders([
|
||||
'ID', 'E-Mail', 'Username', 'First name', 'Last name', 'Locale', 'Enabled', 'Roles',
|
||||
]);
|
||||
|
||||
foreach ($adminUsers as $adminUser) {
|
||||
$table->addRow([
|
||||
$adminUser->getId(),
|
||||
$adminUser->getEmail(),
|
||||
$adminUser->getUsername(),
|
||||
$adminUser->getFirstName() ?? '',
|
||||
$adminUser->getLastName() ?? '',
|
||||
$adminUser->getLocaleCode(),
|
||||
$adminUser->isEnabled() ? '✔' : '✘',
|
||||
$this->formatRoles($adminUser->getRoles()),
|
||||
]);
|
||||
}
|
||||
|
||||
$table->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $roles
|
||||
*/
|
||||
private function formatRoles(array $roles): string
|
||||
{
|
||||
if ($roles === []) {
|
||||
return 'No roles';
|
||||
}
|
||||
|
||||
$formatted = array_map(
|
||||
static fn (string $role): string => str_replace('ROLE_', '', $role),
|
||||
$roles,
|
||||
);
|
||||
|
||||
return implode(",\n", $formatted);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue