├── etc
├── build
│ └── .gitignore
└── fixtures
│ └── vendor
│ └── logo
│ ├── finley.png
│ ├── lenora.png
│ ├── marlow.png
│ ├── modella.png
│ ├── orchid.png
│ ├── zaneya.png
│ ├── style_mart.png
│ └── sunbeam_market.png
├── tests
├── Unit
│ ├── .gitignore
│ ├── OdiseoSyliusVendorPluginTest.php
│ ├── Repository
│ │ ├── VendorRepositoryTest.php
│ │ └── VendorEmailRepositoryTest.php
│ ├── Twig
│ │ ├── VendorExtensionTest.php
│ │ ├── Component
│ │ │ └── VendorComponentTest.php
│ │ └── VendorRuntimeTest.php
│ ├── Entity
│ │ ├── VendorTraitTest.php
│ │ ├── VendorsTraitTest.php
│ │ ├── VendorEmailTest.php
│ │ └── VendorTranslationTest.php
│ ├── Fixture
│ │ └── VendorFixtureTest.php
│ ├── Form
│ │ ├── Type
│ │ │ ├── VendorTranslationTypeTest.php
│ │ │ ├── VendorEmailTypeTest.php
│ │ │ ├── VendorTypeTest.php
│ │ │ └── VendorChoiceTypeTest.php
│ │ └── Extension
│ │ │ └── ProductTypeExtensionTest.php
│ ├── EventListener
│ │ └── VendorLogoUploadListenerTest.php
│ ├── Event
│ │ └── VendorFormMenuBuilderEventTest.php
│ ├── Uploader
│ │ └── VendorLogoUploaderTest.php
│ └── Menu
│ │ └── AdminMenuListenerTest.php
├── Functional
│ └── .gitignore
├── Integration
│ └── .gitignore
├── TestApplication
│ ├── src
│ │ ├── Entity
│ │ │ ├── .gitkeep
│ │ │ └── Product.php
│ │ └── Repository
│ │ │ └── ProductRepository.php
│ ├── templates
│ │ └── .gitignore
│ ├── .env.test
│ ├── config
│ │ ├── bundles.php
│ │ ├── routes.yaml
│ │ ├── services_test.php
│ │ └── config.yaml
│ └── .env
└── Behat
│ ├── Resources
│ ├── suites.yml
│ ├── images
│ │ └── logo_odiseo.png
│ ├── services
│ │ ├── pages
│ │ │ ├── shop.xml
│ │ │ ├── admin.xml
│ │ │ ├── shop
│ │ │ │ └── vendor.xml
│ │ │ └── admin
│ │ │ │ └── vendor.xml
│ │ ├── pages.xml
│ │ └── contexts.xml
│ ├── services.xml
│ └── suites
│ │ └── ui
│ │ ├── shop_vendors.yaml
│ │ └── managing_vendors.yaml
│ ├── Behaviour
│ ├── ContainsErrorInterface.php
│ └── ContainsErrorTrait.php
│ ├── Page
│ ├── Admin
│ │ └── Vendor
│ │ │ ├── IndexPageInterface.php
│ │ │ ├── IndexPage.php
│ │ │ ├── CreatePageInterface.php
│ │ │ ├── UpdatePageInterface.php
│ │ │ ├── CreatePage.php
│ │ │ └── UpdatePage.php
│ └── Shop
│ │ └── Vendor
│ │ ├── ShowPageInterface.php
│ │ ├── IndexPageInterface.php
│ │ ├── ShowPage.php
│ │ └── IndexPage.php
│ └── Context
│ ├── Transform
│ └── VendorContext.php
│ └── Ui
│ └── Shop
│ └── VendorContext.php
├── AGENTS.md
├── .github
├── CODEOWNERS
└── dependabot.yml
├── assets
├── shop
│ ├── entrypoint.js
│ └── controllers.json
└── admin
│ ├── entrypoint.js
│ └── controllers.json
├── config
├── services.yaml
├── routes
│ ├── admin.yaml
│ ├── shop.yaml
│ ├── admin
│ │ └── vendor.yaml
│ └── shop
│ │ └── vendor.yaml
├── services
│ ├── controller.yaml
│ ├── uploader.yaml
│ ├── sitemap.yaml
│ ├── twig.yaml
│ ├── twig_component.yaml
│ ├── fixture.yaml
│ ├── event_listener.yaml
│ └── form.yaml
├── validation
│ ├── VendorTranslation.yaml
│ ├── VendorEmail.yaml
│ └── Vendor.yaml
├── app
│ ├── twig_hooks
│ │ ├── admin
│ │ │ └── base.yaml
│ │ └── shop
│ │ │ ├── base.yaml
│ │ │ └── vendor
│ │ │ ├── index.yaml
│ │ │ └── show.yaml
│ ├── resources
│ │ ├── vendor_email.yaml
│ │ └── vendor.yaml
│ └── grids
│ │ ├── shop
│ │ ├── vendor.yaml
│ │ └── vendor_product.yaml
│ │ └── admin
│ │ └── vendor.yaml
├── api_platform
│ ├── properties
│ │ ├── VendorTranslation.xml
│ │ └── Vendor.xml
│ └── resources
│ │ ├── admin
│ │ └── VendorTranslation.xml
│ │ └── shop
│ │ └── Vendor.xml
├── config.yaml
├── serialization
│ └── VendorTranslation.xml
└── doctrine
│ ├── VendorTranslation.orm.xml
│ ├── VendorEmail.orm.xml
│ └── Vendor.orm.xml
├── templates
├── shop
│ ├── vendor
│ │ ├── show
│ │ │ ├── content
│ │ │ │ ├── header.html.twig
│ │ │ │ ├── pagination.html.twig
│ │ │ │ ├── filters.html.twig
│ │ │ │ ├── header
│ │ │ │ │ ├── breadcrumbs.html.twig
│ │ │ │ │ └── vendor.html.twig
│ │ │ │ ├── products.html.twig
│ │ │ │ └── filters
│ │ │ │ │ ├── limit.html.twig
│ │ │ │ │ └── search.html.twig
│ │ │ └── content.html.twig
│ │ ├── index
│ │ │ ├── content
│ │ │ │ ├── title.html.twig
│ │ │ │ ├── pagination.html.twig
│ │ │ │ ├── breadcrumbs.html.twig
│ │ │ │ ├── filters.html.twig
│ │ │ │ ├── vendors.html.twig
│ │ │ │ └── filters
│ │ │ │ │ ├── search.html.twig
│ │ │ │ │ ├── limit.html.twig
│ │ │ │ │ └── sorting.html.twig
│ │ │ └── content.html.twig
│ │ ├── index.html.twig
│ │ ├── show.html.twig
│ │ └── common
│ │ │ └── vendor.html.twig
│ └── shared
│ │ ├── layout
│ │ ├── base
│ │ │ ├── scripts.html.twig
│ │ │ └── styles.html.twig
│ │ ├── _menuMobile.html.twig
│ │ └── _menu.html.twig
│ │ └── product
│ │ └── show
│ │ └── vendor.html.twig
└── admin
│ ├── shared
│ └── layout
│ │ └── base
│ │ ├── styles.html.twig
│ │ └── scripts.html.twig
│ └── vendor
│ ├── form
│ ├── sections.html.twig
│ ├── sections
│ │ ├── general
│ │ │ ├── name.html.twig
│ │ │ ├── slug.html.twig
│ │ │ ├── email.html.twig
│ │ │ ├── enabled.html.twig
│ │ │ └── channels.html.twig
│ │ ├── profile
│ │ │ ├── translations
│ │ │ │ └── description.html.twig
│ │ │ ├── translations.html.twig
│ │ │ └── extraEmails.html.twig
│ │ ├── media
│ │ │ └── logo.html.twig
│ │ ├── media.html.twig
│ │ ├── general.html.twig
│ │ └── profile.html.twig
│ ├── side_navigation.html.twig
│ └── side_navigation
│ │ ├── media.html.twig
│ │ ├── general.html.twig
│ │ └── profile.html.twig
│ ├── grid
│ └── field
│ │ └── image.html.twig
│ ├── form.html.twig
│ └── form_theme.html.twig
├── screenshot_1.png
├── screenshot_2.png
├── screenshot_3.png
├── screenshot_4.png
├── sylius-vendor-plugin.png
├── yarn.lock
├── src
├── Repository
│ ├── VendorEmailRepositoryInterface.php
│ ├── VendorEmailRepository.php
│ ├── VendorRepositoryInterface.php
│ ├── ProductRepositoryInterface.php
│ ├── VendorRepository.php
│ └── ProductRepositoryTrait.php
├── Entity
│ ├── VendorAwareInterface.php
│ ├── VendorEmailInterface.php
│ ├── VendorsAwareInterface.php
│ ├── VendorTranslationInterface.php
│ ├── VendorTrait.php
│ ├── VendorEmail.php
│ ├── VendorTranslation.php
│ ├── VendorsTrait.php
│ └── VendorInterface.php
├── Uploader
│ ├── VendorLogoUploaderInterface.php
│ └── VendorLogoUploader.php
├── OdiseoSyliusVendorPlugin.php
├── Twig
│ ├── VendorExtension.php
│ ├── Component
│ │ ├── VendorComponent.php
│ │ └── Vendor
│ │ │ └── FormComponent.php
│ └── VendorRuntime.php
├── Fixture
│ ├── ProductFixture.php
│ ├── VendorFixture.php
│ └── Factory
│ │ └── ProductExampleFactory.php
├── DependencyInjection
│ ├── Configuration.php
│ └── OdiseoSyliusVendorExtension.php
├── Form
│ ├── Type
│ │ ├── VendorEmailType.php
│ │ ├── VendorTranslationType.php
│ │ └── VendorChoiceType.php
│ └── Extension
│ │ └── ProductTypeExtension.php
├── Event
│ └── VendorFormMenuBuilderEvent.php
├── EventListener
│ └── VendorLogoUploadListener.php
├── Menu
│ └── AdminMenuListener.php
└── Migrations
│ └── Version20250406024705.php
├── doc
├── fixtures.md
├── customization.md
├── usage.md
├── sitemap.md
├── installation.md
└── tests.md
├── ecs.php
├── features
├── shop
│ ├── browsing_vendors.feature
│ └── displaying_vendor.feature
└── admin
│ ├── browsing_vendors.feature
│ ├── deleting_vendor.feature
│ ├── editing_vendor.feature
│ └── adding_vendor.feature
├── .gitignore
├── .dockerignore
├── translations
├── messages.en.yaml
├── messages.es.yaml
├── messages.fr.yaml
├── validators.en.yaml
├── validators.es.yaml
└── validators.fr.yaml
├── compose.yml
├── phpstan.neon
├── LICENSE
├── phpunit.xml.dist
├── .editorconfig
├── Makefile
├── COMPATIBILITY_GUIDE.md
└── compose.override.dist.yml
/etc/build/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/Unit/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AGENTS.md:
--------------------------------------------------------------------------------
1 | See @CLAUDE.md
2 |
--------------------------------------------------------------------------------
/tests/Functional/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/Integration/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @odiseoteam
2 |
--------------------------------------------------------------------------------
/tests/TestApplication/src/Entity/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/TestApplication/templates/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/shop/entrypoint.js:
--------------------------------------------------------------------------------
1 | // Shop entrypoint - add your imports here
2 |
--------------------------------------------------------------------------------
/config/services.yaml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: "services/**" }
3 |
--------------------------------------------------------------------------------
/assets/admin/entrypoint.js:
--------------------------------------------------------------------------------
1 | // Admin entrypoint - add your imports here
2 |
--------------------------------------------------------------------------------
/templates/shop/vendor/show/content/header.html.twig:
--------------------------------------------------------------------------------
1 | {% hook 'header' %}
2 |
--------------------------------------------------------------------------------
/assets/admin/controllers.json:
--------------------------------------------------------------------------------
1 | {
2 | "controllers": [],
3 | "entrypoints": []
4 | }
5 |
--------------------------------------------------------------------------------
/assets/shop/controllers.json:
--------------------------------------------------------------------------------
1 | {
2 | "controllers": [],
3 | "entrypoints": []
4 | }
5 |
--------------------------------------------------------------------------------
/config/routes/admin.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor_admin_vendor:
2 | resource: admin/vendor.yaml
3 |
--------------------------------------------------------------------------------
/screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/screenshot_1.png
--------------------------------------------------------------------------------
/screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/screenshot_2.png
--------------------------------------------------------------------------------
/screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/screenshot_3.png
--------------------------------------------------------------------------------
/screenshot_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/screenshot_4.png
--------------------------------------------------------------------------------
/sylius-vendor-plugin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/sylius-vendor-plugin.png
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 |
--------------------------------------------------------------------------------
/config/routes/shop.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor_shop_vendor:
2 | prefix: /vendors
3 | resource: shop/vendor.yaml
4 |
--------------------------------------------------------------------------------
/templates/shop/vendor/index/content/title.html.twig:
--------------------------------------------------------------------------------
1 |
{{ 'odiseo_vendor.ui.vendors'|trans }}
2 |
--------------------------------------------------------------------------------
/templates/admin/shared/layout/base/styles.html.twig:
--------------------------------------------------------------------------------
1 | {{ encore_entry_link_tags('app-admin-entry', null, 'app.admin') }}
2 |
--------------------------------------------------------------------------------
/templates/shop/shared/layout/base/scripts.html.twig:
--------------------------------------------------------------------------------
1 | {{ encore_entry_script_tags('app-shop-entry', null, 'app.shop') }}
2 |
--------------------------------------------------------------------------------
/templates/shop/shared/layout/base/styles.html.twig:
--------------------------------------------------------------------------------
1 | {{ encore_entry_link_tags('app-shop-entry', null, 'app.shop') }}
2 |
--------------------------------------------------------------------------------
/templates/shop/vendor/index/content.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% hook 'content' %}
3 |
4 |
--------------------------------------------------------------------------------
/templates/shop/vendor/show/content.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% hook 'content' %}
3 |
4 |
--------------------------------------------------------------------------------
/templates/admin/shared/layout/base/scripts.html.twig:
--------------------------------------------------------------------------------
1 | {{ encore_entry_script_tags('app-admin-entry', null, 'app.admin') }}
2 |
--------------------------------------------------------------------------------
/tests/Behat/Resources/suites.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - suites/ui/managing_vendors.yaml
3 | - suites/ui/shop_vendors.yaml
4 |
--------------------------------------------------------------------------------
/tests/TestApplication/.env.test:
--------------------------------------------------------------------------------
1 | DATABASE_URL=mysql://root:root@127.0.0.1/odiseo_sylius_vendor_plugin_%kernel.environment%
2 |
3 |
--------------------------------------------------------------------------------
/etc/fixtures/vendor/logo/finley.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/etc/fixtures/vendor/logo/finley.png
--------------------------------------------------------------------------------
/etc/fixtures/vendor/logo/lenora.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/etc/fixtures/vendor/logo/lenora.png
--------------------------------------------------------------------------------
/etc/fixtures/vendor/logo/marlow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/etc/fixtures/vendor/logo/marlow.png
--------------------------------------------------------------------------------
/etc/fixtures/vendor/logo/modella.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/etc/fixtures/vendor/logo/modella.png
--------------------------------------------------------------------------------
/etc/fixtures/vendor/logo/orchid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/etc/fixtures/vendor/logo/orchid.png
--------------------------------------------------------------------------------
/etc/fixtures/vendor/logo/zaneya.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/etc/fixtures/vendor/logo/zaneya.png
--------------------------------------------------------------------------------
/etc/fixtures/vendor/logo/style_mart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/etc/fixtures/vendor/logo/style_mart.png
--------------------------------------------------------------------------------
/etc/fixtures/vendor/logo/sunbeam_market.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/etc/fixtures/vendor/logo/sunbeam_market.png
--------------------------------------------------------------------------------
/tests/Behat/Resources/images/logo_odiseo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/odiseoteam/SyliusVendorPlugin/HEAD/tests/Behat/Resources/images/logo_odiseo.png
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% hook 'sections' %}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/general/name.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 |
3 | {{ form_row(form.name, sylius_test_form_attribute('name')) }}
4 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/general/slug.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 |
3 | {{ form_row(form.slug, sylius_test_form_attribute('slug')) }}
4 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/general/email.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 |
3 | {{ form_row(form.email, sylius_test_form_attribute('email')) }}
4 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/general/enabled.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 |
3 | {{ form_row(form.enabled, sylius_test_form_attribute('enabled')) }}
4 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/general/channels.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 |
3 | {{ form_row(form.channels, sylius_test_form_attribute('channels')) }}
4 |
--------------------------------------------------------------------------------
/tests/TestApplication/config/bundles.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
7 | ];
8 |
--------------------------------------------------------------------------------
/templates/shop/vendor/index/content/pagination.html.twig:
--------------------------------------------------------------------------------
1 | {% import '@SyliusShop/shared/pagination/pagination.html.twig' as pagination %}
2 | {{ pagination.simple(hookable_metadata.context.resources.data) }}
3 |
--------------------------------------------------------------------------------
/templates/shop/vendor/show/content/pagination.html.twig:
--------------------------------------------------------------------------------
1 | {% import '@SyliusShop/shared/pagination/pagination.html.twig' as pagination %}
2 | {{ pagination.simple(hookable_metadata.context.resources.data) }}
3 |
--------------------------------------------------------------------------------
/templates/admin/vendor/grid/field/image.html.twig:
--------------------------------------------------------------------------------
1 | {% if data %}
2 |
3 |
4 |
5 | {% endif %}
6 |
--------------------------------------------------------------------------------
/config/services/controller.yaml:
--------------------------------------------------------------------------------
1 | #services:
2 | # odiseo_vendor.controller.vendor_slug:
3 | # class: Odiseo\SyliusVendorPlugin\Controller\VendorSlugController
4 | # public: true
5 | # arguments:
6 | # - '@sylius.generator.slug'
7 |
--------------------------------------------------------------------------------
/tests/Behat/Behaviour/ContainsErrorInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% hook 'side_navigation' %}
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/Repository/VendorEmailRepositoryInterface.php:
--------------------------------------------------------------------------------
1 |
5 | {{ form_row(form.description, sylius_test_form_attribute('description', locale)) }}
6 |
7 |
--------------------------------------------------------------------------------
/templates/shop/vendor/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends '@SyliusShop/shared/layout/base.html.twig' %}
2 |
3 | {% block title %}{{ 'odiseo_vendor.ui.vendors'|trans }} | {{ parent() }}{% endblock %}
4 |
5 | {% block content %}
6 | {% hook 'odiseo_vendor_shop.vendor.index' with { vendors, resources } %}
7 | {% endblock %}
8 |
--------------------------------------------------------------------------------
/src/Entity/VendorAwareInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% hook 'search' %}
4 |
5 |
6 | {% hook 'limit' %}
7 | {% hook 'sorting' %}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/templates/shop/vendor/show/content/filters.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% hook 'search' %}
4 |
5 |
6 | {% hook 'limit' %}
7 | {% hook 'sorting' %}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/TestApplication/config/routes.yaml:
--------------------------------------------------------------------------------
1 | odiseo_sylius_vendor_admin:
2 | resource: "@OdiseoSyliusVendorPlugin/config/routes/admin.yaml"
3 |
4 | odiseo_sylius_vendor_shop:
5 | resource: "@OdiseoSyliusVendorPlugin/config/routes/shop.yaml"
6 | prefix: /{_locale}
7 | requirements:
8 | _locale: ^[A-Za-z]{2,4}(_([A-Za-z]{4}|[0-9]{3}))?(_([A-Za-z]{2}|[0-9]{3}))?$
9 |
--------------------------------------------------------------------------------
/src/Uploader/VendorLogoUploaderInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/Behat/Resources/services/pages/admin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/profile/translations.html.twig:
--------------------------------------------------------------------------------
1 | {% import '@SyliusAdmin/shared/helper/translations.html.twig' as translations %}
2 |
3 | {% set form = hookable_metadata.context.form %}
4 | {% set prefixes = hookable_metadata.prefixes %}
5 |
6 |
7 | {{ translations.with_hook(form.translations, prefixes, null, {accordion_id: 'vendor-translations', accordion_flush: true}) }}
8 |
9 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/side_navigation/media.html.twig:
--------------------------------------------------------------------------------
1 |
9 | {{ 'sylius.ui.media'|trans }}
10 |
11 |
--------------------------------------------------------------------------------
/tests/TestApplication/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=mysql://root:root@127.0.0.1/odiseo_sylius_vendor_plugin_%kernel.environment%
2 |
3 | BEHAT_BASE_URL="http://nginx/"
4 |
5 | SYLIUS_TEST_APP_BUNDLES_PATH="tests/TestApplication/config/bundles.php"
6 | SYLIUS_TEST_APP_CONFIGS_TO_IMPORT="@OdiseoSyliusVendorPlugin/tests/TestApplication/config/config.yaml"
7 | SYLIUS_TEST_APP_ROUTES_TO_IMPORT="@OdiseoSyliusVendorPlugin/tests/TestApplication/config/routes.yaml"
8 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/side_navigation/general.html.twig:
--------------------------------------------------------------------------------
1 |
9 | {{ 'sylius.ui.general'|trans }}
10 |
11 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/side_navigation/profile.html.twig:
--------------------------------------------------------------------------------
1 |
9 | {{ 'sylius.ui.profile'|trans }}
10 |
11 |
--------------------------------------------------------------------------------
/src/OdiseoSyliusVendorPlugin.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/Behat/Page/Shop/Vendor/ShowPageInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/doc/customization.md:
--------------------------------------------------------------------------------
1 | ## Customization
2 |
3 | ### Overriding models and resources
4 |
5 | The plugin configures the model like a "sylius resource" using the `sylius_resource` configuration.
6 | You can see it here: [src/Resources/config/resources/vendor.yaml](https://github.com/odiseoteam/SyliusVendorPlugin/blob/master/src/Resources/config/resources/vendor.yaml).
7 |
8 | So, you can override the class resource you want simply overriding the proper part of that configuration.
9 |
--------------------------------------------------------------------------------
/ecs.php:
--------------------------------------------------------------------------------
1 | paths([
10 | __DIR__ . '/src',
11 | __DIR__ . '/tests/Behat',
12 | __DIR__ . '/ecs.php',
13 | ]);
14 |
15 | $ecsConfig->import('vendor/sylius-labs/coding-standard/ecs.php');
16 | };
17 |
18 |
--------------------------------------------------------------------------------
/features/shop/browsing_vendors.feature:
--------------------------------------------------------------------------------
1 | @shop_vendors
2 | Feature: Browsing brands
3 | In order to show different brands
4 | As a Customer
5 | I want to browse vendors
6 |
7 | Background:
8 | Given the store operates on a single channel in "United States"
9 | And the store has 10 vendors
10 |
11 | @ui
12 | Scenario: Browsing vendors
13 | When I want to list vendors
14 | Then I should see 10 vendors on the page
15 |
--------------------------------------------------------------------------------
/templates/shop/shared/product/show/vendor.html.twig:
--------------------------------------------------------------------------------
1 | {% set product = hookable_metadata.context.product|default(null) %}
2 |
3 | {% if product and product.vendor %}
4 |
12 | {% endif %}
13 |
--------------------------------------------------------------------------------
/tests/Behat/Page/Shop/Vendor/IndexPageInterface.php:
--------------------------------------------------------------------------------
1 | deleteResourceOnPage(['name' => $name]);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/TestApplication/config/services_test.php:
--------------------------------------------------------------------------------
1 | env(), 'test')) {
9 | $container->import('../../../vendor/sylius/sylius/src/Sylius/Behat/Resources/config/services.xml');
10 | $container->import('@OdiseoSyliusVendorPlugin/tests/Behat/Resources/services.xml');
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/doc/usage.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | For the administration you will have the Vendor menu.
4 | Feel free to modify the plugin templates like you want.
5 |
6 | ### Partial routes
7 |
8 | To render vendor by channel you can do something like this:
9 |
10 | ```twig
11 | {{ render(url('odiseo_sylius_vendor_plugin_shop_partial_vendor_index_by_channel', {'template': '@OdiseoSyliusVendorPlugin/Shop/Vendor/_vendors.html.twig'})) }}
12 | ```
13 |
14 | ### Form validation group
15 |
16 | For forms use the validation group named `odiseo`
17 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/media/logo.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 | {% set vendor = form.vars.data %}
3 |
4 |
5 | {% if vendor.logoName is not null %}
6 |
7 | {% else %}
8 |
9 | {% endif %}
10 |
11 | {{ form_row(form.logoFile, sylius_test_form_attribute('logo')) }}
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 |
4 | /vendor/
5 | /var/
6 | /node_modules/
7 | /drivers/
8 | /composer.lock
9 |
10 | /etc/build/*
11 | !/etc/build/.gitignore
12 |
13 | /.phpunit.result.cache
14 | /behat.yml
15 | /phpspec.yml
16 | /phpunit.xml
17 | /compose.override.yml
18 | .phpunit.result.cache
19 |
20 | # Symfony CLI https://symfony.com/doc/current/setup/symfony_server.html#different-php-settings-per-project
21 | /.php-version
22 | /php.ini
23 |
24 | /tests/TestApplication/.env.local
25 | /tests/TestApplication/.env.*.local
26 |
--------------------------------------------------------------------------------
/tests/Behat/Behaviour/ContainsErrorTrait.php:
--------------------------------------------------------------------------------
1 | getDocument()->findAll('css', '[data-test-form-error-alert]');
16 |
17 | return count($validationMessageElements) > 0;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/TestApplication/src/Entity/Product.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | public function getVendors(): Collection;
15 |
16 | public function hasVendor(VendorInterface $vendor): bool;
17 |
18 | public function addVendor(VendorInterface $vendor): void;
19 |
20 | public function removeVendor(VendorInterface $vendor): void;
21 | }
22 |
--------------------------------------------------------------------------------
/src/Entity/VendorTranslationInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ vendor.name }}
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/features/admin/deleting_vendor.feature:
--------------------------------------------------------------------------------
1 | @managing_vendors
2 | Feature: Deleting a vendor
3 | In order to delete a brand
4 | As an Administrator
5 | I want to be able to delete a vendor
6 |
7 | Background:
8 | Given I am logged in as an administrator
9 | And the store operates on a single channel in "United States"
10 | And there is an existing vendor with "Test" name
11 |
12 | @ui
13 | Scenario: Deleting a vendor
14 | When I go to the vendors page
15 | And I delete the vendor "Test"
16 | Then I should be notified that it has been successfully deleted
17 |
--------------------------------------------------------------------------------
/src/Fixture/ProductFixture.php:
--------------------------------------------------------------------------------
1 | children();
15 |
16 | $node->scalarNode('vendor');
17 |
18 | parent::configureResourceNode($resourceNode);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/config/api_platform/properties/VendorTranslation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templates/shop/vendor/show/content/products.html.twig:
--------------------------------------------------------------------------------
1 | {% import '@SyliusShop/shared/messages.html.twig' as messages %}
2 |
3 | {% set resources = hookable_metadata.context.resources %}
4 |
5 | {% if resources.data|length > 0 %}
6 |
7 | {% for product in resources.data %}
8 | {{ component('sylius_shop:product:card', { product: product, template: '@SyliusShop/product/common/card.html.twig' }) }}
9 | {% endfor %}
10 |
11 | {% else %}
12 | {{ messages.info('sylius.ui.no_results_to_display') }}
13 | {% endif %}
14 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/media.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 | {% set vendor = form.vars.data %}
3 |
4 |
16 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/general.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 | {% set vendor = form.vars.data %}
3 |
4 |
16 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/profile.html.twig:
--------------------------------------------------------------------------------
1 | {% set form = hookable_metadata.context.form %}
2 | {% set vendor = form.vars.data %}
3 |
4 |
16 |
--------------------------------------------------------------------------------
/tests/Behat/Resources/suites/ui/shop_vendors.yaml:
--------------------------------------------------------------------------------
1 | default:
2 | suites:
3 | ui_shop_vendors:
4 | contexts:
5 | - sylius.behat.context.hook.doctrine_orm
6 |
7 | - odiseo_vendor.behat.context.transform.vendor
8 | - sylius.behat.context.transform.shared_storage
9 |
10 | - sylius.behat.context.setup.channel
11 | - sylius.behat.context.setup.product
12 | - odiseo_vendor.behat.context.setup.vendor
13 |
14 | - odiseo_vendor.behat.context.ui.shop.vendor
15 | filters:
16 | tags: "@shop_vendors&&@ui"
17 |
--------------------------------------------------------------------------------
/config/app/resources/vendor_email.yaml:
--------------------------------------------------------------------------------
1 | sylius_resource:
2 | resources:
3 | odiseo_vendor.vendor_email:
4 | driver: doctrine/orm
5 | classes:
6 | model: Odiseo\SyliusVendorPlugin\Entity\VendorEmail
7 | interface: Odiseo\SyliusVendorPlugin\Entity\VendorEmailInterface
8 | form: Odiseo\SyliusVendorPlugin\Form\Type\VendorEmailType
9 | repository: Odiseo\SyliusVendorPlugin\Repository\VendorEmailRepository
10 | controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
11 | factory: Sylius\Component\Resource\Factory\Factory
12 |
--------------------------------------------------------------------------------
/config/routes/shop/vendor.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor_shop_vendor_index:
2 | path: /
3 | methods: [GET]
4 | defaults:
5 | _controller: odiseo_vendor.controller.vendor::indexAction
6 | _sylius:
7 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index.html.twig'
8 | grid: odiseo_vendor_shop_vendor
9 |
10 | odiseo_vendor_shop_vendor_show:
11 | path: /{slug}
12 | methods: [GET]
13 | defaults:
14 | _controller: sylius.controller.product::indexAction
15 | _sylius:
16 | template: "@OdiseoSyliusVendorPlugin/shop/vendor/show.html.twig"
17 | grid: odiseo_vendor_shop_vendor_product
18 |
--------------------------------------------------------------------------------
/features/admin/editing_vendor.feature:
--------------------------------------------------------------------------------
1 | @managing_vendors
2 | Feature: Editing a vendor
3 | In order to change a brand
4 | As an Administrator
5 | I want to be able to edit a vendor
6 |
7 | Background:
8 | Given I am logged in as an administrator
9 | And the store operates on a single channel in "United States"
10 | And there is an existing vendor with "Test" name
11 |
12 | @ui
13 | Scenario: Renaming a vendor
14 | Given I want to modify the vendor "Test"
15 | When I rename the name with "Test Edited"
16 | And I save my changes
17 | Then I should be notified that it has been successfully edited
18 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
19 |
20 | return $treeBuilder;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Entity/VendorTrait.php:
--------------------------------------------------------------------------------
1 | vendor;
18 | }
19 |
20 | public function setVendor(?VendorInterface $vendor): void
21 | {
22 | $this->vendor = $vendor;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/templates/shop/vendor/show/content/header/vendor.html.twig:
--------------------------------------------------------------------------------
1 | {% set vendor = hookable_metadata.context.vendor %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
{{ vendor.name }}
10 |
{{ vendor.description }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/translations/messages.en.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor:
2 | ui:
3 | vendor: Vendor
4 | vendors: Vendors
5 | edit_vendor: Edit vendor
6 | new_vendor: New vendor
7 | profile: Profile
8 | manage_vendors: Manage vendors
9 | menu:
10 | admin:
11 | vendors: Vendors
12 | form:
13 | product:
14 | select_vendor: Select vendor
15 | vendor:
16 | slug: Slug
17 | email: Email
18 | translations: Translations
19 | description: Description
20 | logo: Logo
21 | channels: Channels
22 | extra_emails: Extra emails
23 |
--------------------------------------------------------------------------------
/translations/messages.es.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor:
2 | ui:
3 | vendor: Vendedor
4 | vendors: Vendedores
5 | edit_vendor: Editar vendedor
6 | new_vendor: Nuevo vendedor
7 | profile: Perfil
8 | manage_vendors: Administrar vendedores
9 | menu:
10 | admin:
11 | vendors: Vendedores
12 | form:
13 | product:
14 | select_vendor: Selecciona vendedor
15 | vendor:
16 | slug: Slug
17 | email: Email
18 | translations: Traducciones
19 | description: Descripción
20 | logo: Logo
21 | channels: Tiendas
22 | extra_emails: Emails extra
23 |
--------------------------------------------------------------------------------
/templates/shop/vendor/index/content/vendors.html.twig:
--------------------------------------------------------------------------------
1 | {% import '@SyliusShop/shared/messages.html.twig' as messages %}
2 |
3 | {% set resources = hookable_metadata.context.resources %}
4 |
5 | {% if resources.data|length > 0 %}
6 |
7 | {% for vendor in resources.data %}
8 |
9 | {{ component('odiseo_vendor_shop:vendor', { vendor: vendor, template: '@OdiseoSyliusVendorPlugin/shop/vendor/common/vendor.html.twig' }) }}
10 |
11 | {% endfor %}
12 |
13 | {% else %}
14 | {{ messages.info('sylius.ui.no_results_to_display') }}
15 | {% endif %}
16 |
--------------------------------------------------------------------------------
/src/Form/Type/VendorEmailType.php:
--------------------------------------------------------------------------------
1 | add('value', EmailType::class, [
19 | 'label' => 'sylius.ui.value',
20 | ])
21 | ;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/translations/messages.fr.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor:
2 | ui:
3 | vendor: Fournisseur
4 | vendors: Fournisseurs
5 | edit_vendor: Modifier fournisseur
6 | new_vendor: Nouveau fournisseur
7 | profile: Profil
8 | manage_vendors: Gérer les fournisseurss
9 | menu:
10 | admin:
11 | vendors: Fournisseurs
12 | form:
13 | product:
14 | select_vendor: Sélectionner un fournisseur
15 | vendor:
16 | slug: Slug
17 | email: Email
18 | translations: Traductions
19 | description: Description
20 | logo: Logo
21 | channels: Channels
22 | extra_emails: Extra emails
23 |
--------------------------------------------------------------------------------
/compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | php:
3 | image: ghcr.io/sylius/sylius-php:8.3-alpine
4 | mysql:
5 | image: mysql:8.4
6 | platform: linux/amd64
7 | healthcheck:
8 | test: '/usr/bin/mysql --execute "SHOW databases;"'
9 | timeout: 3s
10 | interval: 1s
11 | retries: 10
12 | environment:
13 | MYSQL_ALLOW_EMPTY_PASSWORD: 1
14 | cap_add:
15 | - SYS_NICE # prevent "mbind: Operation not permitted" errors
16 | nginx:
17 | image: ghcr.io/sylius/sylius-nginx:latest
18 | depends_on:
19 | - php
20 | mailhog:
21 | # do not use in production!
22 | image: mailhog/mailhog:latest
23 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: max
3 | reportUnmatchedIgnoredErrors: false
4 | paths:
5 | - src
6 | - tests/Behat
7 |
8 | excludePaths:
9 | # Makes PHPStan crash
10 | - 'src/DependencyInjection/Configuration.php'
11 |
12 | # Test dependencies
13 | - 'tests/TestApplication/src/**.php'
14 |
15 | ignoreErrors:
16 | - '/Parameter #1 \$configuration of method Symfony\\Component\\DependencyInjection\\Extension\\Extension::processConfiguration\(\) expects Symfony\\Component\\Config\\Definition\\ConfigurationInterface, Symfony\\Component\\Config\\Definition\\ConfigurationInterface\|null given\./'
17 | -
18 | identifier: missingType.iterableValue
19 |
--------------------------------------------------------------------------------
/src/Event/VendorFormMenuBuilderEvent.php:
--------------------------------------------------------------------------------
1 | vendor;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Behat/Resources/suites/ui/managing_vendors.yaml:
--------------------------------------------------------------------------------
1 | default:
2 | suites:
3 | ui_managing_vendors:
4 | contexts:
5 | - sylius.behat.context.hook.doctrine_orm
6 |
7 | - odiseo_vendor.behat.context.transform.vendor
8 | - sylius.behat.context.transform.shared_storage
9 |
10 | - sylius.behat.context.setup.channel
11 | - sylius.behat.context.setup.admin_security
12 | - odiseo_vendor.behat.context.setup.vendor
13 |
14 | - odiseo_vendor.behat.context.ui.admin.managing_vendors
15 | - sylius.behat.context.ui.admin.notification
16 | filters:
17 | tags: "@managing_vendors&&@ui"
18 |
--------------------------------------------------------------------------------
/tests/Behat/Resources/services/pages/shop/vendor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/doc/sitemap.md:
--------------------------------------------------------------------------------
1 | ## Sitemap
2 |
3 | This plugin suggests installing the Sylius sitemap plugin providing a sitemap for vendors.
4 |
5 | ### Installation
6 |
7 | 1. Run `composer require stefandoorn/sitemap-plugin ^2.0@alpha`
8 |
9 | 2. Enable the plugin in bundles.php
10 |
11 | ```php
12 | ['all' => true],
18 | ];
19 | ```
20 |
21 | 3. Import the providers
22 |
23 | ```yml
24 | # config/services.yaml
25 | imports:
26 | # ...
27 | - { resource: "@OdiseoSyliusVendorPlugin/Resources/config/services/sitemap.yaml" }
28 | ```
29 |
30 | Follow the original [SyliusSitemapPlugin](https://github.com/stefandoorn/sitemap-plugin) documentation.
31 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form.html.twig:
--------------------------------------------------------------------------------
1 | {% form_theme form '@OdiseoSyliusVendorPlugin/admin/vendor/form_theme.html.twig' %}
2 |
3 |
4 | {{ form_start(form, {'attr': {'class': 'ui loadable form', 'novalidate': 'novalidate', 'id': form.vars.id}}) }}
5 |
6 | {% if hookable_metadata.configuration.method is defined %}
7 |
8 | {% endif %}
9 | {{ form_errors(form) }}
10 | {{ form_widget(form._token) }}
11 |
12 | {% hook 'form' with { form } %}
13 |
14 | {{ form_end(form, {render_rest: false}) }}
15 |
16 |
--------------------------------------------------------------------------------
/src/Form/Type/VendorTranslationType.php:
--------------------------------------------------------------------------------
1 | add('description', TextareaType::class, [
19 | 'label' => 'odiseo_vendor.form.vendor.description',
20 | ])
21 | ;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/EventListener/VendorLogoUploadListener.php:
--------------------------------------------------------------------------------
1 | getSubject();
22 | Assert::isInstanceOf($vendor, VendorInterface::class);
23 |
24 | $this->uploader->upload($vendor);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Menu/AdminMenuListener.php:
--------------------------------------------------------------------------------
1 | getMenu();
15 |
16 | /** @var ItemInterface $item */
17 | $item = $menu->getChild('catalog');
18 | if (null == $item) {
19 | $item = $menu;
20 | }
21 |
22 | $item->addChild('vendors', ['route' => 'odiseo_vendor_admin_vendor_index'])
23 | ->setLabel('odiseo_vendor.menu.admin.vendors')
24 | ->setLabelAttribute('icon', 'trademark')
25 | ;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Entity/VendorEmail.php:
--------------------------------------------------------------------------------
1 | createdAt = new \DateTime();
21 | }
22 |
23 | public function getId(): ?int
24 | {
25 | return $this->id;
26 | }
27 |
28 | public function getValue(): ?string
29 | {
30 | return $this->value;
31 | }
32 |
33 | public function setValue(?string $value): void
34 | {
35 | $this->value = $value;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Repository/VendorRepositoryInterface.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(OdiseoSyliusVendorPlugin::class, $plugin);
18 | $this->assertInstanceOf(Bundle::class, $plugin);
19 | }
20 |
21 | public function testItHasCorrectName(): void
22 | {
23 | $plugin = new OdiseoSyliusVendorPlugin();
24 |
25 | $this->assertEquals('OdiseoSyliusVendorPlugin', $plugin->getName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Repository/ProductRepositoryInterface.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | interface ProductRepositoryInterface extends BaseProductRepositoryInterface
18 | {
19 | public function createShopListByVendorQueryBuilder(
20 | ChannelInterface $channel,
21 | VendorInterface $vendor,
22 | string $locale,
23 | array $sorting = [],
24 | ): QueryBuilder;
25 | }
26 |
--------------------------------------------------------------------------------
/templates/shop/vendor/index/content/filters/search.html.twig:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/templates/shop/vendor/index/content/filters/limit.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% set resources = hookable_metadata.context.resources %}
3 | {% set pagination_limits = ( resources.definition.limits ) %}
4 |
5 |
11 |
12 | {{ 'sylius.ui.show'|trans }}: {{ hookable_metadata.context.resources.data.maxPerPage }}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/templates/shop/vendor/show/content/filters/limit.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% set resources = hookable_metadata.context.resources %}
3 | {% set pagination_limits = ( resources.definition.limits ) %}
4 |
5 |
11 |
12 | {{ 'sylius.ui.show'|trans }}: {{ hookable_metadata.context.resources.data.maxPerPage }}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Fixture/VendorFixture.php:
--------------------------------------------------------------------------------
1 | children();
15 |
16 | $node->arrayNode('channels')->scalarPrototype();
17 | $node->scalarNode('name')->cannotBeEmpty();
18 | $node->scalarNode('slug')->cannotBeEmpty();
19 | $node->scalarNode('email')->cannotBeEmpty();
20 | $node->scalarNode('logo');
21 | $node->scalarNode('description');
22 | }
23 |
24 | public function getName(): string
25 | {
26 | return 'vendor';
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/templates/shop/vendor/show/content/filters/search.html.twig:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/config/config.yaml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: "app/twig_hooks/**/*.yaml" }
3 | - { resource: "app/resources/**/*.yaml" }
4 | - { resource: "app/grids/**/*.yaml" }
5 | - { resource: "app/fixtures/**/*.yaml" }
6 |
7 | knp_gaufrette:
8 | stream_wrapper: ~
9 | adapters:
10 | odiseo_vendor_logo:
11 | local:
12 | directory: "%sylius_core.public_dir%/media/vendor-logo"
13 | create: true
14 | filesystems:
15 | odiseo_vendor_logo:
16 | adapter: odiseo_vendor_logo
17 |
18 | liip_imagine:
19 | loaders:
20 | odiseo_vendor_logo:
21 | filesystem:
22 | data_root: "%kernel.project_dir%/public/media/vendor-logo"
23 | filter_sets:
24 | odiseo_vendor_logo:
25 | data_loader: odiseo_vendor_logo
26 | filters:
27 | thumbnail: { size: [350, 350], mode: outbound }
28 |
--------------------------------------------------------------------------------
/config/validation/VendorEmail.yaml:
--------------------------------------------------------------------------------
1 | Odiseo\SyliusVendorPlugin\Entity\VendorEmail:
2 | constraints:
3 | - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
4 | fields: [vendor, value]
5 | errorPath: value
6 | message: 'odiseo_vendor.vendor.email.unique'
7 | groups: ['odiseo']
8 | properties:
9 | value:
10 | - NotBlank:
11 | message: 'odiseo_vendor.vendor.email.not_blank'
12 | groups: ['odiseo']
13 | - Length:
14 | min: 2
15 | max: 250
16 | minMessage: 'odiseo_vendor.vendor.email.min_length'
17 | maxMessage: 'odiseo_vendor.vendor.email.max_length'
18 | groups: ['odiseo']
19 | - Email:
20 | message: 'odiseo_vendor.vendor.email.invalid'
21 | mode: strict
22 | groups: ['odiseo']
23 |
--------------------------------------------------------------------------------
/tests/Behat/Page/Shop/Vendor/ShowPage.php:
--------------------------------------------------------------------------------
1 | getElement('name')->getText();
25 | }
26 |
27 | /**
28 | * @inheritdoc
29 | */
30 | protected function getDefinedElements(): array
31 | {
32 | return array_merge(parent::getDefinedElements(), [
33 | 'name' => '[data-test-vendor-name]',
34 | ]);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/config/services/twig_component.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | odiseo_vendor.twig.component.vendor:
3 | class: Odiseo\SyliusVendorPlugin\Twig\Component\VendorComponent
4 | tags:
5 | - { name: sylius.twig_component, key: 'odiseo_vendor_shop:vendor' }
6 |
7 | odiseo_vendor.twig.component.admin.vendor.form:
8 | class: Odiseo\SyliusVendorPlugin\Twig\Component\Vendor\FormComponent
9 | arguments:
10 | - '@odiseo_vendor.repository.vendor'
11 | - '@form.factory'
12 | - '%odiseo_vendor.model.vendor.class%'
13 | - 'Odiseo\SyliusVendorPlugin\Form\Type\VendorType'
14 | - '@sylius.generator.slug'
15 | calls:
16 | - method: setLiveResponder
17 | arguments:
18 | - '@ux.live_component.live_responder'
19 | tags:
20 | - { name: 'sylius.live_component.admin', key: 'odiseo_vendor:admin:vendor:form' }
21 |
--------------------------------------------------------------------------------
/tests/Unit/Repository/VendorRepositoryTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(class_exists(VendorRepository::class));
17 | }
18 |
19 | public function testItImplementsCorrectInterface(): void
20 | {
21 | $reflectionClass = new \ReflectionClass(VendorRepository::class);
22 | $this->assertTrue($reflectionClass->implementsInterface('Odiseo\SyliusVendorPlugin\Repository\VendorRepositoryInterface'));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Entity/VendorTranslation.php:
--------------------------------------------------------------------------------
1 | createdAt = new \DateTime();
21 | }
22 |
23 | public function getId(): ?int
24 | {
25 | return $this->id;
26 | }
27 |
28 | public function getDescription(): ?string
29 | {
30 | return $this->description;
31 | }
32 |
33 | public function setDescription(?string $description): void
34 | {
35 | $this->description = $description;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Twig/VendorRuntime.php:
--------------------------------------------------------------------------------
1 | localeContext->getLocaleCode();
24 | }
25 |
26 | return $this->vendorRepository->findOneBySlug($slug, $localeCode);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "10:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: vimeo/psalm
11 | versions:
12 | - 4.5.0
13 | - 4.5.1
14 | - 4.5.2
15 | - 4.6.0
16 | - 4.6.1
17 | - 4.6.2
18 | - 4.6.3
19 | - 4.7.0
20 | - 4.7.1
21 | - dependency-name: phpstan/phpstan
22 | versions:
23 | - 0.12.70
24 | - 0.12.71
25 | - 0.12.73
26 | - 0.12.75
27 | - 0.12.76
28 | - 0.12.77
29 | - 0.12.78
30 | - 0.12.80
31 | - 0.12.81
32 | - 0.12.83
33 | - 0.12.84
34 | - dependency-name: sylius-labs/coding-standard
35 | versions:
36 | - 4.0.2
37 | - dependency-name: phpstan/phpstan-doctrine
38 | versions:
39 | - 0.12.32
40 | - dependency-name: phpstan/phpstan-webmozart-assert
41 | versions:
42 | - 0.12.10
43 | - 0.12.11
44 |
--------------------------------------------------------------------------------
/tests/Unit/Repository/VendorEmailRepositoryTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(class_exists(VendorEmailRepository::class));
17 | }
18 |
19 | public function testItImplementsCorrectInterface(): void
20 | {
21 | $reflectionClass = new \ReflectionClass(VendorEmailRepository::class);
22 | $this->assertTrue($reflectionClass->implementsInterface('Odiseo\SyliusVendorPlugin\Repository\VendorEmailRepositoryInterface'));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/TestApplication/config/config.yaml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: "@OdiseoSyliusVendorPlugin/config/config.yaml" }
3 | - { resource: "services_test.php" }
4 |
5 | twig:
6 | paths:
7 | '%kernel.project_dir%/../../../tests/TestApplication/templates': ~
8 |
9 | # Remove if you do not need to extend entities
10 | doctrine:
11 | orm:
12 | entity_managers:
13 | default:
14 | mappings:
15 | TestApplication:
16 | is_bundle: false
17 | type: attribute
18 | dir: '%kernel.project_dir%/../../../tests/TestApplication/src/Entity'
19 | prefix: Tests\Odiseo\SyliusVendorPlugin
20 |
21 | sylius_product:
22 | resources:
23 | product:
24 | classes:
25 | model: Tests\Odiseo\SyliusVendorPlugin\Entity\Product
26 | repository: Tests\Odiseo\SyliusVendorPlugin\Repository\ProductRepository
27 |
--------------------------------------------------------------------------------
/tests/Unit/Twig/VendorExtensionTest.php:
--------------------------------------------------------------------------------
1 | extension = new VendorExtension();
18 | }
19 |
20 | public function testItIsInitializable(): void
21 | {
22 | $this->assertInstanceOf(VendorExtension::class, $this->extension);
23 | }
24 |
25 | public function testItReturnsTwigFunctions(): void
26 | {
27 | $functions = $this->extension->getFunctions();
28 |
29 | $this->assertIsArray($functions);
30 | $this->assertCount(1, $functions);
31 | $this->assertInstanceOf(TwigFunction::class, $functions[0]);
32 | $this->assertEquals('odiseo_vendor_get_vendor_by_slug', $functions[0]->getName());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form_theme.html.twig:
--------------------------------------------------------------------------------
1 | {% extends '@SyliusAdmin/shared/form_theme.html.twig' %}
2 |
3 | {% block _odiseo_vendor_channels_row %}
4 | {{ form_label(form) }}
5 |
6 | {% for channel in form %}
7 | {{ form_row(channel, sylius_test_form_attribute('channel-code', channel.vars.value)|merge({ label_attr: { class: 'checkbox-switch' } })) }}
8 | {% endfor %}
9 |
10 | {{ form_errors(form) }}
11 | {% endblock %}
12 |
13 | {% block _odiseo_vendor_slug_row %}
14 | {{ form_label(form) }}
15 |
16 | {{ form_widget(form, sylius_test_form_attribute('slug')) }}
17 |
24 | {{ ux_icon('tabler:wand', {'class': 'icon mx-0'}) }}
25 |
26 |
27 | {{ form_errors(form) }}
28 | {% endblock %}
29 |
--------------------------------------------------------------------------------
/src/Entity/VendorsTrait.php:
--------------------------------------------------------------------------------
1 | vendors = new ArrayCollection();
17 | }
18 |
19 | public function getVendors(): Collection
20 | {
21 | return $this->vendors;
22 | }
23 |
24 | public function hasVendor(VendorInterface $vendor): bool
25 | {
26 | return $this->vendors->contains($vendor);
27 | }
28 |
29 | public function addVendor(VendorInterface $vendor): void
30 | {
31 | if (!$this->hasVendor($vendor)) {
32 | $this->vendors->add($vendor);
33 | }
34 | }
35 |
36 | public function removeVendor(VendorInterface $vendor): void
37 | {
38 | if ($this->hasVendor($vendor)) {
39 | $this->vendors->removeElement($vendor);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Unit/Twig/Component/VendorComponentTest.php:
--------------------------------------------------------------------------------
1 | component = new VendorComponent();
18 | }
19 |
20 | public function testItIsInitializable(): void
21 | {
22 | $this->assertInstanceOf(VendorComponent::class, $this->component);
23 | }
24 |
25 | public function testItHasNullVendorByDefault(): void
26 | {
27 | $this->assertNull($this->component->vendor);
28 | }
29 |
30 | public function testItCanSetVendor(): void
31 | {
32 | $vendor = new Vendor();
33 | $this->component->vendor = $vendor;
34 |
35 | $this->assertSame($vendor, $this->component->vendor);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/config/services/fixture.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | odiseo_vendor.fixture.example_factory.product:
3 | class: Odiseo\SyliusVendorPlugin\Fixture\Factory\ProductExampleFactory
4 | decorates: 'sylius.fixture.example_factory.product'
5 | arguments:
6 | - '@odiseo_vendor.fixture.example_factory.product.inner'
7 | - '@odiseo_vendor.repository.vendor'
8 |
9 | odiseo_vendor.fixture.vendor:
10 | class: Odiseo\SyliusVendorPlugin\Fixture\VendorFixture
11 | arguments:
12 | - '@odiseo_vendor.manager.vendor'
13 | - '@odiseo_vendor.fixture.example_factory.vendor'
14 | tags:
15 | - { name: sylius_fixtures.fixture }
16 |
17 | odiseo_vendor.fixture.example_factory.vendor:
18 | class: Odiseo\SyliusVendorPlugin\Fixture\Factory\VendorExampleFactory
19 | arguments:
20 | - '@odiseo_vendor.factory.vendor'
21 | - '@odiseo_vendor.uploader.vendor_logo'
22 | - '@sylius.repository.channel'
23 | - '@sylius.repository.locale'
24 | - '@file_locator'
25 |
--------------------------------------------------------------------------------
/tests/Behat/Page/Shop/Vendor/IndexPage.php:
--------------------------------------------------------------------------------
1 | getElement('vendors');
25 |
26 | $vendorsNumberOnPage = count($vendorsList->findAll('css', '[data-test-vendor]'));
27 |
28 | return $pagesNumber == $vendorsNumberOnPage;
29 | }
30 |
31 | /**
32 | * @inheritdoc
33 | */
34 | protected function getDefinedElements(): array
35 | {
36 | return array_merge(parent::getDefinedElements(), [
37 | 'vendors' => '[data-test-vendors]',
38 | ]);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/config/app/resources/vendor.yaml:
--------------------------------------------------------------------------------
1 | sylius_resource:
2 | resources:
3 | odiseo_vendor.vendor:
4 | driver: doctrine/orm
5 | classes:
6 | model: Odiseo\SyliusVendorPlugin\Entity\Vendor
7 | interface: Odiseo\SyliusVendorPlugin\Entity\VendorInterface
8 | form: Odiseo\SyliusVendorPlugin\Form\Type\VendorType
9 | repository: Odiseo\SyliusVendorPlugin\Repository\VendorRepository
10 | factory: Sylius\Component\Resource\Factory\Factory
11 | translation:
12 | classes:
13 | model: Odiseo\SyliusVendorPlugin\Entity\VendorTranslation
14 | interface: Odiseo\SyliusVendorPlugin\Entity\VendorTranslationInterface
15 | form: Odiseo\SyliusVendorPlugin\Form\Type\VendorTranslationType
16 | repository: Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
17 | controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
18 | factory: Sylius\Component\Resource\Factory\Factory
19 |
--------------------------------------------------------------------------------
/config/services/event_listener.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | odiseo_vendor.event_listener.admin.menu_builder:
3 | class: Odiseo\SyliusVendorPlugin\Menu\AdminMenuListener
4 | tags:
5 | - { name: kernel.event_listener, event: sylius.menu.admin.main, method: addAdminMenuItems }
6 |
7 | odiseo_vendor.menu_builder.admin.vendor_form:
8 | class: Odiseo\SyliusVendorPlugin\Menu\AdminVendorFormMenuBuilder
9 | arguments:
10 | - '@knp_menu.factory'
11 | - '@event_dispatcher'
12 | tags:
13 | - { name: knp_menu.menu_builder, method: createMenu, alias: odiseo_vendor.admin.admin_vendor_form }
14 |
15 | odiseo_vendor.event_listener.vendor_logo_upload:
16 | class: Odiseo\SyliusVendorPlugin\EventListener\VendorLogoUploadListener
17 | arguments:
18 | - '@odiseo_vendor.uploader.vendor_logo'
19 | tags:
20 | - { name: kernel.event_listener, event: odiseo_vendor.vendor.pre_create, method: uploadLogo }
21 | - { name: kernel.event_listener, event: odiseo_vendor.vendor.pre_update, method: uploadLogo }
22 |
--------------------------------------------------------------------------------
/config/app/twig_hooks/shop/base.yaml:
--------------------------------------------------------------------------------
1 | sylius_twig_hooks:
2 | hooks:
3 | 'sylius_shop.base#stylesheets':
4 | custom_styles:
5 | template: '@OdiseoSyliusVendorPlugin/shop/shared/layout/base/styles.html.twig'
6 | priority: -100
7 |
8 | 'sylius_shop.base#javascripts':
9 | custom_scripts:
10 | template: '@OdiseoSyliusVendorPlugin/shop/shared/layout/base/scripts.html.twig'
11 | priority: -100
12 |
13 | 'sylius_shop.base.header.content':
14 | content:
15 | template: "@OdiseoSyliusVendorPlugin/shop/shared/layout/_menu.html.twig"
16 | priority: 250
17 |
18 | 'sylius_shop.base.header.navbar.menu':
19 | content:
20 | template: "@OdiseoSyliusVendorPlugin/shop/shared/layout/_menuMobile.html.twig"
21 | priority: -100
22 |
23 | 'sylius_shop.product.show.content.info.summary':
24 | vendor:
25 | template: "@OdiseoSyliusVendorPlugin/shop/shared/product/show/vendor.html.twig"
26 | priority: 150
27 |
--------------------------------------------------------------------------------
/src/Form/Extension/ProductTypeExtension.php:
--------------------------------------------------------------------------------
1 | add('vendor', VendorChoiceType::class, [
18 | 'label' => 'odiseo_vendor.form.product.select_vendor',
19 | 'constraints' => [
20 | new NotBlank([
21 | 'message' => 'odiseo_vendor.product.vendor.not_blank',
22 | 'groups' => ['sylius'],
23 | ]),
24 | ],
25 | ]);
26 | }
27 |
28 | public static function getExtendedTypes(): iterable
29 | {
30 | return [ProductType::class];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2026 Diego D'amico
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is furnished
10 | to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/config/serialization/VendorTranslation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | odiseo_vendor:admin:vendor:index
11 | odiseo_vendor:admin:vendor:show
12 |
13 |
14 | odiseo_vendor:admin:vendor:create
15 | odiseo_vendor:admin:vendor:update
16 |
17 |
18 | odiseo_vendor:admin:vendor:index
19 | odiseo_vendor:admin:vendor:show
20 | odiseo_vendor:admin:vendor:create
21 | odiseo_vendor:admin:vendor:update
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/config/api_platform/resources/admin/VendorTranslation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/config/doctrine/VendorTranslation.orm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/Behat/Context/Transform/VendorContext.php:
--------------------------------------------------------------------------------
1 | vendorRepository = $vendorRepository;
21 | }
22 |
23 | /**
24 | * @Transform /^vendor "([^"]+)"$/
25 | * @Transform /^"([^"]+)" vendor$/
26 | */
27 | public function getVendorByName(string $vendorName): VendorInterface
28 | {
29 | /** @var VendorInterface $vendor */
30 | $vendor = $this->vendorRepository->findOneBy(['name' => $vendorName]);
31 |
32 | Assert::notNull(
33 | $vendor,
34 | 'Vendor with name %s does not exist',
35 | );
36 |
37 | return $vendor;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Behat/Page/Admin/Vendor/CreatePageInterface.php:
--------------------------------------------------------------------------------
1 | entity = new class() {
18 | use VendorTrait;
19 | };
20 | }
21 |
22 | public function testItHasNoVendorByDefault(): void
23 | {
24 | $this->assertNull($this->entity->getVendor());
25 | }
26 |
27 | public function testItAllowsToSetAndGetVendor(): void
28 | {
29 | $vendor = new Vendor();
30 |
31 | $this->entity->setVendor($vendor);
32 | $this->assertSame($vendor, $this->entity->getVendor());
33 | }
34 |
35 | public function testItAllowsToSetVendorToNull(): void
36 | {
37 | $vendor = new Vendor();
38 | $this->entity->setVendor($vendor);
39 |
40 | $this->entity->setVendor(null);
41 | $this->assertNull($this->entity->getVendor());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Unit/Fixture/VendorFixtureTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(class_exists(VendorFixture::class));
17 | }
18 |
19 | public function testItHasCorrectName(): void
20 | {
21 | // Test the getName method without instantiating the complex fixture
22 | $reflectionClass = new \ReflectionClass(VendorFixture::class);
23 | $method = $reflectionClass->getMethod('getName');
24 |
25 | // Create a mock to avoid constructor issues
26 | $fixture = $this->getMockBuilder(VendorFixture::class)
27 | ->disableOriginalConstructor()
28 | ->onlyMethods([])
29 | ->getMock();
30 |
31 | $this->assertEquals('vendor', $fixture->getName());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/translations/validators.en.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor:
2 | vendor:
3 | slug:
4 | unique: There is an existing vendor with this slug.
5 | not_blank: Slug cannot be blank.
6 | min_length: Slug must be at least {{ limit }} characters long.
7 | max_length: Slug can not be longer than {{ limit }} characters.
8 | name:
9 | unique: There is an existing vendor with this name.
10 | not_blank: Name cannot be blank.
11 | min_length: Name must be at least {{ limit }} characters long.
12 | max_length: Name can not be longer than {{ limit }} characters.
13 | email:
14 | invalid: This email is invalid.
15 | unique: This email is already used.
16 | not_blank: Email cannot be blank.
17 | min_length: Email must be at least {{ limit }} characters long.
18 | max_length: Email can not be longer than {{ limit }} characters.
19 | logo_file:
20 | not_blank: Logo cannot be blank.
21 | vendor_translation:
22 | description:
23 | not_blank: Description cannot be blank.
24 | product:
25 | vendor:
26 | not_blank: Please select a vendor.
27 |
--------------------------------------------------------------------------------
/tests/Behat/Resources/services/pages/admin/vendor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | odiseo_vendor_admin_vendor_index
7 |
8 |
9 | odiseo_vendor_admin_vendor_create
10 |
11 |
12 | odiseo_vendor_admin_vendor_update
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/Unit/Entity/VendorsTraitTest.php:
--------------------------------------------------------------------------------
1 | entity = new class() {
18 | use VendorsTrait;
19 | };
20 | }
21 |
22 | public function testItInitializesVendorsCollection(): void
23 | {
24 | $this->assertCount(0, $this->entity->getVendors());
25 | }
26 |
27 | public function testItManagesVendors(): void
28 | {
29 | $vendor = new Vendor();
30 |
31 | $this->assertFalse($this->entity->hasVendor($vendor));
32 |
33 | $this->entity->addVendor($vendor);
34 | $this->assertTrue($this->entity->hasVendor($vendor));
35 | $this->assertCount(1, $this->entity->getVendors());
36 |
37 | $this->entity->removeVendor($vendor);
38 | $this->assertFalse($this->entity->hasVendor($vendor));
39 | $this->assertCount(0, $this->entity->getVendors());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/translations/validators.es.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor:
2 | vendor:
3 | slug:
4 | unique: Ya existe un vendedor con este slug.
5 | not_blank: El slug no puede ir vacío.
6 | min_length: El slug debe tener al menos {{ limit }} caracteres.
7 | max_length: El slug no puede tener más de {{ limit }} caracteres.
8 | name:
9 | unique: Ya existe un vendedor con este nombre.
10 | not_blank: El nombre no puede ir vacío.
11 | min_length: El nombre debe tener al menos {{ limit }} caracteres.
12 | max_length: El nombre no puede tener más de {{ limit }} caracteres.
13 | email:
14 | invalid: Este email no es válido.
15 | unique: Este correo electrónico ya está en uso.
16 | not_blank: El email no puede ir vacío.
17 | min_length: El email debe tener al menos {{ limit }} caracteres.
18 | max_length: El email no puede tener más de {{ limit }} caracteres.
19 | logo_file:
20 | not_blank: El logo no puede ir vacío.
21 | vendor_translation:
22 | description:
23 | not_blank: La descripción no puede ir vacía.
24 | product:
25 | vendor:
26 | not_blank: Por favor, seleccione un vendedor.
27 |
--------------------------------------------------------------------------------
/translations/validators.fr.yaml:
--------------------------------------------------------------------------------
1 | odiseo_vendor:
2 | vendor:
3 | slug:
4 | unique: Il existe déjà un fournisseur avec ce slug.
5 | not_blank: Le slug ne peux pas être null.
6 | min_length: Le slug doit contenir au minimum {{ limit }} caractères.
7 | max_length: Le slug doit contenir au maximum {{ limit }} caractères.
8 | name:
9 | unique: Il existe déjà un fournisseur avec ce nom.
10 | not_blank: Le nom ne peut pas être null.
11 | min_length: Le nom doit contenir au minimum {{ limit }} caractères.
12 | max_length: Le nom doit contenir au maximum {{ limit }} caractères.
13 | email:
14 | invalid: Cette adresse email est invalide.
15 | unique: Cette adresse email est déjà utilisée.
16 | not_blank: L''adresse email ne peut pas être nulle.
17 | min_length: L''email doit contenir au minimum {{ limit }} caractères.
18 | max_length: L''email doit contenir au maximum {{ limit }} caractères.
19 | logo_file:
20 | not_blank: Le logo ne peut pas être null.
21 | vendor_translation:
22 | description:
23 | not_blank: La description ne peut pas être nulle.
24 | product:
25 | vendor:
26 | not_blank: Veuillez sélectionner un fournisseur.
27 |
--------------------------------------------------------------------------------
/config/app/grids/shop/vendor.yaml:
--------------------------------------------------------------------------------
1 | sylius_grid:
2 | grids:
3 | odiseo_vendor_shop_vendor:
4 | driver:
5 | name: doctrine/orm
6 | options:
7 | class: "%odiseo_vendor.model.vendor.class%"
8 | repository:
9 | method: createShopListQueryBuilder
10 | arguments:
11 | channel: "expr:service('sylius.context.channel').getChannel()"
12 | sorting: "expr:service('request_stack').getCurrentRequest().get('sorting', [])"
13 | sorting:
14 | position: asc
15 | limits: [12, 24, 36]
16 | fields:
17 | createdAt:
18 | type: datetime
19 | sortable: ~
20 | position:
21 | type: string
22 | sortable: ~
23 | name:
24 | type: string
25 | label: sylius.ui.name
26 | sortable: ~
27 | filters:
28 | search:
29 | type: shop_string
30 | label: false
31 | options:
32 | fields: [name]
33 | form_options:
34 | type: contains
35 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | tests
21 |
22 |
23 |
24 | tests/Unit
25 |
26 |
27 |
28 | tests/Functional
29 |
30 |
31 |
32 | tests/Integration
33 |
34 |
35 |
36 | tests/Functional
37 | tests/Integration
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/tests/Unit/Form/Type/VendorTranslationTypeTest.php:
--------------------------------------------------------------------------------
1 | formType = new VendorTranslationType('VendorTranslation', ['sylius']);
19 | }
20 |
21 | public function testItIsInitializable(): void
22 | {
23 | $this->assertInstanceOf(VendorTranslationType::class, $this->formType);
24 | }
25 |
26 | public function testItBuildsForm(): void
27 | {
28 | $builder = $this->createMock(FormBuilderInterface::class);
29 | $builder->expects($this->once())
30 | ->method('add')
31 | ->with('description', TextareaType::class, $this->isType('array'))
32 | ->willReturnSelf();
33 |
34 | $this->formType->buildForm($builder, []);
35 | }
36 |
37 | public function testItHasCorrectBlockPrefix(): void
38 | {
39 | $this->assertEquals('vendor_translation', $this->formType->getBlockPrefix());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/config/doctrine/VendorEmail.orm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tests/Behat/Page/Admin/Vendor/CreatePage.php:
--------------------------------------------------------------------------------
1 | getDocument()->fillField('Name', $name);
18 | }
19 |
20 | public function fillSlug(string $slug): void
21 | {
22 | $this->getDocument()->fillField('Slug', $slug);
23 | }
24 |
25 | public function fillDescription(string $description): void
26 | {
27 | $this->getDocument()->fillField('Description', $description);
28 | }
29 |
30 | public function fillEmail(string $email): void
31 | {
32 | $this->getDocument()->fillField('Email', $email);
33 | }
34 |
35 | public function uploadFile(string $file): void
36 | {
37 | $path = __DIR__ . '/../../../Resources/images/' . $file;
38 | Assert::fileExists($path);
39 | $realPath = realpath($path);
40 | Assert::string($realPath, sprintf('Could not resolve real path for %s', $path));
41 | $this->getDocument()->attachFileToField('Logo', $realPath);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Behat/Page/Admin/Vendor/UpdatePage.php:
--------------------------------------------------------------------------------
1 | getDocument()->fillField('Name', $name);
18 | }
19 |
20 | public function fillSlug(string $slug): void
21 | {
22 | $this->getDocument()->fillField('Slug', $slug);
23 | }
24 |
25 | public function fillDescription(string $description): void
26 | {
27 | $this->getDocument()->fillField('Description', $description);
28 | }
29 |
30 | public function fillEmail(string $email): void
31 | {
32 | $this->getDocument()->fillField('Email', $email);
33 | }
34 |
35 | public function uploadFile(string $file): void
36 | {
37 | $path = __DIR__ . '/../../../Resources/images/' . $file;
38 | Assert::fileExists($path);
39 | $realPath = realpath($path);
40 | Assert::string($realPath, sprintf('Could not resolve real path for %s', $path));
41 | $this->getDocument()->attachFileToField('Logo', $realPath);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/features/admin/adding_vendor.feature:
--------------------------------------------------------------------------------
1 | @managing_vendors
2 | Feature: Adding a new vendor
3 | In order to show different brands
4 | As an Administrator
5 | I want to add a new vendor to the admin
6 |
7 | Background:
8 | Given I am logged in as an administrator
9 | And the store operates on a single channel in "United States"
10 |
11 | @ui
12 | Scenario: Adding a new vendor
13 | Given I want to add a new vendor
14 | When I fill the name with "Test"
15 | And I fill the slug with "test"
16 | And I fill the description with "This is a test"
17 | And I fill the email with "test@odiseo.com.ar"
18 | And I upload the "logo_odiseo.png" image
19 | And I add it
20 | Then I should be notified that it has been successfully created
21 | And the vendor "Test" should appear in the admin
22 |
23 | @ui
24 | Scenario: Trying to add a new vendor with empty fields
25 | Given I want to add a new vendor
26 | When I fill the name with "Test"
27 | And I add it
28 | Then I should be notified that the form contains invalid fields
29 |
30 | @ui
31 | Scenario: Trying to add a vendor with existing slug
32 | Given there is an existing vendor with "Test" name
33 | And I want to add a new vendor
34 | When I fill the slug with "test"
35 | And I add it
36 | Then I should be notified that the form contains invalid fields
37 |
--------------------------------------------------------------------------------
/config/services/form.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | odiseo_vendor.form.extension.type.product:
3 | class: Odiseo\SyliusVendorPlugin\Form\Extension\ProductTypeExtension
4 | tags:
5 | - { name: form.type_extension, extended_type: Sylius\Bundle\ProductBundle\Form\Type\ProductType }
6 |
7 | odiseo_vendor.form.type.vendor_choice:
8 | class: Odiseo\SyliusVendorPlugin\Form\Type\VendorChoiceType
9 | arguments:
10 | - '@odiseo_vendor.repository.vendor'
11 | tags:
12 | - { name: form.type , alias: odiseo_vendor_choice }
13 |
14 | odiseo_vendor.form.type.vendor:
15 | class: Odiseo\SyliusVendorPlugin\Form\Type\VendorType
16 | arguments:
17 | - '%odiseo_vendor.model.vendor.class%'
18 | - ['odiseo']
19 | tags:
20 | - { name: form.type, alias: odiseo_vendor }
21 |
22 | odiseo_vendor.form.type.vendor_translation:
23 | class: Odiseo\SyliusVendorPlugin\Form\Type\VendorTranslationType
24 | arguments:
25 | - '%odiseo_vendor.model.vendor_translation.class%'
26 | - ['odiseo']
27 | tags:
28 | - { name: form.type, alias: odiseo_vendor_translation }
29 |
30 | odiseo_vendor.form.type.vendor_email:
31 | class: Odiseo\SyliusVendorPlugin\Form\Type\VendorEmailType
32 | arguments:
33 | - '%odiseo_vendor.model.vendor_email.class%'
34 | - ['odiseo']
35 | tags:
36 | - { name: form.type, alias: odiseo_vendor_email }
37 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | # Change these settings to your own preference
9 | indent_style = space
10 | indent_size = 4
11 |
12 | # We recommend you to keep these unchanged
13 | end_of_line = lf
14 | charset = utf-8
15 | trim_trailing_whitespace = true
16 | insert_final_newline = true
17 |
18 | [*.feature]
19 | indent_style = space
20 | indent_size = 4
21 |
22 | [*.js]
23 | indent_style = space
24 | indent_size = 2
25 |
26 | [*.json]
27 | indent_style = space
28 | indent_size = 2
29 |
30 | [*.md]
31 | indent_style = space
32 | indent_size = 4
33 | trim_trailing_whitespace = false
34 |
35 | [*.neon]
36 | indent_style = space
37 | indent_size = 4
38 |
39 | [*.php]
40 | indent_style = space
41 | indent_size = 4
42 |
43 | [*.sh]
44 | indent_style = space
45 | indent_size = 4
46 |
47 | [*.{yaml,yml}]
48 | indent_style = space
49 | indent_size = 4
50 | trim_trailing_whitespace = false
51 |
52 | [.babelrc]
53 | indent_style = space
54 | indent_size = 2
55 |
56 | [.gitmodules]
57 | indent_style = tab
58 | indent_size = 4
59 |
60 | [.php_cs{,.dist}]
61 | indent_style = space
62 | indent_size = 4
63 |
64 | [composer.json]
65 | indent_style = space
66 | indent_size = 4
67 |
68 | [package.json]
69 | indent_style = space
70 | indent_size = 2
71 |
72 | [phpspec.yml{,.dist}]
73 | indent_style = space
74 | indent_size = 4
75 |
76 | [phpstan.neon]
77 | indent_style = space
78 | indent_size = 4
79 |
80 | [phpunit.xml{,.dist}]
81 | indent_style = space
82 | indent_size = 4
83 |
--------------------------------------------------------------------------------
/templates/admin/vendor/form/sections/profile/extraEmails.html.twig:
--------------------------------------------------------------------------------
1 | {% set extraEmails = hookable_metadata.context.form.extraEmails %}
2 | {% set vendor = hookable_metadata.context.vendor %}
3 | {% set form = hookable_metadata.context.form %}
4 |
5 |
6 | {{ form_label(extraEmails) }}
7 |
12 | {% for email_form in extraEmails %}
13 |
18 |
19 |
20 | {{ form_row(email_form.value, sylius_test_form_attribute('value')) }}
21 |
22 |
23 | {{ form_widget(email_form.vars.button_delete, { label: 'sylius.ui.delete'|trans, attr: { class: 'btn btn-outline-danger w-100','data-test-email-delete': '' }}) }}
24 |
25 |
26 |
27 | {% endfor %}
28 |
29 |
30 | {% set attributes = sylius_test_form_attribute('add-extraEmail')|sylius_merge_recursive({ label: 'sylius.ui.add_image'|trans, attr: { class: 'btn btn-outline-primary '}}) %}
31 |
32 |
33 | {{ form_widget(extraEmails.vars.button_add, attributes) }}
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/DependencyInjection/OdiseoSyliusVendorExtension.php:
--------------------------------------------------------------------------------
1 | load('services.yaml');
24 | }
25 |
26 | public function prepend(ContainerBuilder $container): void
27 | {
28 | $this->prependDoctrineMigrations($container);
29 | }
30 |
31 | protected function getMigrationsNamespace(): string
32 | {
33 | return 'Odiseo\SyliusVendorPlugin\Migrations';
34 | }
35 |
36 | protected function getMigrationsDirectory(): string
37 | {
38 | return '@OdiseoSyliusVendorPlugin/src/Migrations';
39 | }
40 |
41 | protected function getNamespacesOfMigrationsExecutedBefore(): array
42 | {
43 | return ['Sylius\Bundle\CoreBundle\Migrations'];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Unit/EventListener/VendorLogoUploadListenerTest.php:
--------------------------------------------------------------------------------
1 | uploader = $this->createMock(VendorLogoUploaderInterface::class);
22 | $this->listener = new VendorLogoUploadListener($this->uploader);
23 | }
24 |
25 | public function testItIsInitializable(): void
26 | {
27 | $this->assertInstanceOf(VendorLogoUploadListener::class, $this->listener);
28 | }
29 |
30 | public function testItUploadsLogoWhenEventTriggered(): void
31 | {
32 | $vendor = new Vendor();
33 | $event = new GenericEvent($vendor);
34 |
35 | $this->uploader->expects($this->once())
36 | ->method('upload')
37 | ->with($vendor);
38 |
39 | $this->listener->uploadLogo($event);
40 | }
41 |
42 | public function testItThrowsExceptionForInvalidSubject(): void
43 | {
44 | $invalidSubject = new \stdClass();
45 | $event = new GenericEvent($invalidSubject);
46 |
47 | $this->expectException(\InvalidArgumentException::class);
48 |
49 | $this->listener->uploadLogo($event);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/config/app/grids/shop/vendor_product.yaml:
--------------------------------------------------------------------------------
1 | sylius_grid:
2 | grids:
3 | odiseo_vendor_shop_vendor_product:
4 | driver:
5 | name: doctrine/orm
6 | options:
7 | class: "%sylius.model.product.class%"
8 | repository:
9 | method: createShopListByVendorQueryBuilder
10 | arguments:
11 | channel: "expr:service('sylius.context.channel').getChannel()"
12 | vendor: "expr:notFoundOnNull(service('odiseo_vendor.repository.vendor').findOneBySlug($slug, service('sylius.context.locale').getLocaleCode()))"
13 | locale: "expr:service('sylius.context.locale').getLocaleCode()"
14 | sorting: "expr:service('request_stack').getCurrentRequest().get('sorting', [])"
15 | sorting:
16 | position: asc
17 | limits: [12, 24, 36]
18 | fields:
19 | createdAt:
20 | type: datetime
21 | sortable: ~
22 | position:
23 | type: string
24 | sortable: productTaxon.position
25 | name:
26 | type: string
27 | sortable: translation.name
28 | price:
29 | type: int
30 | sortable: channelPricing.price
31 | filters:
32 | search:
33 | type: shop_string
34 | label: false
35 | options:
36 | fields: [translation.name]
37 | form_options:
38 | type: contains
39 |
--------------------------------------------------------------------------------
/tests/Unit/Form/Type/VendorEmailTypeTest.php:
--------------------------------------------------------------------------------
1 | formType = new VendorEmailType(VendorEmail::class, ['sylius']);
21 | }
22 |
23 | public function testItIsInitializable(): void
24 | {
25 | $this->assertInstanceOf(VendorEmailType::class, $this->formType);
26 | }
27 |
28 | public function testItBuildsForm(): void
29 | {
30 | $builder = $this->createMock(FormBuilderInterface::class);
31 | $builder->expects($this->once())
32 | ->method('add')
33 | ->with('value', EmailType::class, $this->isType('array'))
34 | ->willReturnSelf();
35 |
36 | $this->formType->buildForm($builder, []);
37 | }
38 |
39 | public function testItConfiguresOptions(): void
40 | {
41 | $resolver = new OptionsResolver();
42 | $this->formType->configureOptions($resolver);
43 |
44 | $options = $resolver->resolve([]);
45 | $this->assertEquals(VendorEmail::class, $options['data_class']);
46 | }
47 |
48 | public function testItHasCorrectBlockPrefix(): void
49 | {
50 | $this->assertEquals('vendor_email', $this->formType->getBlockPrefix());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Twig/Component/Vendor/FormComponent.php:
--------------------------------------------------------------------------------
1 | */
25 | use ResourceFormComponentTrait;
26 |
27 | /**
28 | * @param RepositoryInterface $vendorRepository
29 | * @param class-string $resourceClass
30 | * @param class-string $formClass
31 | */
32 | public function __construct(
33 | RepositoryInterface $vendorRepository,
34 | FormFactoryInterface $formFactory,
35 | string $resourceClass,
36 | string $formClass,
37 | protected readonly SlugGeneratorInterface $slugGenerator,
38 | ) {
39 | $this->initialize($vendorRepository, $formFactory, $resourceClass, $formClass);
40 | }
41 |
42 | #[LiveAction]
43 | public function generateSlug(): void
44 | {
45 | $this->formValues['slug'] = $this->slugGenerator->generate(
46 | $this->formValues['name'],
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/config/app/twig_hooks/shop/vendor/index.yaml:
--------------------------------------------------------------------------------
1 | sylius_twig_hooks:
2 | hooks:
3 | 'odiseo_vendor_shop.vendor.index':
4 | content:
5 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content.html.twig'
6 | priority: 0
7 |
8 | 'odiseo_vendor_shop.vendor.index.content':
9 | breadcrumbs:
10 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/breadcrumbs.html.twig'
11 | priority: 500
12 | title:
13 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/title.html.twig'
14 | priority: 400
15 | filters:
16 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/filters.html.twig'
17 | priority: 300
18 | vendors:
19 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/vendors.html.twig'
20 | priority: 100
21 | pagination:
22 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/pagination.html.twig'
23 | priority: 0
24 |
25 | 'odiseo_vendor_shop.vendor.index.content.search':
26 | search:
27 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/filters/search.html.twig'
28 | priority: 0
29 |
30 | 'odiseo_vendor_shop.vendor.index.content.limit':
31 | limit:
32 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/filters/limit.html.twig'
33 | priority: 0
34 |
35 | 'odiseo_vendor_shop.vendor.index.content.sorting':
36 | sorting:
37 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/filters/sorting.html.twig'
38 | priority: 0
39 |
--------------------------------------------------------------------------------
/config/api_platform/resources/shop/Vendor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | odiseo_vendor:shop:vendor:index
16 |
17 |
18 |
19 |
20 |
21 |
22 | ASC
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | odiseo_vendor:shop:vendor:show
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/tests/Unit/Form/Type/VendorTypeTest.php:
--------------------------------------------------------------------------------
1 | formType = new VendorType(Vendor::class, ['sylius']);
20 | }
21 |
22 | public function testItIsInitializable(): void
23 | {
24 | $this->assertInstanceOf(VendorType::class, $this->formType);
25 | }
26 |
27 | public function testItBuildsForm(): void
28 | {
29 | $builder = $this->createMock(FormBuilderInterface::class);
30 | $builder->expects($this->exactly(8))
31 | ->method('add')
32 | ->willReturnCallback(function ($name, $type, $options) use ($builder) {
33 | $this->assertContains($name, ['name', 'slug', 'enabled', 'translations', 'email', 'logoFile', 'channels', 'extraEmails']);
34 |
35 | return $builder;
36 | })
37 | ->willReturnSelf();
38 |
39 | $this->formType->buildForm($builder, []);
40 | }
41 |
42 | public function testItConfiguresOptions(): void
43 | {
44 | $resolver = new OptionsResolver();
45 | $this->formType->configureOptions($resolver);
46 |
47 | $options = $resolver->resolve([]);
48 | $this->assertEquals(Vendor::class, $options['data_class']);
49 | }
50 |
51 | public function testItHasCorrectBlockPrefix(): void
52 | {
53 | $this->assertEquals('odiseo_vendor', $this->formType->getBlockPrefix());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/Unit/Entity/VendorEmailTest.php:
--------------------------------------------------------------------------------
1 | vendorEmail = new VendorEmail();
19 | }
20 |
21 | public function testItIsInitializable(): void
22 | {
23 | $this->assertInstanceOf(VendorEmailInterface::class, $this->vendorEmail);
24 | }
25 |
26 | public function testItHasNoIdByDefault(): void
27 | {
28 | $this->assertNull($this->vendorEmail->getId());
29 | }
30 |
31 | public function testItHasNoValueByDefault(): void
32 | {
33 | $this->assertNull($this->vendorEmail->getValue());
34 | }
35 |
36 | public function testItAllowsToSetAndGetValue(): void
37 | {
38 | $this->vendorEmail->setValue('test@example.com');
39 | $this->assertEquals('test@example.com', $this->vendorEmail->getValue());
40 | }
41 |
42 | public function testItHasCreatedAtSetOnConstruction(): void
43 | {
44 | $this->assertInstanceOf(\DateTime::class, $this->vendorEmail->getCreatedAt());
45 | }
46 |
47 | public function testItIsTimestampable(): void
48 | {
49 | $now = new \DateTime();
50 |
51 | $this->vendorEmail->setUpdatedAt($now);
52 | $this->assertEquals($now, $this->vendorEmail->getUpdatedAt());
53 | }
54 |
55 | public function testItCanBeAssociatedWithVendor(): void
56 | {
57 | $vendor = new Vendor();
58 |
59 | $this->vendorEmail->setVendor($vendor);
60 | $this->assertSame($vendor, $this->vendorEmail->getVendor());
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Unit/Entity/VendorTranslationTest.php:
--------------------------------------------------------------------------------
1 | vendorTranslation = new VendorTranslation();
18 | }
19 |
20 | public function testItIsInitializable(): void
21 | {
22 | $this->assertInstanceOf(VendorTranslationInterface::class, $this->vendorTranslation);
23 | }
24 |
25 | public function testItHasNoIdByDefault(): void
26 | {
27 | $this->assertNull($this->vendorTranslation->getId());
28 | }
29 |
30 | public function testItHasNoDescriptionByDefault(): void
31 | {
32 | $this->assertNull($this->vendorTranslation->getDescription());
33 | }
34 |
35 | public function testItAllowsToSetAndGetDescription(): void
36 | {
37 | $this->vendorTranslation->setDescription('Test description');
38 | $this->assertEquals('Test description', $this->vendorTranslation->getDescription());
39 | }
40 |
41 | public function testItHasCreatedAtSetOnConstruction(): void
42 | {
43 | $this->assertInstanceOf(\DateTime::class, $this->vendorTranslation->getCreatedAt());
44 | }
45 |
46 | public function testItIsTimestampable(): void
47 | {
48 | $now = new \DateTime();
49 |
50 | $this->vendorTranslation->setUpdatedAt($now);
51 | $this->assertEquals($now, $this->vendorTranslation->getUpdatedAt());
52 | }
53 |
54 | public function testItInheritsLocaleFromAbstractTranslation(): void
55 | {
56 | $this->vendorTranslation->setLocale('en_US');
57 | $this->assertEquals('en_US', $this->vendorTranslation->getLocale());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Unit/Event/VendorFormMenuBuilderEventTest.php:
--------------------------------------------------------------------------------
1 | createMock(FactoryInterface::class);
18 | $menu = $this->createMock(ItemInterface::class);
19 | $vendor = new Vendor();
20 |
21 | $event = new VendorFormMenuBuilderEvent($factory, $menu, $vendor);
22 |
23 | $this->assertInstanceOf(VendorFormMenuBuilderEvent::class, $event);
24 | }
25 |
26 | public function testItReturnsFactory(): void
27 | {
28 | $factory = $this->createMock(FactoryInterface::class);
29 | $menu = $this->createMock(ItemInterface::class);
30 | $vendor = new Vendor();
31 |
32 | $event = new VendorFormMenuBuilderEvent($factory, $menu, $vendor);
33 |
34 | $this->assertSame($factory, $event->getFactory());
35 | }
36 |
37 | public function testItReturnsMenu(): void
38 | {
39 | $factory = $this->createMock(FactoryInterface::class);
40 | $menu = $this->createMock(ItemInterface::class);
41 | $vendor = new Vendor();
42 |
43 | $event = new VendorFormMenuBuilderEvent($factory, $menu, $vendor);
44 |
45 | $this->assertSame($menu, $event->getMenu());
46 | }
47 |
48 | public function testItReturnsVendor(): void
49 | {
50 | $factory = $this->createMock(FactoryInterface::class);
51 | $menu = $this->createMock(ItemInterface::class);
52 | $vendor = new Vendor();
53 |
54 | $event = new VendorFormMenuBuilderEvent($factory, $menu, $vendor);
55 |
56 | $this->assertSame($vendor, $event->getVendor());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Behat/Context/Ui/Shop/VendorContext.php:
--------------------------------------------------------------------------------
1 | indexPage = $indexPage;
25 | $this->showPage = $showPage;
26 | }
27 |
28 | /**
29 | * @When I want to list vendors
30 | *
31 | * @throws \FriendsOfBehat\PageObjectExtension\Page\UnexpectedPageException
32 | */
33 | public function iWantToListVendors(): void
34 | {
35 | $this->indexPage->open();
36 | }
37 |
38 | /**
39 | * @Then I should see :pagesNumber vendors on the page
40 | *
41 | * @throws \Behat\Mink\Exception\ElementNotFoundException
42 | */
43 | public function iShouldSeeVendorsOnThePage(int $pagesNumber): void
44 | {
45 | Assert::true($this->indexPage->hasPagesNumber($pagesNumber));
46 | }
47 |
48 | /**
49 | * @When I go to the :slug page
50 | *
51 | * @throws \FriendsOfBehat\PageObjectExtension\Page\UnexpectedPageException
52 | */
53 | public function iGoToThePage(string $slug): void
54 | {
55 | $this->showPage->open(['slug' => $slug]);
56 | }
57 |
58 | /**
59 | * @Then I should see a page with :name name
60 | *
61 | * @throws \Behat\Mink\Exception\ElementNotFoundException
62 | */
63 | public function iShouldSeeAPageWithName(string $name): void
64 | {
65 | Assert::true($this->showPage->hasName($name));
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/config/app/twig_hooks/shop/vendor/show.yaml:
--------------------------------------------------------------------------------
1 | sylius_twig_hooks:
2 | hooks:
3 | 'odiseo_vendor_shop.vendor.show':
4 | content:
5 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/show/content.html.twig'
6 | priority: 0
7 |
8 | 'odiseo_vendor_shop.vendor.show.content':
9 | header:
10 | template: "@OdiseoSyliusVendorPlugin/shop/vendor/show/content/header.html.twig"
11 | priority: 300
12 | filters:
13 | template: "@OdiseoSyliusVendorPlugin/shop/vendor/show/content/filters.html.twig"
14 | priority: 200
15 | products:
16 | template: "@OdiseoSyliusVendorPlugin/shop/vendor/show/content/products.html.twig"
17 | priority: 100
18 | pagination:
19 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/index/content/pagination.html.twig'
20 | priority: 0
21 |
22 | 'odiseo_vendor_shop.vendor.show.content.header':
23 | breadcrumb:
24 | template: "@OdiseoSyliusVendorPlugin/shop/vendor/show/content/header/breadcrumbs.html.twig"
25 | priority: 100
26 | vendor:
27 | template: "@OdiseoSyliusVendorPlugin/shop/vendor/show/content/header/vendor.html.twig"
28 | priority: 0
29 |
30 | 'odiseo_vendor_shop.vendor.show.content.search':
31 | search:
32 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/show/content/filters/search.html.twig'
33 | priority: 0
34 |
35 | 'odiseo_vendor_shop.vendor.show.content.limit':
36 | limit:
37 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/show/content/filters/limit.html.twig'
38 | priority: 0
39 |
40 | 'odiseo_vendor_shop.vendor.show.content.sorting':
41 | sorting:
42 | template: '@OdiseoSyliusVendorPlugin/shop/vendor/show/content/filters/sorting.html.twig'
43 | priority: 0
44 |
--------------------------------------------------------------------------------
/src/Form/Type/VendorChoiceType.php:
--------------------------------------------------------------------------------
1 | addModelTransformer(new CollectionToArrayTransformer());
29 | }
30 | }
31 |
32 | public function configureOptions(OptionsResolver $resolver): void
33 | {
34 | $criteria = [];
35 | $orderBy = ['name' => 'ASC'];
36 |
37 | $resolver->setDefaults([
38 | 'choices' => function (Options $_options) use ($criteria, $orderBy): array {
39 | $vendors = $this->vendorRepository->findBy($criteria, $orderBy);
40 |
41 | $choices = [];
42 | /** @var VendorInterface $vendor */
43 | foreach ($vendors as $vendor) {
44 | $choices[$vendor->getName()] = $vendor;
45 | }
46 |
47 | return $choices;
48 | },
49 | ]);
50 | }
51 |
52 | public function getParent(): string
53 | {
54 | return ChoiceType::class;
55 | }
56 |
57 | public function getBlockPrefix(): string
58 | {
59 | return 'odiseo_sylius_vendor_choice';
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Unit/Form/Extension/ProductTypeExtensionTest.php:
--------------------------------------------------------------------------------
1 | extension = new ProductTypeExtension();
21 | }
22 |
23 | public function testItIsInitializable(): void
24 | {
25 | $this->assertInstanceOf(ProductTypeExtension::class, $this->extension);
26 | }
27 |
28 | public function testItBuildsForm(): void
29 | {
30 | $builder = $this->createMock(FormBuilderInterface::class);
31 |
32 | $builder->expects($this->once())
33 | ->method('add')
34 | ->with(
35 | 'vendor',
36 | VendorChoiceType::class,
37 | $this->callback(function ($options) {
38 | $this->assertEquals('odiseo_vendor.form.product.select_vendor', $options['label']);
39 | $this->assertArrayHasKey('constraints', $options);
40 | $this->assertIsArray($options['constraints']);
41 | $this->assertInstanceOf(NotBlank::class, $options['constraints'][0]);
42 |
43 | return true;
44 | }),
45 | );
46 |
47 | $this->extension->buildForm($builder, []);
48 | }
49 |
50 | public function testItExtendsProductType(): void
51 | {
52 | $extendedTypes = $this->extension::getExtendedTypes();
53 |
54 | $this->assertIsIterable($extendedTypes);
55 | $this->assertContains(ProductType::class, $extendedTypes);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/config/api_platform/properties/Vendor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | object
18 |
19 |
20 |
21 |
22 | string
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/Repository/VendorRepository.php:
--------------------------------------------------------------------------------
1 | findByEnabledQueryBuilder($channel);
19 | }
20 |
21 | public function findByEnabledQueryBuilder(?ChannelInterface $channel): QueryBuilder
22 | {
23 | $queryBuilder = $this->createQueryBuilder('o')
24 | ->andWhere('o.enabled = :enabled')
25 | ->setParameter('enabled', true)
26 | ;
27 |
28 | if ($channel instanceof ChannelInterface) {
29 | $queryBuilder->innerJoin('o.channels', 'channel')
30 | ->andWhere('channel = :channel')
31 | ->setParameter('channel', $channel)
32 | ;
33 | }
34 |
35 | return $queryBuilder;
36 | }
37 |
38 | public function findByChannel(ChannelInterface $channel): array
39 | {
40 | return (array) $this->findByEnabledQueryBuilder($channel)->getQuery()->getResult();
41 | }
42 |
43 | public function findOneBySlug(string $slug, string $locale): ?VendorInterface
44 | {
45 | $vendor = $this->createQueryBuilder('o')
46 | ->addSelect('translation')
47 | ->innerJoin('o.translations', 'translation')
48 | ->andWhere('o.slug = :slug')
49 | ->andWhere('o.enabled = true')
50 | ->andWhere('translation.locale = :locale')
51 | ->setParameter('slug', $slug)
52 | ->setParameter('locale', $locale)
53 | ->getQuery()
54 | ->getOneOrNullResult()
55 | ;
56 |
57 | return $vendor instanceof VendorInterface ? $vendor : null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Unit/Form/Type/VendorChoiceTypeTest.php:
--------------------------------------------------------------------------------
1 | vendorRepository = $this->createMock(VendorRepositoryInterface::class);
22 | $this->formType = new VendorChoiceType($this->vendorRepository);
23 | }
24 |
25 | public function testItIsInitializable(): void
26 | {
27 | $this->assertInstanceOf(VendorChoiceType::class, $this->formType);
28 | }
29 |
30 | public function testItConfiguresOptions(): void
31 | {
32 | $vendor1 = new Vendor();
33 | $vendor1->setName('Vendor 1');
34 | $vendor2 = new Vendor();
35 | $vendor2->setName('Vendor 2');
36 | $vendors = [$vendor1, $vendor2];
37 |
38 | $this->vendorRepository->expects($this->once())
39 | ->method('findBy')
40 | ->with([], ['name' => 'ASC'])
41 | ->willReturn($vendors);
42 |
43 | $resolver = new OptionsResolver();
44 | $this->formType->configureOptions($resolver);
45 |
46 | $options = $resolver->resolve([]);
47 |
48 | $this->assertArrayHasKey('choices', $options);
49 | // After resolving, choices should be an array with vendor names as keys
50 | $this->assertIsArray($options['choices']);
51 | $this->assertArrayHasKey('Vendor 1', $options['choices']);
52 | $this->assertArrayHasKey('Vendor 2', $options['choices']);
53 | }
54 |
55 | public function testItHasCorrectBlockPrefix(): void
56 | {
57 | $this->assertEquals('odiseo_sylius_vendor_choice', $this->formType->getBlockPrefix());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Unit/Uploader/VendorLogoUploaderTest.php:
--------------------------------------------------------------------------------
1 | filesystem = $this->createMock(FilesystemInterface::class);
22 | $this->uploader = new VendorLogoUploader($this->filesystem);
23 | }
24 |
25 | public function testItIsInitializable(): void
26 | {
27 | $this->assertInstanceOf(VendorLogoUploader::class, $this->uploader);
28 | }
29 |
30 | public function testItDoesNothingWhenNoLogoFile(): void
31 | {
32 | $vendor = new Vendor();
33 |
34 | $this->filesystem->expects($this->never())
35 | ->method('write');
36 |
37 | $this->uploader->upload($vendor);
38 | $this->assertNull($vendor->getLogoName());
39 | }
40 |
41 | public function testItCanUploadLogo(): void
42 | {
43 | // Create a real temporary file for testing
44 | $tmpFile = tempnam(sys_get_temp_dir(), 'test_logo');
45 | file_put_contents($tmpFile, 'test content');
46 |
47 | $vendor = new Vendor();
48 | $file = new File($tmpFile);
49 | $vendor->setLogoFile($file);
50 |
51 | // Mock filesystem to simulate successful operations
52 | $this->filesystem->expects($this->any())
53 | ->method('has')
54 | ->willReturn(false);
55 |
56 | $this->filesystem->expects($this->once())
57 | ->method('write')
58 | ->with($this->isType('string'), 'test content', true);
59 |
60 | $this->uploader->upload($vendor);
61 |
62 | // Verify that a logo name was set
63 | $this->assertNotNull($vendor->getLogoName());
64 |
65 | // Clean up
66 | unlink($tmpFile);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Uploader/VendorLogoUploader.php:
--------------------------------------------------------------------------------
1 | getLogoFile() === null) {
21 | return;
22 | }
23 |
24 | /** @var File $file */
25 | $file = $vendor->getLogoFile();
26 |
27 | if (null !== $vendor->getLogoName() && $this->has($vendor->getLogoName())) {
28 | $this->remove($vendor->getLogoName());
29 | }
30 |
31 | do {
32 | $path = $this->name($file);
33 | } while ($this->isAdBlockingProne($path) || $this->filesystem->has($path));
34 |
35 | $vendor->setLogoName($path);
36 |
37 | if ($vendor->getLogoName() === null) {
38 | return;
39 | }
40 |
41 | if (file_get_contents($file->getPathname()) === false) {
42 | return;
43 | }
44 |
45 | $this->filesystem->write(
46 | $vendor->getLogoName(),
47 | (string) file_get_contents($file->getPathname()),
48 | true,
49 | );
50 | }
51 |
52 | public function remove(string $path): bool
53 | {
54 | if ($this->filesystem->has($path)) {
55 | return $this->filesystem->delete($path);
56 | }
57 |
58 | return false;
59 | }
60 |
61 | private function has(string $path): bool
62 | {
63 | return $this->filesystem->has($path);
64 | }
65 |
66 | private function name(File $file): string
67 | {
68 | $name = \str_replace('.', '', \uniqid('', true));
69 | $extension = $file->guessExtension();
70 |
71 | if (\is_string($extension) && '' !== $extension) {
72 | $name = \sprintf('%s.%s', $name, $extension);
73 | }
74 |
75 | return $name;
76 | }
77 |
78 | private function isAdBlockingProne(string $path): bool
79 | {
80 | return str_contains($path, 'ad');
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Repository/ProductRepositoryTrait.php:
--------------------------------------------------------------------------------
1 | createQueryBuilder('o')
20 | ->distinct()
21 | ->addSelect('translation')
22 | ->addSelect('productTaxon')
23 | ->innerJoin('o.translations', 'translation', 'WITH', 'translation.locale = :locale')
24 | ->innerJoin('o.productTaxons', 'productTaxon');
25 |
26 | $queryBuilder
27 | ->andWhere(':channel MEMBER OF o.channels')
28 | ->andWhere('o.enabled = true')
29 | ->andWhere('o.vendor = :vendor')
30 | ->setParameter('locale', $locale)
31 | ->setParameter('channel', $channel)
32 | ->setParameter('vendor', $vendor)
33 | ;
34 |
35 | if (isset($sorting['price'])) {
36 | $subQuery = $this->createQueryBuilder('m')
37 | ->select('min(v.position)')
38 | ->innerJoin('m.variants', 'v')
39 | ->andWhere('m.id = :product_id')
40 | ->andWhere('v.enabled = :enabled')
41 | ;
42 |
43 | $queryBuilder
44 | ->addSelect('variant')
45 | ->addSelect('channelPricing')
46 | ->innerJoin('o.variants', 'variant')
47 | ->innerJoin('variant.channelPricings', 'channelPricing')
48 | ->andWhere('channelPricing.channelCode = :channelCode')
49 | ->andWhere(
50 | $queryBuilder->expr()->in(
51 | 'variant.position',
52 | str_replace(':product_id', 'o.id', $subQuery->getDQL()),
53 | ),
54 | )
55 | ->setParameter('channelCode', $channel->getCode())
56 | ->setParameter('enabled', true)
57 | ;
58 | }
59 |
60 | return $queryBuilder;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/config/validation/Vendor.yaml:
--------------------------------------------------------------------------------
1 | Odiseo\SyliusVendorPlugin\Entity\Vendor:
2 | constraints:
3 | - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
4 | fields: [name]
5 | message: 'odiseo_vendor.vendor.name.unique'
6 | groups: ['odiseo']
7 | - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
8 | fields: [slug]
9 | message: 'odiseo_vendor.vendor.slug.unique'
10 | groups: ['odiseo']
11 | - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
12 | fields: [email]
13 | message: 'odiseo_vendor.vendor.email.unique'
14 | groups: ['odiseo']
15 | properties:
16 | name:
17 | - NotBlank:
18 | message: 'odiseo_vendor.vendor.name.not_blank'
19 | groups: ['odiseo']
20 | - Length:
21 | min: 2
22 | max: 250
23 | minMessage: 'odiseo_vendor.vendor.name.min_length'
24 | maxMessage: 'odiseo_vendor.vendor.name.max_length'
25 | groups: ['odiseo']
26 | slug:
27 | - NotBlank:
28 | message: 'odiseo_vendor.vendor.slug.not_blank'
29 | groups: ['odiseo']
30 | - Length:
31 | min: 2
32 | max: 250
33 | minMessage: 'odiseo_vendor.vendor.slug.min_length'
34 | maxMessage: 'odiseo_vendor.vendor.slug.max_length'
35 | groups: ['odiseo']
36 | email:
37 | - NotBlank:
38 | message: 'odiseo_vendor.vendor.email.not_blank'
39 | groups: ['odiseo']
40 | - Length:
41 | min: 2
42 | max: 250
43 | minMessage: 'odiseo_vendor.vendor.email.min_length'
44 | maxMessage: 'odiseo_vendor.vendor.email.max_length'
45 | groups: ['odiseo']
46 | - Email:
47 | message: 'odiseo_vendor.vendor.email.invalid'
48 | mode: strict
49 | groups: ['odiseo']
50 | logoFile:
51 | - NotBlank:
52 | message: 'odiseo_vendor.vendor.logo_file.not_blank'
53 | groups: ['odiseo_vendor_logo_create']
54 | translations:
55 | - Valid: ~
56 | extraEmails:
57 | - Valid: ~
58 |
--------------------------------------------------------------------------------
/tests/Unit/Twig/VendorRuntimeTest.php:
--------------------------------------------------------------------------------
1 | vendorRepository = $this->createMock(VendorRepositoryInterface::class);
24 | $this->localeContext = $this->createMock(LocaleContextInterface::class);
25 | $this->runtime = new VendorRuntime($this->vendorRepository, $this->localeContext);
26 | }
27 |
28 | public function testItIsInitializable(): void
29 | {
30 | $this->assertInstanceOf(VendorRuntime::class, $this->runtime);
31 | }
32 |
33 | public function testItGetsVendorBySlugWithProvidedLocale(): void
34 | {
35 | $vendor = new Vendor();
36 | $slug = 'test-vendor';
37 | $locale = 'en_US';
38 |
39 | $this->vendorRepository->expects($this->once())
40 | ->method('findOneBySlug')
41 | ->with($slug, $locale)
42 | ->willReturn($vendor);
43 |
44 | $this->localeContext->expects($this->never())
45 | ->method('getLocaleCode');
46 |
47 | $result = $this->runtime->getVendorBySlug($slug, $locale);
48 |
49 | $this->assertSame($vendor, $result);
50 | }
51 |
52 | public function testItGetsVendorBySlugWithContextLocale(): void
53 | {
54 | $vendor = new Vendor();
55 | $slug = 'test-vendor';
56 | $contextLocale = 'fr_FR';
57 |
58 | $this->localeContext->expects($this->once())
59 | ->method('getLocaleCode')
60 | ->willReturn($contextLocale);
61 |
62 | $this->vendorRepository->expects($this->once())
63 | ->method('findOneBySlug')
64 | ->with($slug, $contextLocale)
65 | ->willReturn($vendor);
66 |
67 | $result = $this->runtime->getVendorBySlug($slug);
68 |
69 | $this->assertSame($vendor, $result);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/templates/shop/shared/layout/_menuMobile.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ 'odiseo_vendor.ui.vendors'|trans }}
8 |
9 |
--------------------------------------------------------------------------------
/src/Entity/VendorInterface.php:
--------------------------------------------------------------------------------
1 |
52 | */
53 | public function getProducts(): Collection;
54 |
55 | public function hasProduct(ProductInterface $product): bool;
56 |
57 | public function addProduct(ProductInterface $product): void;
58 |
59 | public function removeProduct(ProductInterface $product): void;
60 |
61 | /**
62 | * @psalm-return Collection
63 | */
64 | public function getExtraEmails(): Collection;
65 |
66 | public function hasExtraEmail(VendorEmailInterface $email): bool;
67 |
68 | public function addExtraEmail(VendorEmailInterface $email): void;
69 |
70 | public function removeExtraEmail(VendorEmailInterface $email): void;
71 |
72 | public function getTranslation(?string $locale = null): TranslationInterface;
73 | }
74 |
--------------------------------------------------------------------------------
/doc/installation.md:
--------------------------------------------------------------------------------
1 | ## Installation
2 |
3 | 1. Run `composer require odiseoteam/sylius-vendor-plugin --no-scripts`
4 |
5 | 2. Enable the plugin in bundles.php
6 |
7 | ```php
8 | ['all' => true],
14 | ];
15 | ```
16 |
17 | 3. Import the plugin configurations
18 |
19 | ```yml
20 | # config/packages/odiseo_sylius_vendor_plugin.yaml
21 | imports:
22 | - { resource: "@OdiseoSyliusVendorPlugin/config/config.yaml" }
23 | ```
24 |
25 | 4. Add the shop and admin routes
26 |
27 | ```yml
28 | # config/routes/odiseo_sylius_vendor_plugin.yaml
29 | odiseo_sylius_vendor_admin:
30 | resource: "@OdiseoSyliusVendorPlugin/config/routes/admin.yaml"
31 | prefix: /admin
32 |
33 | odiseo_sylius_vendor_shop:
34 | resource: "@OdiseoSyliusVendorPlugin/config/routes/shop.yaml"
35 | prefix: /{_locale}/vendors
36 | requirements:
37 | _locale: ^[A-Za-z]{2,4}(_([A-Za-z]{4}|[0-9]{3}))?(_([A-Za-z]{2}|[0-9]{3}))?$
38 | ```
39 |
40 | 5. Include traits and override the models
41 |
42 | ```php
43 | $vendorRepository
22 | */
23 | public function __construct(
24 | protected BaseProductExampleFactory $baseProductExampleFactory,
25 | protected RepositoryInterface $vendorRepository,
26 | ) {
27 | $this->optionsResolver = new OptionsResolver();
28 | $this->configureOptions($this->optionsResolver);
29 | }
30 |
31 | public function create(array $options = []): ProductInterface
32 | {
33 | [$extracted, $remaining] = $this->extractOptions($options, $this->optionsResolver);
34 |
35 | $product = $this->baseProductExampleFactory->create($remaining);
36 |
37 | if ($product instanceof VendorAwareInterface) {
38 | $product->setVendor($extracted['vendor']);
39 | }
40 |
41 | return $product;
42 | }
43 |
44 | protected function configureOptions(OptionsResolver $resolver): void
45 | {
46 | $resolver
47 | ->setDefault('vendor', LazyOption::randomOne($this->vendorRepository))
48 | ->setAllowedTypes('vendor', ['string', VendorInterface::class])
49 | ->setNormalizer('vendor', LazyOption::findOneBy($this->vendorRepository, 'slug'))
50 | ;
51 | }
52 |
53 | private function extractOptions(array $options, OptionsResolver $resolver): array
54 | {
55 | $optionsToExtract = array_fill_keys($resolver->getDefinedOptions(), null);
56 | $extractedOptions = array_intersect_key($options, $optionsToExtract);
57 | $remainingOptions = array_diff_key($options, $optionsToExtract);
58 |
59 | return [$resolver->resolve($extractedOptions), $remainingOptions];
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Behat/Resources/services/contexts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/config/doctrine/Vendor.orm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/templates/shop/shared/layout/_menu.html.twig:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/config/app/grids/admin/vendor.yaml:
--------------------------------------------------------------------------------
1 | sylius_grid:
2 | grids:
3 | odiseo_vendor_admin_vendor:
4 | driver:
5 | name: doctrine/orm
6 | options:
7 | class: "%odiseo_vendor.model.vendor.class%"
8 | sorting:
9 | position: asc
10 | fields:
11 | logo:
12 | type: twig
13 | label: sylius.ui.image
14 | path: logoName
15 | options:
16 | template: "@OdiseoSyliusVendorPlugin/admin/vendor/grid/field/image.html.twig"
17 | name:
18 | type: string
19 | label: sylius.ui.name
20 | sortable: ~
21 | email:
22 | type: string
23 | label: sylius.ui.email
24 | sortable: ~
25 | enabled:
26 | type: twig
27 | label: sylius.ui.enabled
28 | sortable: ~
29 | options:
30 | template: "@SyliusAdmin/shared/grid/field/boolean.html.twig"
31 | position:
32 | type: twig
33 | label: sylius.ui.position
34 | path: position
35 | sortable: position
36 | options:
37 | template: "@SyliusUi/grid/field/position.html.twig"
38 | vars:
39 | th_class: "w-1 text-center"
40 | td_class: "text-center"
41 | createdAt:
42 | type: datetime
43 | label: sylius.ui.creation_date
44 | sortable: ~
45 | options:
46 | format: d-m-Y H:i
47 | filters:
48 | search:
49 | type: string
50 | label: sylius.ui.search
51 | options:
52 | fields: [name, email]
53 | form_options:
54 | type: !php/const Sylius\Component\Grid\Filter\StringFilter::TYPE_CONTAINS
55 | enabled:
56 | type: boolean
57 | label: sylius.ui.enabled
58 | createdAt:
59 | type: date
60 | label: sylius.ui.creation_date
61 | actions:
62 | main:
63 | create:
64 | type: create
65 | item:
66 | update:
67 | type: update
68 | delete:
69 | type: delete
70 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: run
2 |
3 | DOCKER_COMPOSE ?= docker compose
4 | DOCKER_USER ?= "$(shell id -u):$(shell id -g)"
5 | ENV ?= "dev"
6 |
7 | init:
8 | @make -s docker-compose-check
9 | @if [ ! -e compose.override.yml ]; then \
10 | cp compose.override.dist.yml compose.override.yml; \
11 | fi
12 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php composer install --no-interaction --no-scripts --no-plugins
13 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm nodejs
14 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) up -d
15 |
16 | run:
17 | @make -s up
18 |
19 | debug:
20 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) -f compose.yml -f compose.override.yml -f compose.debug.yml up -d
21 |
22 | up:
23 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) up -d
24 |
25 | down:
26 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) down
27 |
28 | clean:
29 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) down -v
30 |
31 | php-shell:
32 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) exec php sh
33 |
34 | node-shell:
35 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm -i nodejs sh
36 |
37 | node-watch:
38 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm -i nodejs "npm run watch"
39 |
40 | docker-compose-check:
41 | @$(DOCKER_COMPOSE) version >/dev/null 2>&1 || (echo "Please install docker compose binary or set DOCKER_COMPOSE=\"docker-compose\" for legacy binary" && exit 1)
42 | @echo "You are using \"$(DOCKER_COMPOSE)\" binary"
43 | @echo "Current version is \"$$($(DOCKER_COMPOSE) version)\""
44 |
45 | database-init:
46 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/console doctrine:database:create -n --if-not-exists
47 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/console doctrine:migrations:migrate -n
48 |
49 | database-reset:
50 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/console doctrine:database:drop -n --force --if-exists
51 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/console doctrine:database:create -n
52 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/console doctrine:migrations:migrate -n
53 |
54 | load-fixtures:
55 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/console sylius:fixtures:load -n
56 |
57 | phpstan:
58 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/phpstan analyse -c phpstan.neon
59 |
60 | ecs:
61 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/ecs check src tests
62 |
63 | phpunit:
64 | @ENV=$(ENV) DOCKER_USER=$(DOCKER_USER) $(DOCKER_COMPOSE) run --rm php vendor/bin/phpunit
65 |
66 | behat:
67 | @ENV=$(ENV) DOCKER_USER=root $(DOCKER_COMPOSE) run --rm php vendor/bin/behat
68 |
--------------------------------------------------------------------------------
/COMPATIBILITY_GUIDE.md:
--------------------------------------------------------------------------------
1 | # Compatibility With Sylius Guide for AI
2 |
3 | This guide explains how to configure the Plugin to work with different versions of Sylius (1.14, 2.0, and 2.1).
4 |
5 | ## Supported Sylius Versions
6 |
7 | - Sylius 1.14.x
8 | - Sylius 2.0.x
9 | - Sylius 2.1.x
10 |
11 | ## Key Compatibility Changes
12 |
13 | ### 1. Composer Configuration
14 |
15 | To support multiple Sylius versions, update your `composer.json`:
16 |
17 | ```json
18 | {
19 | "require": {
20 | "php": "^8.1",
21 | "sylius/sylius": "^1.14 || ^2.0 || ^2.1"
22 | },
23 | "require-dev": {
24 | "sylius/sylius-rector": "^1.14 || ^2.0",
25 | "sylius/test-application": "^1.14.0@alpha || ^2.0.0@alpha || ^2.1.0@alpha",
26 | "symfony/webpack-encore-bundle": "^1.15 || ^2.2"
27 | },
28 | "prefer-stable": true
29 | }
30 | ```
31 |
32 | **Important notes:**
33 | - PHP 8.1 is the minimum requirement for cross-version compatibility
34 | - Use `prefer-stable: true` to prefer stable packages when available
35 | - Use version constraints with `||` operator to support multiple versions
36 |
37 | #### For Sylius 2.0 and 2.1:
38 |
39 | In Sylius 2.0+, these properties are provided by the parent class, so you can use them directly without initialization.
40 |
41 | ### 2. Testing Across Versions
42 |
43 | To test your plugin with different Sylius versions:
44 |
45 | 1. **Update composer.json for specific version:**
46 | ```bash
47 | # For Sylius 1.14
48 | composer require "sylius/sylius:~1.14.0"
49 |
50 | # For Sylius 2.0
51 | composer require "sylius/sylius:~2.0.0"
52 |
53 | # For Sylius 2.1
54 | composer require "sylius/sylius:~2.1.0"
55 | ```
56 |
57 | 2. **Run tests:**
58 | ```bash
59 | vendor/bin/phpunit
60 | vendor/bin/phpspec run
61 | vendor/bin/behat
62 | ```
63 |
64 | ## Best Practices
65 |
66 | 1. **Use Conservative Constraints**: When supporting multiple versions, use the most conservative approach that works across all versions.
67 |
68 | 2. **Test Thoroughly**: Always test your plugin with each supported Sylius version before release.
69 |
70 | 3. **Document Version-Specific Features**: If certain features only work with specific Sylius versions, document this clearly.
71 |
72 | 4. **Use CI/CD**: Set up GitHub Actions or other CI tools to test against all supported versions automatically.
73 |
74 | ## Troubleshooting
75 |
76 | ### Common Issues
77 |
78 | 1. **Service not found errors**: Check that service IDs haven't changed between versions.
79 |
80 | 2. **Namespace conflicts**: Use fully qualified class names when there's ambiguity.
81 |
82 | 3. **Dependency conflicts**: Use `composer why-not` to debug version constraint issues.
83 |
84 | ## Conclusion
85 |
86 | By following this guide, your plugin should work seamlessly across Sylius 1.14, 2.0, and 2.1. The key is understanding the differences between versions and implementing conditional logic where necessary while maintaining a clean, maintainable codebase.
87 |
--------------------------------------------------------------------------------
/src/Migrations/Version20250406024705.php:
--------------------------------------------------------------------------------
1 | addSql(<<<'SQL'
24 | CREATE TABLE odiseo_vendors_channels (vendor_id INT NOT NULL, channel_id INT NOT NULL, INDEX IDX_C2DB56EDF603EE73 (vendor_id), INDEX IDX_C2DB56ED72F5A1AA (channel_id), PRIMARY KEY(vendor_id, channel_id)) DEFAULT CHARACTER SET UTF8 COLLATE `UTF8_unicode_ci` ENGINE = InnoDB
25 | SQL);
26 | $this->addSql(<<<'SQL'
27 | ALTER TABLE odiseo_vendors_channels ADD CONSTRAINT FK_C2DB56EDF603EE73 FOREIGN KEY (vendor_id) REFERENCES odiseo_vendor (id)
28 | SQL);
29 | $this->addSql(<<<'SQL'
30 | ALTER TABLE odiseo_vendors_channels ADD CONSTRAINT FK_C2DB56ED72F5A1AA FOREIGN KEY (channel_id) REFERENCES sylius_channel (id)
31 | SQL);
32 | $this->addSql(<<<'SQL'
33 | ALTER TABLE odiseo_vendor_channels DROP FOREIGN KEY FK_42A3C6D272F5A1AA
34 | SQL);
35 | $this->addSql(<<<'SQL'
36 | ALTER TABLE odiseo_vendor_channels DROP FOREIGN KEY FK_42A3C6D2F603EE73
37 | SQL);
38 | $this->addSql(<<<'SQL'
39 | DROP TABLE odiseo_vendor_channels
40 | SQL);
41 | }
42 |
43 | public function down(Schema $schema): void
44 | {
45 | // this down() migration is auto-generated, please modify it to your needs
46 | $this->addSql(<<<'SQL'
47 | CREATE TABLE odiseo_vendor_channels (channel_id INT NOT NULL, vendor_id INT NOT NULL, INDEX IDX_42A3C6D272F5A1AA (channel_id), INDEX IDX_42A3C6D2F603EE73 (vendor_id), PRIMARY KEY(channel_id, vendor_id)) DEFAULT CHARACTER SET utf8mb3 COLLATE `utf8mb3_unicode_ci` ENGINE = InnoDB COMMENT = ''
48 | SQL);
49 | $this->addSql(<<<'SQL'
50 | ALTER TABLE odiseo_vendor_channels ADD CONSTRAINT FK_42A3C6D272F5A1AA FOREIGN KEY (channel_id) REFERENCES sylius_channel (id) ON UPDATE NO ACTION ON DELETE NO ACTION
51 | SQL);
52 | $this->addSql(<<<'SQL'
53 | ALTER TABLE odiseo_vendor_channels ADD CONSTRAINT FK_42A3C6D2F603EE73 FOREIGN KEY (vendor_id) REFERENCES odiseo_vendor (id) ON UPDATE NO ACTION ON DELETE NO ACTION
54 | SQL);
55 | $this->addSql(<<<'SQL'
56 | ALTER TABLE odiseo_vendors_channels DROP FOREIGN KEY FK_C2DB56EDF603EE73
57 | SQL);
58 | $this->addSql(<<<'SQL'
59 | ALTER TABLE odiseo_vendors_channels DROP FOREIGN KEY FK_C2DB56ED72F5A1AA
60 | SQL);
61 | $this->addSql(<<<'SQL'
62 | DROP TABLE odiseo_vendors_channels
63 | SQL);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Unit/Menu/AdminMenuListenerTest.php:
--------------------------------------------------------------------------------
1 | listener = new AdminMenuListener();
19 | }
20 |
21 | public function testItIsInitializable(): void
22 | {
23 | $this->assertInstanceOf(AdminMenuListener::class, $this->listener);
24 | }
25 |
26 | public function testItAddsMenuItemToCatalogChild(): void
27 | {
28 | $factory = $this->createMock(\Knp\Menu\FactoryInterface::class);
29 | $menu = $this->createMock(ItemInterface::class);
30 | $catalogItem = $this->createMock(ItemInterface::class);
31 | $vendorsItem = $this->createMock(ItemInterface::class);
32 | $event = new MenuBuilderEvent($factory, $menu);
33 |
34 | $menu->expects($this->once())
35 | ->method('getChild')
36 | ->with('catalog')
37 | ->willReturn($catalogItem);
38 |
39 | $catalogItem->expects($this->once())
40 | ->method('addChild')
41 | ->with('vendors', ['route' => 'odiseo_vendor_admin_vendor_index'])
42 | ->willReturn($vendorsItem);
43 |
44 | $vendorsItem->expects($this->once())
45 | ->method('setLabel')
46 | ->with('odiseo_vendor.menu.admin.vendors')
47 | ->willReturnSelf();
48 |
49 | $vendorsItem->expects($this->once())
50 | ->method('setLabelAttribute')
51 | ->with('icon', 'trademark')
52 | ->willReturnSelf();
53 |
54 | $this->listener->addAdminMenuItems($event);
55 | }
56 |
57 | public function testItAddsMenuItemToMainMenuWhenCatalogNotFound(): void
58 | {
59 | $factory = $this->createMock(\Knp\Menu\FactoryInterface::class);
60 | $menu = $this->createMock(ItemInterface::class);
61 | $vendorsItem = $this->createMock(ItemInterface::class);
62 | $event = new MenuBuilderEvent($factory, $menu);
63 |
64 | $menu->expects($this->once())
65 | ->method('getChild')
66 | ->with('catalog')
67 | ->willReturn(null);
68 |
69 | $menu->expects($this->once())
70 | ->method('addChild')
71 | ->with('vendors', ['route' => 'odiseo_vendor_admin_vendor_index'])
72 | ->willReturn($vendorsItem);
73 |
74 | $vendorsItem->expects($this->once())
75 | ->method('setLabel')
76 | ->with('odiseo_vendor.menu.admin.vendors')
77 | ->willReturnSelf();
78 |
79 | $vendorsItem->expects($this->once())
80 | ->method('setLabelAttribute')
81 | ->with('icon', 'trademark')
82 | ->willReturnSelf();
83 |
84 | $this->listener->addAdminMenuItems($event);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/doc/tests.md:
--------------------------------------------------------------------------------
1 | ## Test the plugin
2 |
3 | We are using PHPStan, Psalm, Behat, PHPSpec and PHPUnit to test this plugin.
4 |
5 | ### How to run the tests
6 |
7 | From the plugin root directory, run the following commands:
8 |
9 | ```bash
10 | $ (cd tests/Application && yarn install)
11 | $ (cd tests/Application && yarn build)
12 | $ (cd tests/Application && APP_ENV=test bin/console assets:install public)
13 |
14 | $ (cd tests/Application && APP_ENV=test bin/console doctrine:database:create)
15 | $ (cd tests/Application && APP_ENV=test bin/console doctrine:schema:create)
16 | ```
17 |
18 | To be able to setup a plugin's database, remember to configure you database credentials in `tests/Application/.env` and `tests/Application/.env.test`.
19 |
20 | ## Usage
21 |
22 | ### Running plugin tests
23 |
24 | - PHPUnit
25 |
26 | ```bash
27 | $ vendor/bin/phpunit
28 | ```
29 |
30 | - PHPSpec
31 |
32 | ```bash
33 | $ vendor/bin/phpspec run
34 | ```
35 |
36 | - Behat (non-JS scenarios)
37 |
38 | ```bash
39 | $ vendor/bin/behat --strict --tags="~@javascript"
40 | ```
41 |
42 | - Behat (JS scenarios)
43 |
44 | 1. [Install Symfony CLI command](https://symfony.com/download).
45 |
46 | 2. Start Headless Chrome:
47 |
48 | ```bash
49 | $ google-chrome-stable --enable-automation --disable-background-networking --no-default-browser-check --no-first-run --disable-popup-blocking --disable-default-apps --allow-insecure-localhost --disable-translate --disable-extensions --no-sandbox --enable-features=Metal --headless --remote-debugging-port=9222 --window-size=2880,1800 --proxy-server='direct://' --proxy-bypass-list='*' http://127.0.0.1
50 | ```
51 |
52 | 3. Install SSL certificates (only once needed) and run test application's webserver on `127.0.0.1:8080`:
53 |
54 | ```bash
55 | $ symfony server:ca:install APP_ENV=test symfony server:start --port=8080 --dir=tests/Application/public --daemon
56 | ```
57 |
58 | 4. Run Behat:
59 |
60 | ```bash
61 | $ vendor/bin/behat --strict --tags="@javascript"
62 | ```
63 |
64 | - Static Analysis
65 |
66 | - Psalm
67 |
68 | ```bash
69 | $ vendor/bin/psalm
70 | ```
71 |
72 | - PHPStan
73 |
74 | ```bash
75 | $ vendor/bin/phpstan analyse -c phpstan.neon -l max src/
76 | ```
77 |
78 | - Coding Standard
79 |
80 | ```bash
81 | $ vendor/bin/phpcs
82 | ```
83 |
84 | ```bash
85 | $ vendor/bin/ecs check src
86 | ```
87 |
88 | ### Opening Sylius with this plugin
89 |
90 | - Using `test` environment:
91 |
92 | ```bash
93 | $ (cd tests/Application && APP_ENV=test bin/console sylius:fixtures:load vendor)
94 | $ (cd tests/Application && APP_ENV=test symfony server:start --document-root=public -d)
95 | ```
96 |
97 | - Using `dev` environment:
98 |
99 | ```bash
100 | $ (cd tests/Application && APP_ENV=dev bin/console sylius:fixtures:load vendor)
101 | $ (cd tests/Application && APP_ENV=dev symfony server:start --document-root=public -d)
102 | ```
103 |
--------------------------------------------------------------------------------
/templates/shop/vendor/index/content/filters/sorting.html.twig:
--------------------------------------------------------------------------------
1 | {% if hookable_metadata.context.resources.data.nbResults > 0 %}
2 | {% set route = app.request.attributes.get('_route') %}
3 | {% set route_parameters = app.request.attributes.all('_route_params')|merge(app.request.query.all) %}
4 |
5 | {% set criteria = app.request.query.all('criteria') %}
6 |
7 | {% set default_path = path(route, route_parameters|merge({'sorting': null, 'criteria': criteria})) %}
8 | {% set from_a_to_z_path = path(route, route_parameters|merge({'sorting': {'name': 'asc'}, 'criteria': criteria})) %}
9 | {% set from_z_to_a_path = path(route, route_parameters|merge({'sorting': {'name': 'desc'}, 'criteria': criteria})) %}
10 | {% set oldest_first_path = path(route, route_parameters|merge({'sorting': {'createdAt': 'asc'}, 'criteria': criteria})) %}
11 | {% set newest_first_path = path(route, route_parameters|merge({'sorting': {'createdAt': 'desc'}, 'criteria': criteria})) %}
12 |
13 | {% if app.request.query.all()['sorting'] is not defined or app.request.query.all()['sorting'] is empty %}
14 | {% set current_sorting_label = 'sylius.ui.by_position'|trans|lower %}
15 | {% elseif app.request.query.all()['sorting'].name is defined and app.request.query.all()['sorting'].name == 'asc'%}
16 | {% set current_sorting_label = 'sylius.ui.from_a_to_z'|trans|lower %}
17 | {% elseif app.request.query.all()['sorting'].name is defined and app.request.query.all()['sorting'].name == 'desc'%}
18 | {% set current_sorting_label = 'sylius.ui.from_z_to_a'|trans|lower %}
19 | {% elseif app.request.query.all()['sorting'].createdAt is defined and app.request.query.all()['sorting'].createdAt == 'desc'%}
20 | {% set current_sorting_label = 'sylius.ui.newest_first'|trans|lower %}
21 | {% elseif app.request.query.all()['sorting'].createdAt is defined and app.request.query.all()['sorting'].createdAt == 'asc'%}
22 | {% set current_sorting_label = 'sylius.ui.oldest_first'|trans|lower %}
23 | {% endif %}
24 |
25 |
26 |
27 | {{ 'sylius.ui.sort'|trans }}: {{ current_sorting_label }}
28 |
29 |
36 |
37 | {% endif %}
38 |
--------------------------------------------------------------------------------
/compose.override.dist.yml:
--------------------------------------------------------------------------------
1 | services:
2 | php:
3 | image: ghcr.io/sylius/sylius-php:8.3-fixuid-xdebug-alpine
4 | user: ${DOCKER_USER:-1000:1000}
5 | depends_on:
6 | mysql:
7 | condition: service_healthy
8 | environment:
9 | # You can move these environment variables to your .env.local file
10 | APP_ENV: ${ENV:-prod}
11 | APP_SECRET: EDITME
12 | DATABASE_URL: "mysql://root@mysql/sylius_%kernel.environment%"
13 | MAILER_DSN: smtp://mailhog:1025
14 | MESSENGER_TRANSPORT_DSN: doctrine://default
15 | SYLIUS_MESSENGER_TRANSPORT_MAIN_DSN: doctrine://default
16 | SYLIUS_MESSENGER_TRANSPORT_MAIN_FAILED_DSN: doctrine://default?queue_name=main_failed
17 | SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_DSN: doctrine://default?queue_name=catalog_promotion_removal
18 | SYLIUS_MESSENGER_TRANSPORT_CATALOG_PROMOTION_REMOVAL_FAILED_DSN: doctrine://default?queue_name=catalog_promotion_removal_failed
19 | SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_DSN: sync://
20 | SYLIUS_MESSENGER_TRANSPORT_PAYMENT_REQUEST_FAILED_DSN: sync://
21 | PHP_DATE_TIMEZONE: ${PHP_DATE_TIMEZONE:-UTC}
22 | XDEBUG_MODE: debug
23 | XDEBUG_CONFIG: >-
24 | client_host=host.docker.internal
25 | client_port=9003
26 | # This should correspond to the server declared in PHPStorm `Preferences | Languages & Frameworks | PHP | Servers`
27 | # Then PHPStorm will use the corresponding path mappings
28 | PHP_IDE_CONFIG: serverName=sylius
29 | extra_hosts:
30 | - "host.docker.internal:host-gateway"
31 | volumes:
32 | - .:/srv/sylius:rw,cached
33 | # if you develop on Linux, you may use a bind-mounted host directory instead
34 | # - ./var:/srv/sylius/var:rw
35 | - ./public:/srv/sylius/public:rw,delegated
36 | # if you develop on Linux, you may use a bind-mounted host directory instead
37 | # - ./public/media:/srv/sylius/public/media:rw
38 | - public-media:/srv/sylius/public/media:rw
39 | mysql:
40 | volumes:
41 | - mysql-data:/var/lib/mysql:rw
42 | ports:
43 | - "3306:3306"
44 | nginx:
45 | environment:
46 | WORKING_DIR: /srv/sylius/vendor/sylius/test-application
47 | volumes:
48 | - .:/srv/sylius:ro
49 | # if you develop on Linux, you may use a bind-mounted host directory instead
50 | # - ./public/media:/srv/sylius/public/media:ro
51 | ports:
52 | - "80:80"
53 | nodejs:
54 | image: node:${NODE_VERSION:-20}-alpine
55 | user: ${DOCKER_USER:-1000:1000}
56 | working_dir: /srv/sylius
57 | entrypoint: [ "/bin/sh","-c" ]
58 | command:
59 | - |
60 | cd vendor/sylius/test-application
61 | yarn install
62 | yarn build
63 | volumes:
64 | - .:/srv/sylius:rw,cached
65 | - ./public:/srv/sylius/public:rw,delegated
66 | mailhog:
67 | ports:
68 | - "8025:8025"
69 |
70 | volumes:
71 | mysql-data:
72 | public-media:
73 |
--------------------------------------------------------------------------------