mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
Upmerge 1.12 into 1.13
This commit is contained in:
commit
7df78676c4
44 changed files with 713 additions and 75 deletions
|
|
@ -1,18 +0,0 @@
|
|||
@managing_countries
|
||||
Feature: Province unique code validation
|
||||
In order to uniquely identify provinces
|
||||
As an Administrator
|
||||
I want to be prevented from adding two provinces with the same code
|
||||
|
||||
Background:
|
||||
Given the store has country "United Kingdom"
|
||||
And this country has the "Northern Ireland" province with "GB-NIR" code
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @javascript @api
|
||||
Scenario: Trying to add a new province with taken code
|
||||
When I want to edit this country
|
||||
And I add the "Scotland" province with "GB-NIR" code
|
||||
And I try to save my changes
|
||||
Then I should be notified that province code must be unique
|
||||
And province with name "Scotland" should not be added in this country
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
@managing_countries
|
||||
Feature: Province unique fields validation
|
||||
In order to uniquely identify provinces
|
||||
As an Administrator
|
||||
I want to be prevented from adding two provinces with the same code or name
|
||||
|
||||
Background:
|
||||
Given the store has country "United Kingdom"
|
||||
And this country has the "Northern Ireland" province with "GB-NIR" code
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui @javascript @api
|
||||
Scenario: Trying to add a new province with a taken code
|
||||
When I want to add a new country
|
||||
And I choose "Gibraltar"
|
||||
And I add the "Scotland" province with "GB-NIR" code
|
||||
And I try to add it
|
||||
Then I should be notified that province code must be unique
|
||||
|
||||
@ui @javascript @api
|
||||
Scenario: Trying to add a new province with a taken name
|
||||
When I want to edit this country
|
||||
And I add the "Northern Ireland" province with "GB-NI" code
|
||||
And I try to save changes
|
||||
Then I should be notified that province name must be unique
|
||||
|
||||
@ui @javascript @api
|
||||
Scenario: Trying to add new provinces with duplicated codes
|
||||
When I want to edit this country
|
||||
And I add the "Scotland" province with "GB-SCO" code
|
||||
And I add the "Not Scotland" province with "GB-SCO" code
|
||||
And I try to save changes
|
||||
Then I should be notified that all province codes and names within this country need to be unique
|
||||
|
||||
@ui @javascript @api
|
||||
Scenario: Trying to add new provinces with duplicated names
|
||||
When I want to edit this country
|
||||
And I add the "Scotland" province with "GB-SC" code
|
||||
And I add the "Scotland" province with "GB-SCO" code
|
||||
And I try to save changes
|
||||
Then I should be notified that all province codes and names within this country need to be unique
|
||||
|
|
@ -7,12 +7,21 @@ Feature: Adding product review as a customer
|
|||
Background:
|
||||
Given the store operates on a single channel in "United States"
|
||||
And the store has a product "Necronomicon"
|
||||
And I am a logged in customer
|
||||
|
||||
@ui @api
|
||||
Scenario: Adding product reviews as a logged in customer
|
||||
Given I am a logged in customer
|
||||
When I want to review product "Necronomicon"
|
||||
And I leave a comment "Great book for every advanced sorcerer.", titled "Scary but astonishing"
|
||||
And I rate it with 5 points
|
||||
And I add it
|
||||
Then I should be notified that my review is waiting for the acceptation
|
||||
|
||||
@ui
|
||||
Scenario: Adding product reviews as a logged in customer with remember me option
|
||||
Given I am a logged in customer by using remember me option
|
||||
When I want to review product "Necronomicon"
|
||||
And I leave a comment "Great book for every advanced sorcerer.", titled "Scary but astonishing"
|
||||
And I rate it with 3 points
|
||||
And I add it
|
||||
Then I should be notified that my review is waiting for the acceptation
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ Feature: Adding a new product
|
|||
Then I should be notified that it has been successfully created
|
||||
And the product "Dice Brewing" should appear in the store
|
||||
|
||||
@ui
|
||||
@ui @javascript
|
||||
Scenario: Adding a new configurable product
|
||||
Given the store has a product option "Bottle size" with a code "bottle_size"
|
||||
And this product option has the "0.7" option value with code "bottle_size_medium"
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ Feature: Editing a product
|
|||
Then I should be notified that it has been successfully edited
|
||||
And this product name should be "Sobieski Vodka"
|
||||
|
||||
@ui @api
|
||||
@ui @javascript @api
|
||||
Scenario: Changing options of configurable product without any variant defined
|
||||
Given the store has a "Marvel's T-Shirt" configurable product
|
||||
And the store has a product option "T-Shirt size" with a code "t_shirt_size"
|
||||
|
|
|
|||
|
|
@ -34,6 +34,16 @@ Feature: Editing taxon's slug
|
|||
And I add it
|
||||
Then this taxon slug should be "medieval-weapons/siege-engines"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Creating a child taxon with autogenerated slug by setting the parent
|
||||
Given the store has "Medieval weapons" taxonomy
|
||||
When I want to create a new taxon
|
||||
And I specify its code as "PIKES"
|
||||
And I name it "Pikes" in "English (United States)"
|
||||
And I set its parent taxon to "Medieval weapons"
|
||||
And I add it
|
||||
Then this taxon slug should be "medieval-weapons/pikes"
|
||||
|
||||
@ui
|
||||
Scenario: Creating a child taxon with a custom slug
|
||||
Given the store has "Medieval weapons" taxonomy
|
||||
|
|
@ -51,13 +61,32 @@ Feature: Editing taxon's slug
|
|||
Then the slug field should not be editable
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Prevent from editing a slug while changing a taxon name
|
||||
Scenario: Prevent from editing the slug while changing a taxon name
|
||||
Given the store has "Medieval weapons" taxonomy
|
||||
When I want to modify the "Medieval weapons" taxon
|
||||
And I rename it to "Renaissance weapons" in "English (United States)"
|
||||
And I save my changes
|
||||
Then the slug of the "Renaissance weapons" taxon should still be "medieval-weapons"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Prevent from editing the slug while setting the taxon's parent
|
||||
Given the store has "Medieval weapons" taxonomy
|
||||
And the store has "Pikes" taxonomy
|
||||
When I want to modify the "Pikes" taxon
|
||||
And I set its parent taxon to "Medieval weapons"
|
||||
And I save my changes
|
||||
Then the slug of the "Pikes" taxon should still be "pikes"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Prevent from editing the slug while changing the taxon's parent
|
||||
Given the store has "Medieval weapons" taxonomy
|
||||
Given the store has "Renaissance weapons" taxonomy
|
||||
And the "Medieval weapons" taxon has child taxon "Pikes"
|
||||
When I want to modify the "Pikes" taxon
|
||||
And I change its parent taxon to "Renaissance weapons"
|
||||
And I save my changes
|
||||
Then the slug of the "Pikes" taxon should still be "medieval-weapons/pikes"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Automatically changing a root taxon's slug while editing a taxon's name
|
||||
Given the store has "Medieval weapons" taxonomy
|
||||
|
|
@ -87,6 +116,29 @@ Feature: Editing taxon's slug
|
|||
And I save my changes
|
||||
Then the slug of the "Renaissance weapons" taxon should be "renaissance"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Automatically changing a child taxon's slug when changing the parent
|
||||
Given the store has "Renaissance weapons" taxonomy
|
||||
And the store has "Medieval weapons" taxonomy
|
||||
And the "Medieval weapons" taxon has child taxon "Pikes"
|
||||
When I want to modify the "Pikes" taxon
|
||||
And I enable slug modification
|
||||
And I change its parent taxon to "Renaissance weapons"
|
||||
And I save my changes
|
||||
Then the slug of the "Pikes" taxon should be "renaissance-weapons/pikes"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Manually changing a child taxon's slug when changing the parent
|
||||
Given the store has "Renaissance weapons" taxonomy
|
||||
And the store has "Medieval weapons" taxonomy
|
||||
And the "Medieval weapons" taxon has child taxon "Pikes"
|
||||
When I want to modify the "Pikes" taxon
|
||||
And I enable slug modification
|
||||
And I change its parent taxon to "Renaissance weapons"
|
||||
And I set its slug to "pikes" in "English (United States)"
|
||||
And I save my changes
|
||||
Then the slug of the "Pikes" taxon should be "pikes"
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Manually changing a child taxon's slug while editing a taxon's name
|
||||
Given the store has "Medieval weapons" taxonomy
|
||||
|
|
|
|||
|
|
@ -70,6 +70,11 @@ parameters:
|
|||
count: 1
|
||||
path: src/Sylius/Bundle/AddressingBundle/Validator/Constraints/ProvinceAddressConstraintValidator.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AddressingBundle\\\\Validator\\\\Constraints\\\\UniqueProvinceCollectionValidator\\:\\:validate\\(\\) has parameter \\$value with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/AddressingBundle/Validator/Constraints/UniqueProvinceCollectionValidator.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\AddressingBundle\\\\Validator\\\\Constraints\\\\ZoneCannotContainItselfValidator\\:\\:validate\\(\\) has parameter \\$value with no type specified\\.$#"
|
||||
count: 1
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ final class ManagingCountriesContext implements Context
|
|||
|
||||
/**
|
||||
* @When I add it
|
||||
* @When I try to add it
|
||||
*/
|
||||
public function iAddIt(): void
|
||||
{
|
||||
|
|
@ -292,13 +293,24 @@ final class ManagingCountriesContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that province code must be unique
|
||||
* @Then /^I should be notified that province (code|name) must be unique$/
|
||||
*/
|
||||
public function iShouldBeNotifiedThatProvinceCodeMustBeUnique(): void
|
||||
public function iShouldBeNotifiedThatProvinceCodeMustBeUnique(string $field): void
|
||||
{
|
||||
Assert::same(
|
||||
Assert::regex(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'provinces[1].code: Province code must be unique.',
|
||||
sprintf('/provinces\[[\d+]\]\.%1$s: Province %1$s must be unique\./', $field),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that all province codes and names within this country need to be unique
|
||||
*/
|
||||
public function iShouldBeNotifiedThatAllProvinceCodesAndNamesWithinThisCountryNeedToBeUnique(): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'provinces: All provinces within this country need to have unique codes and names.',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,4 +67,17 @@ final class ShopSecurityContext implements Context
|
|||
|
||||
$this->sharedStorage->set('user', $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given I am a logged in customer by using remember me option
|
||||
*/
|
||||
public function iAmLoggedInCustomerByUsingRememberMeOption(): void
|
||||
{
|
||||
$userData = ['email' => 'sylius@example.com', 'password' => 'sylius', 'enabled' => true];
|
||||
|
||||
$user = $this->userFactory->create($userData);
|
||||
$this->userRepository->add($user);
|
||||
|
||||
$this->securityService->logInWithRememberMe($user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ final class TaxonomyContext implements Context
|
|||
public function theTaxonHasChildrenTaxonAnd(TaxonInterface $taxon, string ...$taxonsNames): void
|
||||
{
|
||||
foreach ($taxonsNames as $taxonName) {
|
||||
$taxon->addChild($this->createTaxon($taxonName));
|
||||
$taxon->addChild($this->createChildTaxon($taxonName, $taxon));
|
||||
}
|
||||
|
||||
$this->objectManager->persist($taxon);
|
||||
|
|
@ -158,6 +158,15 @@ final class TaxonomyContext implements Context
|
|||
return $taxon;
|
||||
}
|
||||
|
||||
private function createChildTaxon(string $name, TaxonInterface $parent): TaxonInterface
|
||||
{
|
||||
$child = $this->createTaxon($name);
|
||||
$child->setParent($parent);
|
||||
$child->setSlug($this->taxonSlugGenerator->generate($child));
|
||||
|
||||
return $child;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TaxonInterface
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ final class ManagingCountriesContext implements Context
|
|||
|
||||
/**
|
||||
* @When I add it
|
||||
* @When I try to add it
|
||||
*/
|
||||
public function iAddIt()
|
||||
{
|
||||
|
|
@ -265,11 +266,22 @@ final class ManagingCountriesContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that province code must be unique
|
||||
* @Then /^I should be notified that province (code|name) must be unique$/
|
||||
*/
|
||||
public function iShouldBeNotifiedThatProvinceCodeMustBeUnique(): void
|
||||
public function iShouldBeNotifiedThatProvinceCodeMustBeUnique(string $field): void
|
||||
{
|
||||
Assert::same($this->updatePage->getValidationMessage('code'), 'Province code must be unique.');
|
||||
Assert::same($this->updatePage->getValidationMessage($field), sprintf('Province %s must be unique.', $field));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that all province codes and names within this country need to be unique
|
||||
*/
|
||||
public function iShouldBeNotifiedThatAllProvinceCodesAndNamesWithinThisCountryNeedToBeUnique(): void
|
||||
{
|
||||
Assert::inArray(
|
||||
'All provinces within this country need to have unique codes and names.',
|
||||
$this->updatePage->getFormValidationErrors(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ final class ManagingTaxonsContext implements Context
|
|||
{
|
||||
$this->updatePage->open(['id' => $taxon->getId()]);
|
||||
|
||||
Assert::true($this->updatePage->hasResourceValues(['slug' => $slug]));
|
||||
Assert::same($this->updatePage->getSlug(), $slug);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -105,6 +105,13 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
|
|||
$provinceForm->fillField('Code', $code);
|
||||
}
|
||||
|
||||
public function getFormValidationErrors(): array
|
||||
{
|
||||
$errors = $this->getElement('form')->findAll('css', '.sylius-validation-error:not(.pointing)');
|
||||
|
||||
return array_map(fn (NodeElement $element) => $element->getText(), $errors);
|
||||
}
|
||||
|
||||
public function getValidationMessage(string $element): string
|
||||
{
|
||||
$provinceForm = $this->getLastProvinceElement();
|
||||
|
|
@ -127,6 +134,7 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
|
|||
return array_merge(parent::getDefinedElements(), [
|
||||
'code' => '#sylius_country_code',
|
||||
'enabled' => '#sylius_country_enabled',
|
||||
'form' => 'form',
|
||||
'provinces' => '#sylius_country_provinces',
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,4 +38,7 @@ interface UpdatePageInterface extends BaseUpdatePageInterface
|
|||
public function removeProvinceName(string $provinceName): void;
|
||||
|
||||
public function specifyProvinceCode(string $provinceCode): void;
|
||||
|
||||
/** @return array<array-key, string> */
|
||||
public function getFormValidationErrors(): array;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ class CreateConfigurableProductPage extends BaseCreatePage implements CreateConf
|
|||
public function hasMainTaxonWithName(string $taxonName): bool
|
||||
{
|
||||
$this->openTaxonBookmarks();
|
||||
Assert::notNull($this->getDocument()->find('css', '.search > .text'));
|
||||
$mainTaxonElement = $this->getElement('main_taxon')->getParent();
|
||||
|
||||
return $taxonName === $this->getDocument()->find('css', '.search > .text')->getText();
|
||||
return $taxonName === $mainTaxonElement->find('css', '.search > .text')->getText();
|
||||
}
|
||||
|
||||
public function selectMainTaxon(TaxonInterface $taxon): void
|
||||
|
|
@ -59,7 +59,9 @@ class CreateConfigurableProductPage extends BaseCreatePage implements CreateConf
|
|||
|
||||
public function selectOption(string $optionName): void
|
||||
{
|
||||
$this->getDocument()->selectFieldOption('Options', $optionName);
|
||||
$option = $this->getElement('options_choice')->getParent();
|
||||
|
||||
AutocompleteHelper::chooseValue($this->getSession(), $option, $optionName);
|
||||
}
|
||||
|
||||
public function attachImage(string $path, ?string $type = null): void
|
||||
|
|
@ -85,6 +87,7 @@ class CreateConfigurableProductPage extends BaseCreatePage implements CreateConf
|
|||
'images' => '#sylius_product_images',
|
||||
'main_taxon' => '#sylius_product_mainTaxon',
|
||||
'name' => '#sylius_product_translations_en_US_name',
|
||||
'options_choice' => '#sylius_product_options',
|
||||
'search' => '.ui.fluid.search.selection.dropdown',
|
||||
'search_item_selected' => 'div.menu > div.item.selected',
|
||||
'slug' => '#sylius_product_translations_en_US_slug',
|
||||
|
|
|
|||
|
|
@ -133,8 +133,9 @@ class CreateSimpleProductPage extends BaseCreatePage implements CreateSimpleProd
|
|||
public function hasMainTaxonWithName(string $taxonName): bool
|
||||
{
|
||||
$this->openTaxonBookmarks();
|
||||
$mainTaxonElement = $this->getElement('main_taxon')->getParent();
|
||||
|
||||
return $taxonName === $this->getDocument()->find('css', '.search > .text')->getText();
|
||||
return $taxonName === $mainTaxonElement->find('css', '.search > .text')->getText();
|
||||
}
|
||||
|
||||
public function checkProductTaxon(TaxonInterface $taxon): void
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ namespace Sylius\Behat\Page\Admin\Product;
|
|||
use Behat\Mink\Element\NodeElement;
|
||||
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
|
||||
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
|
||||
use Sylius\Behat\Service\AutocompleteHelper;
|
||||
use Sylius\Component\Taxonomy\Model\TaxonInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
|
|
@ -35,7 +36,9 @@ class UpdateConfigurableProductPage extends BaseUpdatePage implements UpdateConf
|
|||
|
||||
public function isProductOptionChosen(string $option): bool
|
||||
{
|
||||
return $this->getElement('options')->find('named', ['option', $option])->hasAttribute('selected');
|
||||
$optionElement = $this->getElement('options')->getParent();
|
||||
|
||||
return AutocompleteHelper::isValueVisible($this->getSession(), $optionElement, $option);
|
||||
}
|
||||
|
||||
public function isProductOptionsDisabled(): bool
|
||||
|
|
@ -46,9 +49,9 @@ class UpdateConfigurableProductPage extends BaseUpdatePage implements UpdateConf
|
|||
public function hasMainTaxonWithName(string $taxonName): bool
|
||||
{
|
||||
$this->openTaxonBookmarks();
|
||||
Assert::notNull($this->getDocument()->find('css', '.search > .text'));
|
||||
$mainTaxonElement = $this->getElement('main_taxon')->getParent();
|
||||
|
||||
return $taxonName === $this->getDocument()->find('css', '.search > .text')->getText();
|
||||
return $taxonName === $mainTaxonElement->find('css', '.search > .text')->getText();
|
||||
}
|
||||
|
||||
public function selectMainTaxon(TaxonInterface $taxon): void
|
||||
|
|
@ -184,6 +187,7 @@ class UpdateConfigurableProductPage extends BaseUpdatePage implements UpdateConf
|
|||
'channels' => '#sylius_product_channels',
|
||||
'code' => '#sylius_product_code',
|
||||
'images' => '#sylius_product_images',
|
||||
'main_taxon' => '#sylius_product_mainTaxon',
|
||||
'name' => '#sylius_product_translations_en_US_name',
|
||||
'options' => '#sylius_product_options',
|
||||
'price' => '#sylius_product_variant_price',
|
||||
|
|
|
|||
|
|
@ -177,8 +177,9 @@ class UpdateSimpleProductPage extends BaseUpdatePage implements UpdateSimpleProd
|
|||
public function hasMainTaxonWithName(string $taxonName): bool
|
||||
{
|
||||
$this->openTaxonBookmarks();
|
||||
$mainTaxonElement = $this->getElement('main_taxon')->getParent();
|
||||
|
||||
return $taxonName === $this->getDocument()->find('css', '.search > .text')->getText();
|
||||
return $taxonName === $mainTaxonElement->find('css', '.search > .text')->getText();
|
||||
}
|
||||
|
||||
public function isTaxonChosen(string $taxonName): bool
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Sylius\Behat\Behaviour\ChecksCodeImmutability;
|
|||
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
|
||||
use Sylius\Behat\Service\AutocompleteHelper;
|
||||
use Sylius\Behat\Service\DriverHelper;
|
||||
use Sylius\Behat\Service\JQueryHelper;
|
||||
use Sylius\Behat\Service\SlugGenerationHelper;
|
||||
use Sylius\Component\Core\Model\TaxonInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
|
@ -32,6 +33,7 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
|
|||
public function chooseParent(TaxonInterface $taxon): void
|
||||
{
|
||||
AutocompleteHelper::chooseValue($this->getSession(), $this->getElement('parent')->getParent(), $taxon->getName());
|
||||
JQueryHelper::waitForAsynchronousActionsToFinish($this->getSession());
|
||||
}
|
||||
|
||||
public function describeItAs(string $description, string $languageCode): void
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Service;
|
||||
|
||||
use Sylius\Component\User\Model\UserInterface;
|
||||
|
||||
interface RememberMeAwareSecurityServiceInterface extends SecurityServiceInterface
|
||||
{
|
||||
public function logInWithRememberMe(UserInterface $user): void;
|
||||
}
|
||||
|
|
@ -19,11 +19,12 @@ use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
|
|||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
|
||||
|
||||
final class SecurityService implements SecurityServiceInterface
|
||||
final class SecurityService implements RememberMeAwareSecurityServiceInterface
|
||||
{
|
||||
private string $sessionTokenVariable;
|
||||
|
||||
|
|
@ -48,6 +49,13 @@ final class SecurityService implements SecurityServiceInterface
|
|||
$this->setToken($token);
|
||||
}
|
||||
|
||||
public function logInWithRememberMe(UserInterface $user): void
|
||||
{
|
||||
$token = new RememberMeToken($user, $this->firewallContextName, 'secret');
|
||||
|
||||
$this->setToken($token);
|
||||
}
|
||||
|
||||
public function logOut(): void
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -62,5 +62,9 @@
|
|||
<service id="sylius.validator.zone_cannot_contain_itself" class="Sylius\Bundle\AddressingBundle\Validator\Constraints\ZoneCannotContainItselfValidator">
|
||||
<tag name="validator.constraint_validator" alias="sylius_zone_cannot_contain_itself_validator" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.validator.unique_province_collection" class="Sylius\Bundle\AddressingBundle\Validator\Constraints\UniqueProvinceCollectionValidator">
|
||||
<tag name="validator.constraint_validator" alias="sylius_unique_province_collection_validator" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@
|
|||
</constraint>
|
||||
</property>
|
||||
<property name="provinces">
|
||||
<constraint name="Sylius\Bundle\AddressingBundle\Validator\Constraints\UniqueProvinceCollection">
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Valid" />
|
||||
</property>
|
||||
</class>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ sylius:
|
|||
not_blank: Please enter country ISO code.
|
||||
regex: Country code can only be comprised of letters, numbers, dashes and underscores.
|
||||
unique: Country ISO code must be unique.
|
||||
unique_provinces: All provinces within this country need to have unique codes and names.
|
||||
province:
|
||||
code:
|
||||
min_length: Province code must be at least 5 characters long|Province code must be at least 5 characters long.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AddressingBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
final class UniqueProvinceCollection extends Constraint
|
||||
{
|
||||
public string $message = 'sylius.country.unique_provinces';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius_unique_province_collection_validator';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AddressingBundle\Validator\Constraints;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Sylius\Component\Addressing\Model\ProvinceInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class UniqueProvinceCollectionValidator extends ConstraintValidator
|
||||
{
|
||||
public function validate($value, Constraint $constraint): void
|
||||
{
|
||||
/** @var Collection<array-key, ProvinceInterface> $value */
|
||||
Assert::allIsInstanceOf($value, ProvinceInterface::class);
|
||||
/** @var UniqueProvinceCollection $constraint */
|
||||
Assert::isInstanceOf($constraint, UniqueProvinceCollection::class);
|
||||
|
||||
if ($value->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$provincesWithAnyRequiredData = $value->filter(
|
||||
fn(ProvinceInterface $province): bool => null !== $province->getCode() || null !== $province->getName()
|
||||
);
|
||||
|
||||
$codes = [];
|
||||
$names = [];
|
||||
foreach ($provincesWithAnyRequiredData as $province) {
|
||||
$code = $province->getCode();
|
||||
$name = $province->getName();
|
||||
|
||||
if (isset($code) && in_array($code, $codes) || isset($name) && in_array($name, $names)) {
|
||||
$this->context->addViolation($constraint->message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$codes[] = $code;
|
||||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\AddressingBundle\Validator\Constraints;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\AddressingBundle\Validator\Constraints\UniqueProvinceCollection;
|
||||
use Sylius\Component\Addressing\Model\ProvinceInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
final class UniqueProvinceCollectionValidatorSpec extends ObjectBehavior
|
||||
{
|
||||
function let(ExecutionContextInterface $executionContext): void
|
||||
{
|
||||
$this->beConstructedWith();
|
||||
$this->initialize($executionContext);
|
||||
}
|
||||
|
||||
function it_is_a_constraint_validator(): void
|
||||
{
|
||||
$this->shouldImplement(ConstraintValidatorInterface::class);
|
||||
}
|
||||
|
||||
function it_throws_exception_when_value_is_not_a_collection(): void
|
||||
{
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', [
|
||||
new \stdClass(),
|
||||
new UniqueProvinceCollection(),
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
function it_throws_exception_when_collection_contains_something_other_than_provinces(): void
|
||||
{
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', [
|
||||
new ArrayCollection([new \stdClass()]),
|
||||
new UniqueProvinceCollection(),
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
function it_throws_exception_when_constraint_is_not_a_unique_province_collection_codes(
|
||||
Constraint $constraint,
|
||||
): void {
|
||||
$this
|
||||
->shouldThrow(\InvalidArgumentException::class)
|
||||
->during('validate', [
|
||||
new ArrayCollection(),
|
||||
$constraint,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
function it_does_nothing_when_collection_is_empty(ExecutionContextInterface $context): void
|
||||
{
|
||||
$context->addViolation(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->validate(new ArrayCollection(), new UniqueProvinceCollection());
|
||||
}
|
||||
|
||||
function it_does_nothing_when_all_provinces_have_unique_codes(
|
||||
ExecutionContextInterface $executionContext,
|
||||
ProvinceInterface $firstProvince,
|
||||
ProvinceInterface $secondProvince,
|
||||
): void {
|
||||
$firstProvince->getCode()->willReturn('first');
|
||||
$firstProvince->getName()->willReturn('first');
|
||||
$secondProvince->getCode()->willReturn('second');
|
||||
$secondProvince->getName()->willReturn('second');
|
||||
|
||||
$executionContext->addViolation(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->validate(
|
||||
new ArrayCollection([
|
||||
$firstProvince->getWrappedObject(),
|
||||
$secondProvince->getWrappedObject(),
|
||||
]),
|
||||
new UniqueProvinceCollection(),
|
||||
);
|
||||
}
|
||||
|
||||
function it_checks_uniqueness_with_incomplete_codes(
|
||||
ExecutionContextInterface $executionContext,
|
||||
ProvinceInterface $province,
|
||||
ProvinceInterface $sameProvinceWithCode,
|
||||
): void {
|
||||
$constraint = new UniqueProvinceCollection();
|
||||
|
||||
$province->getCode()->willReturn(null);
|
||||
$province->getName()->willReturn('name');
|
||||
$sameProvinceWithCode->getCode()->willReturn('code');
|
||||
$sameProvinceWithCode->getName()->willReturn('name');
|
||||
|
||||
$executionContext->addViolation($constraint->message)->shouldBeCalled();
|
||||
|
||||
$this->validate(
|
||||
new ArrayCollection([
|
||||
$province->getWrappedObject(),
|
||||
$sameProvinceWithCode->getWrappedObject(),
|
||||
]),
|
||||
$constraint,
|
||||
);
|
||||
}
|
||||
|
||||
function it_checks_uniqueness_with_incomplete_names(
|
||||
ExecutionContextInterface $executionContext,
|
||||
ProvinceInterface $province,
|
||||
ProvinceInterface $sameProvinceWithName,
|
||||
): void {
|
||||
$constraint = new UniqueProvinceCollection();
|
||||
|
||||
$province->getCode()->willReturn('code');
|
||||
$province->getName()->willReturn(null);
|
||||
$sameProvinceWithName->getCode()->willReturn('code');
|
||||
$sameProvinceWithName->getName()->willReturn('name');
|
||||
|
||||
$executionContext->addViolation($constraint->message)->shouldBeCalled();
|
||||
|
||||
$this->validate(
|
||||
new ArrayCollection([
|
||||
$province->getWrappedObject(),
|
||||
$sameProvinceWithName->getWrappedObject(),
|
||||
]),
|
||||
$constraint,
|
||||
);
|
||||
}
|
||||
|
||||
function it_adds_violation_when_codes_are_duplicated(
|
||||
ExecutionContextInterface $executionContext,
|
||||
ProvinceInterface $firstProvince,
|
||||
ProvinceInterface $secondProvince,
|
||||
ProvinceInterface $thirdProvince,
|
||||
): void {
|
||||
$constraint = new UniqueProvinceCollection();
|
||||
|
||||
$firstProvince->getCode()->willReturn('same');
|
||||
$firstProvince->getName()->willReturn('first');
|
||||
$secondProvince->getCode()->willReturn('same');
|
||||
$secondProvince->getName()->willReturn('second');
|
||||
$thirdProvince->getCode()->willReturn('different');
|
||||
$thirdProvince->getName()->willReturn('third');
|
||||
|
||||
$executionContext->addViolation($constraint->message)->shouldBeCalled();
|
||||
|
||||
$this->validate(
|
||||
new ArrayCollection([
|
||||
$firstProvince->getWrappedObject(),
|
||||
$secondProvince->getWrappedObject(),
|
||||
$thirdProvince->getWrappedObject(),
|
||||
]),
|
||||
$constraint,
|
||||
);
|
||||
}
|
||||
|
||||
function it_adds_violation_when_names_are_duplicated(
|
||||
ExecutionContextInterface $executionContext,
|
||||
ProvinceInterface $firstProvince,
|
||||
ProvinceInterface $secondProvince,
|
||||
ProvinceInterface $thirdProvince,
|
||||
): void {
|
||||
$constraint = new UniqueProvinceCollection();
|
||||
|
||||
$firstProvince->getCode()->willReturn('first');
|
||||
$firstProvince->getName()->willReturn('first');
|
||||
$secondProvince->getCode()->willReturn('second');
|
||||
$secondProvince->getName()->willReturn('same');
|
||||
$thirdProvince->getCode()->willReturn('third');
|
||||
$thirdProvince->getName()->willReturn('same');
|
||||
|
||||
$executionContext->addViolation($constraint->message)->shouldBeCalled();
|
||||
|
||||
$this->validate(
|
||||
new ArrayCollection([
|
||||
$firstProvince->getWrappedObject(),
|
||||
$secondProvince->getWrappedObject(),
|
||||
$thirdProvince->getWrappedObject(),
|
||||
]),
|
||||
$constraint,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,10 @@ sylius_admin_ajax_customer_group:
|
|||
resource: "@SyliusAdminBundle/Resources/config/routing/ajax/customer_group.yml"
|
||||
prefix: /customer-groups
|
||||
|
||||
sylius_admin_ajax_product_option:
|
||||
resource: "@SyliusAdminBundle/Resources/config/routing/ajax/product_option.yml"
|
||||
prefix: /product-options
|
||||
|
||||
sylius_admin_ajax_render_province_form:
|
||||
path: /render-province-form
|
||||
defaults:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
sylius_admin_ajax_product_options_by_phrase:
|
||||
path: /search-phrase
|
||||
methods: [GET]
|
||||
defaults:
|
||||
_controller: sylius.controller.product_option::indexAction
|
||||
_format: json
|
||||
_sylius:
|
||||
serialization_groups: [Autocomplete]
|
||||
permission: true
|
||||
repository:
|
||||
method: findByPhrase
|
||||
arguments:
|
||||
phrase: $phrase
|
||||
locale: expr:service('sylius.context.locale').getLocaleCode()
|
||||
limit: "!!int %sylius.ajax.product.option_autocomplete_limit%"
|
||||
|
||||
sylius_admin_ajax_find_product_options:
|
||||
path: /options
|
||||
methods: [GET]
|
||||
defaults:
|
||||
_controller: sylius.controller.product_option::indexAction
|
||||
_format: json
|
||||
_sylius:
|
||||
serialization_groups: [Autocomplete]
|
||||
permission: true
|
||||
repository:
|
||||
method: findByCodes
|
||||
arguments:
|
||||
codes: $code
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
<parameters>
|
||||
<parameter key="sylius.ajax.product.autocomplete_limit">25</parameter>
|
||||
<parameter key="sylius.ajax.product.variant_autocomplete_limit">25</parameter>
|
||||
<parameter key="sylius.ajax.product.option_autocomplete_limit">10</parameter>
|
||||
<parameter key="sylius.ajax.customer_group.autocomplete_limit">25</parameter>
|
||||
<parameter key="sylius.message.admin_user_create.validation_groups" type="collection">
|
||||
<parameter>sylius</parameter>
|
||||
|
|
|
|||
|
|
@ -11,26 +11,21 @@ import $ from 'jquery';
|
|||
|
||||
const updateSlug = function updateSlug(element) {
|
||||
const slugInput = element.parents('.content').find('[name*="[slug]"]');
|
||||
const loadableParent = slugInput.parents('.field.loadable');
|
||||
|
||||
if (slugInput.attr('readonly') === 'readonly') {
|
||||
return;
|
||||
}
|
||||
|
||||
const loadableParent = slugInput.parents('.field.loadable');
|
||||
const parentTaxonInput = $('#sylius_taxon_parent');
|
||||
|
||||
loadableParent.addClass('loading');
|
||||
|
||||
let data;
|
||||
if (slugInput.attr('data-parent') != '' && slugInput.attr('data-parent') != undefined) {
|
||||
if (parentTaxonInput.length > 0 && parentTaxonInput.val() !== '') {
|
||||
data = {
|
||||
name: element.val(),
|
||||
locale: element.closest('[data-locale]').data('locale'),
|
||||
parentId: slugInput.attr('data-parent'),
|
||||
};
|
||||
} else if ($('#sylius_taxon_parent').length > 0 && $('#sylius_taxon_parent').is(':visible') && $('#sylius_taxon_parent').val() != '') {
|
||||
data = {
|
||||
name: element.val(),
|
||||
locale: element.closest('[data-locale]').data('locale'),
|
||||
parentId: $('#sylius_taxon_parent').val(),
|
||||
parentCode: parentTaxonInput.val(),
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
|
|
@ -51,6 +46,11 @@ const updateSlug = function updateSlug(element) {
|
|||
slugInput.parents('.field').removeClass('error');
|
||||
slugInput.parents('.field').find('.sylius-validation-error').remove();
|
||||
}
|
||||
},
|
||||
error() {
|
||||
slugInput.val('');
|
||||
},
|
||||
complete() {
|
||||
loadableParent.removeClass('loading');
|
||||
},
|
||||
});
|
||||
|
|
@ -78,6 +78,14 @@ $.fn.extend({
|
|||
updateSlug(element);
|
||||
}, 1000);
|
||||
});
|
||||
$('#sylius_taxon_parent').parent().on('change', () => {
|
||||
const nameInput = $('[data-locale]').find('.content.active [name*="sylius_taxon[translations]"][name*="[name]"]');
|
||||
if (nameInput.val() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
updateSlug($(nameInput));
|
||||
});
|
||||
|
||||
$('.toggle-taxon-slug-modification').on('click', (event) => {
|
||||
event.preventDefault();
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
.attribute-row {
|
||||
flex-wrap: wrap;
|
||||
margin: 10px 0;
|
||||
margin: 10px 0 0;
|
||||
|
||||
@media (min-width: 1152px) {
|
||||
display: flex;
|
||||
|
|
@ -50,11 +50,20 @@
|
|||
align-self: center;
|
||||
width: 200px;
|
||||
margin-right: 20px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
&.field.required {
|
||||
margin: 0 20px 0 0;
|
||||
|
||||
> label {
|
||||
margin: 0 !important;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
span.language {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
i {
|
||||
width: 20px;
|
||||
|
|
@ -85,6 +94,10 @@
|
|||
min-height: 6em !important;
|
||||
}
|
||||
|
||||
.attribute-action {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.attribute-action > * {
|
||||
margin: 4px 0 !important;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{{ form_errors(form) }}
|
||||
<div class="ui segment">
|
||||
{{ form_errors(form) }}
|
||||
{{ form_row(form.code) }}
|
||||
{{ form_row(form.enabled) }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,11 @@
|
|||
<div class="attribute" data-id="{{ code }}">
|
||||
{% set id = form.vars.label|replace({' ': '_'})|lower %}
|
||||
<div class="attribute-row">
|
||||
<div class="attribute-label {% if not localeCode or localeCode == sylius_base_locale %} required field {% endif %}">
|
||||
<div class="attribute-label{% if not localeCode or localeCode == sylius_base_locale %} required field{% endif %}">
|
||||
<label>
|
||||
{% if localeCode %}
|
||||
{{ flags.fromLocaleCode(localeCode) }}
|
||||
<span class="language">{{ flags.fromLocaleCode(localeCode) }} {{ localeCode|sylius_locale_name }}</span>
|
||||
<br>
|
||||
{% else %}
|
||||
<i class="globe icon"></i>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -41,13 +41,16 @@
|
|||
|
||||
<div class="attribute" data-id="{{ form.vars.data.attribute.code }}">
|
||||
<div class="attribute-row">
|
||||
<div class="attribute-label">
|
||||
<div class="attribute-label{% if not form.localeCode.vars.value or form.localeCode.vars.value == sylius_base_locale %} required field{% endif %}">
|
||||
<label>
|
||||
{% if form.localeCode.vars.value %}
|
||||
{{ flags.fromLocaleCode(form.localeCode.vars.value) }}
|
||||
<span class="language">{{ flags.fromLocaleCode(form.localeCode.vars.value) }} {{ form.localeCode.vars.value|sylius_locale_name }}</span>
|
||||
<br>
|
||||
{% else %}
|
||||
<i class="globe icon"></i>
|
||||
{% endif %}
|
||||
{{ form.vars.value.attribute.translation(form.vars.value.localeCode).name }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="attribute-input" data-test-product-attribute-value-in-locale="{{ "%s %s"|format(form.vars.data.attribute.name, form.localeCode.vars.value) }}">
|
||||
<div {{ 'checkbox' in form.children.value.vars.block_prefixes ? 'class="ui toggle checkbox"' : '' }}>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
{% if product.simple %}
|
||||
{{ form_row(form.variant.shippingRequired) }}
|
||||
{% else %}
|
||||
{{ form_row(form.options) }}
|
||||
{{ form_row(form.options, {'remote_url': path('sylius_admin_ajax_product_options_by_phrase'), 'load_edit_url': path('sylius_admin_ajax_find_product_options')}) }}
|
||||
{{ form_row(form.variantSelectionMethod) }}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,4 +46,43 @@ class ProductOptionRepository extends EntityRepository implements ProductOptionR
|
|||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
public function findByPhrase(string $phrase, string $locale, int $limit = 10): array
|
||||
{
|
||||
$subqueryBuilder = $this->createQueryBuilder('sq')
|
||||
->innerJoin('sq.translations', 'translation', 'WITH', 'translation.name LIKE :name')
|
||||
->groupBy('sq.id')
|
||||
->addGroupBy('translation.translatable')
|
||||
->orderBy('translation.translatable', 'DESC')
|
||||
;
|
||||
|
||||
$queryBuilder = $this->createQueryBuilder('o');
|
||||
|
||||
/** @var ProductOptionInterface[] $results */
|
||||
$results = $queryBuilder
|
||||
->andWhere($queryBuilder->expr()->in('o', $subqueryBuilder->getDQL()))
|
||||
->setParameter('name', '%' . $phrase . '%')
|
||||
->setMaxResults($limit)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
|
||||
foreach ($results as $result) {
|
||||
$result->setFallbackLocale(array_key_first($result->getTranslations()->toArray()));
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function findByCodes(array $codes = []): array
|
||||
{
|
||||
$expr = $this->getEntityManager()->getExpressionBuilder();
|
||||
|
||||
return $this->createQueryBuilder('o')
|
||||
->andWhere($expr->in('o.code', ':codes'))
|
||||
->setParameter('codes', $codes)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Sylius\Bundle\ProductBundle\Form\EventSubscriber;
|
||||
|
||||
use Sylius\Bundle\ProductBundle\Form\Type\ProductOptionChoiceType;
|
||||
use Sylius\Bundle\ProductBundle\Form\Type\ProductOptionAutocompleteType;
|
||||
use Sylius\Component\Product\Model\ProductInterface;
|
||||
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
|
@ -46,7 +46,7 @@ final class ProductOptionFieldSubscriber implements EventSubscriberInterface
|
|||
/** Options should be disabled for configurable product if it has at least one defined variant */
|
||||
$disableOptions = (null !== $this->variantResolver->getVariant($product)) && $product->hasVariants();
|
||||
|
||||
$form->add('options', ProductOptionChoiceType::class, [
|
||||
$form->add('options', ProductOptionAutocompleteType::class, [
|
||||
'required' => false,
|
||||
'disabled' => $disableOptions,
|
||||
'multiple' => true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ProductBundle\Form\Type;
|
||||
|
||||
use Sylius\Bundle\ResourceBundle\Form\Type\ResourceAutocompleteChoiceType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ProductOptionAutocompleteType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'choice_value' => 'code',
|
||||
'choice_name' => 'name',
|
||||
'resource' => 'sylius.product_option',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingPropertyType
|
||||
*/
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['remote_criteria_type'] = 'contains';
|
||||
$view->vars['remote_criteria_name'] = 'phrase';
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ResourceAutocompleteChoiceType::class;
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'sylius_product_option_autocomplete_choice';
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,11 @@ Sylius\Component\Product\Model\ProductOption:
|
|||
expose: true
|
||||
type: integer
|
||||
xml_attribute: true
|
||||
groups: [Default, Detailed]
|
||||
groups: [Default, Detailed, Autocomplete]
|
||||
code:
|
||||
expose: true
|
||||
type: string
|
||||
groups: [Default, Detailed]
|
||||
groups: [Default, Detailed, Autocomplete]
|
||||
position:
|
||||
expose: true
|
||||
type: integer
|
||||
|
|
@ -23,3 +23,7 @@ Sylius\Component\Product\Model\ProductOption:
|
|||
expose: true
|
||||
type: iterable
|
||||
groups: [Default, Detailed]
|
||||
virtual_properties:
|
||||
getName:
|
||||
serialized_name: name
|
||||
groups: [Default, Detailed, Autocomplete]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
{{ form_widget(form.comment, sylius_test_form_attribute('comment')|sylius_merge_recursive( {'attr': {'placeholder': 'sylius.ui.write_your_own_review'|trans}})) }}
|
||||
{{ form_errors(form.comment) }}
|
||||
</div>
|
||||
{% if not is_granted("IS_AUTHENTICATED_FULLY") %}
|
||||
{% if not is_granted("IS_AUTHENTICATED_REMEMBERED") %}
|
||||
<div class="field">
|
||||
{{ form_widget(form.author.email, sylius_test_form_attribute('author-email')|sylius_merge_recursive( {'attr': {'placeholder': 'sylius.ui.email'|trans}})) }}
|
||||
{{ form_errors(form.author.email) }}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use Sylius\Component\Resource\Factory\FactoryInterface;
|
|||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
use Sylius\Component\Taxonomy\Generator\TaxonSlugGeneratorInterface;
|
||||
use Sylius\Component\Taxonomy\Model\TaxonInterface;
|
||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
|
@ -33,8 +34,11 @@ final class TaxonSlugController
|
|||
public function generateAction(Request $request): Response
|
||||
{
|
||||
$name = (string) $request->query->get('name');
|
||||
if ('' === trim($name)) {
|
||||
throw new BadRequestException('Cannot generate slug without a name');
|
||||
}
|
||||
|
||||
$locale = (string) $request->query->get('locale');
|
||||
$parentId = $request->query->get('parentId');
|
||||
|
||||
/** @var TaxonInterface $taxon */
|
||||
$taxon = $this->taxonFactory->createNew();
|
||||
|
|
@ -42,12 +46,25 @@ final class TaxonSlugController
|
|||
$taxon->setFallbackLocale($locale);
|
||||
$taxon->setName($name);
|
||||
|
||||
if (null !== $parentId) {
|
||||
$taxon->setParent($this->taxonRepository->find($parentId));
|
||||
}
|
||||
$taxon->setParent($this->getParentTaxon($request));
|
||||
|
||||
return new JsonResponse([
|
||||
'slug' => $this->taxonSlugGenerator->generate($taxon, $locale),
|
||||
]);
|
||||
}
|
||||
|
||||
private function getParentTaxon(Request $request): ?TaxonInterface
|
||||
{
|
||||
$parentCode = $request->query->get('parentCode');
|
||||
if (null !== $parentCode) {
|
||||
return $this->taxonRepository->findOneBy(['code' => $parentCode]);
|
||||
}
|
||||
|
||||
$parentId = $request->query->get('parentId');
|
||||
if (null !== $parentId) {
|
||||
return $this->taxonRepository->find($parentId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@
|
|||
{%- endblock sylius_translations_row %}
|
||||
|
||||
{% block sylius_resource_autocomplete_choice_row %}
|
||||
<div class="{% if required %}required {% endif %}field{% if (not compound or force_error|default(false)) and not valid %} error{% endif %}">
|
||||
<div class="{% if required %}required {% endif %}{% if disabled %}disabled {% endif %}field{% if (not compound or force_error|default(false)) and not valid %} error{% endif %}">
|
||||
{{- form_label(form) -}}
|
||||
<div
|
||||
class="sylius-autocomplete ui fluid search selection dropdown {% if multiple %}multiple{% endif %}"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,19 @@ interface ProductOptionRepositoryInterface extends RepositoryInterface
|
|||
public function createListQueryBuilder(string $locale): QueryBuilder;
|
||||
|
||||
/**
|
||||
* @return array|ProductOptionInterface[]
|
||||
* @return ProductOptionInterface[]
|
||||
*/
|
||||
public function findByName(string $name, string $locale): array;
|
||||
|
||||
/**
|
||||
* @return ProductOptionInterface[]
|
||||
*/
|
||||
public function findByPhrase(string $phrase, string $locale, int $limit = 10): array;
|
||||
|
||||
/**
|
||||
* @param string[] $codes
|
||||
*
|
||||
* @return ProductOptionInterface[]
|
||||
*/
|
||||
public function findByCodes(array $codes = []): array;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue