├── 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 | {{ data }} 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 | 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 | 11 | -------------------------------------------------------------------------------- /templates/admin/vendor/form/side_navigation/profile.html.twig: -------------------------------------------------------------------------------- 1 | 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 |
5 |

6 | {{ 'odiseo_vendor.ui.vendor'|trans }}: 7 | 8 | {{ product.vendor.name }} 9 | 10 |

11 |
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 | {{ vendor.name }} 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 |
5 |
6 |
7 |

8 | {{ 'sylius.ui.media'|trans }} 9 |

10 |
11 |
12 | {% hook 'media' with { vendor } %} 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /templates/admin/vendor/form/sections/general.html.twig: -------------------------------------------------------------------------------- 1 | {% set form = hookable_metadata.context.form %} 2 | {% set vendor = form.vars.data %} 3 | 4 |
5 |
6 |
7 |

8 | {{ 'sylius.ui.general'|trans }} 9 |

10 |
11 |
12 | {% hook 'general' with { vendor } %} 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /templates/admin/vendor/form/sections/profile.html.twig: -------------------------------------------------------------------------------- 1 | {% set form = hookable_metadata.context.form %} 2 | {% set vendor = form.vars.data %} 3 | 4 |
5 |
6 |
7 |

8 | {{ 'sylius.ui.profile'|trans }} 9 |

10 |
11 |
12 | {% hook 'profile' with { vendor } %} 13 |
14 |
15 |
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 | {{ vendor.name }} 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 |
2 |
3 | {% set vendors = hookable_metadata.context.vendors %} 4 | 5 | {% for filter in vendors.definition.enabledFilters %} 6 | {{ sylius_grid_render_filter(vendors, filter) }} 7 | {% endfor %} 8 | 11 | 12 | {{ ux_icon('tabler:x') }} 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /templates/shop/vendor/index/content/filters/limit.html.twig: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /templates/shop/vendor/show/content/filters/limit.html.twig: -------------------------------------------------------------------------------- 1 | 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 |
2 |
3 | {% set products = hookable_metadata.context.products %} 4 | 5 | {% for filter in products.definition.enabledFilters %} 6 | {{ sylius_grid_render_filter(products, filter) }} 7 | {% endfor %} 8 | 11 | 12 | {{ ux_icon('tabler:x') }} 13 | 14 |
15 |
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 | 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 | 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 |
2 |
3 | 13 |
14 |
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 | 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 | --------------------------------------------------------------------------------