mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +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:
|
Background:
|
||||||
Given the store operates on a single channel in "United States"
|
Given the store operates on a single channel in "United States"
|
||||||
And the store has a product "Necronomicon"
|
And the store has a product "Necronomicon"
|
||||||
And I am a logged in customer
|
|
||||||
|
|
||||||
@ui @api
|
@ui @api
|
||||||
Scenario: Adding product reviews as a logged in customer
|
Scenario: Adding product reviews as a logged in customer
|
||||||
|
Given I am a logged in customer
|
||||||
When I want to review product "Necronomicon"
|
When I want to review product "Necronomicon"
|
||||||
And I leave a comment "Great book for every advanced sorcerer.", titled "Scary but astonishing"
|
And I leave a comment "Great book for every advanced sorcerer.", titled "Scary but astonishing"
|
||||||
And I rate it with 5 points
|
And I rate it with 5 points
|
||||||
And I add it
|
And I add it
|
||||||
Then I should be notified that my review is waiting for the acceptation
|
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
|
Then I should be notified that it has been successfully created
|
||||||
And the product "Dice Brewing" should appear in the store
|
And the product "Dice Brewing" should appear in the store
|
||||||
|
|
||||||
@ui
|
@ui @javascript
|
||||||
Scenario: Adding a new configurable product
|
Scenario: Adding a new configurable product
|
||||||
Given the store has a product option "Bottle size" with a code "bottle_size"
|
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"
|
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
|
Then I should be notified that it has been successfully edited
|
||||||
And this product name should be "Sobieski Vodka"
|
And this product name should be "Sobieski Vodka"
|
||||||
|
|
||||||
@ui @api
|
@ui @javascript @api
|
||||||
Scenario: Changing options of configurable product without any variant defined
|
Scenario: Changing options of configurable product without any variant defined
|
||||||
Given the store has a "Marvel's T-Shirt" configurable product
|
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"
|
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
|
And I add it
|
||||||
Then this taxon slug should be "medieval-weapons/siege-engines"
|
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
|
@ui
|
||||||
Scenario: Creating a child taxon with a custom slug
|
Scenario: Creating a child taxon with a custom slug
|
||||||
Given the store has "Medieval weapons" taxonomy
|
Given the store has "Medieval weapons" taxonomy
|
||||||
|
|
@ -51,13 +61,32 @@ Feature: Editing taxon's slug
|
||||||
Then the slug field should not be editable
|
Then the slug field should not be editable
|
||||||
|
|
||||||
@ui @javascript
|
@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
|
Given the store has "Medieval weapons" taxonomy
|
||||||
When I want to modify the "Medieval weapons" taxon
|
When I want to modify the "Medieval weapons" taxon
|
||||||
And I rename it to "Renaissance weapons" in "English (United States)"
|
And I rename it to "Renaissance weapons" in "English (United States)"
|
||||||
And I save my changes
|
And I save my changes
|
||||||
Then the slug of the "Renaissance weapons" taxon should still be "medieval-weapons"
|
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
|
@ui @javascript
|
||||||
Scenario: Automatically changing a root taxon's slug while editing a taxon's name
|
Scenario: Automatically changing a root taxon's slug while editing a taxon's name
|
||||||
Given the store has "Medieval weapons" taxonomy
|
Given the store has "Medieval weapons" taxonomy
|
||||||
|
|
@ -87,6 +116,29 @@ Feature: Editing taxon's slug
|
||||||
And I save my changes
|
And I save my changes
|
||||||
Then the slug of the "Renaissance weapons" taxon should be "renaissance"
|
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
|
@ui @javascript
|
||||||
Scenario: Manually changing a child taxon's slug while editing a taxon's name
|
Scenario: Manually changing a child taxon's slug while editing a taxon's name
|
||||||
Given the store has "Medieval weapons" taxonomy
|
Given the store has "Medieval weapons" taxonomy
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,11 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Sylius/Bundle/AddressingBundle/Validator/Constraints/ProvinceAddressConstraintValidator.php
|
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\\.$#"
|
message: "#^Method Sylius\\\\Bundle\\\\AddressingBundle\\\\Validator\\\\Constraints\\\\ZoneCannotContainItselfValidator\\:\\:validate\\(\\) has parameter \\$value with no type specified\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ final class ManagingCountriesContext implements Context
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @When I add it
|
* @When I add it
|
||||||
|
* @When I try to add it
|
||||||
*/
|
*/
|
||||||
public function iAddIt(): void
|
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()),
|
$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);
|
$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
|
public function theTaxonHasChildrenTaxonAnd(TaxonInterface $taxon, string ...$taxonsNames): void
|
||||||
{
|
{
|
||||||
foreach ($taxonsNames as $taxonName) {
|
foreach ($taxonsNames as $taxonName) {
|
||||||
$taxon->addChild($this->createTaxon($taxonName));
|
$taxon->addChild($this->createChildTaxon($taxonName, $taxon));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->objectManager->persist($taxon);
|
$this->objectManager->persist($taxon);
|
||||||
|
|
@ -158,6 +158,15 @@ final class TaxonomyContext implements Context
|
||||||
return $taxon;
|
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
|
* @return TaxonInterface
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ final class ManagingCountriesContext implements Context
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @When I add it
|
* @When I add it
|
||||||
|
* @When I try to add it
|
||||||
*/
|
*/
|
||||||
public function iAddIt()
|
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()]);
|
$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);
|
$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
|
public function getValidationMessage(string $element): string
|
||||||
{
|
{
|
||||||
$provinceForm = $this->getLastProvinceElement();
|
$provinceForm = $this->getLastProvinceElement();
|
||||||
|
|
@ -127,6 +134,7 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
|
||||||
return array_merge(parent::getDefinedElements(), [
|
return array_merge(parent::getDefinedElements(), [
|
||||||
'code' => '#sylius_country_code',
|
'code' => '#sylius_country_code',
|
||||||
'enabled' => '#sylius_country_enabled',
|
'enabled' => '#sylius_country_enabled',
|
||||||
|
'form' => 'form',
|
||||||
'provinces' => '#sylius_country_provinces',
|
'provinces' => '#sylius_country_provinces',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,7 @@ interface UpdatePageInterface extends BaseUpdatePageInterface
|
||||||
public function removeProvinceName(string $provinceName): void;
|
public function removeProvinceName(string $provinceName): void;
|
||||||
|
|
||||||
public function specifyProvinceCode(string $provinceCode): 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
|
public function hasMainTaxonWithName(string $taxonName): bool
|
||||||
{
|
{
|
||||||
$this->openTaxonBookmarks();
|
$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
|
public function selectMainTaxon(TaxonInterface $taxon): void
|
||||||
|
|
@ -59,7 +59,9 @@ class CreateConfigurableProductPage extends BaseCreatePage implements CreateConf
|
||||||
|
|
||||||
public function selectOption(string $optionName): void
|
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
|
public function attachImage(string $path, ?string $type = null): void
|
||||||
|
|
@ -85,6 +87,7 @@ class CreateConfigurableProductPage extends BaseCreatePage implements CreateConf
|
||||||
'images' => '#sylius_product_images',
|
'images' => '#sylius_product_images',
|
||||||
'main_taxon' => '#sylius_product_mainTaxon',
|
'main_taxon' => '#sylius_product_mainTaxon',
|
||||||
'name' => '#sylius_product_translations_en_US_name',
|
'name' => '#sylius_product_translations_en_US_name',
|
||||||
|
'options_choice' => '#sylius_product_options',
|
||||||
'search' => '.ui.fluid.search.selection.dropdown',
|
'search' => '.ui.fluid.search.selection.dropdown',
|
||||||
'search_item_selected' => 'div.menu > div.item.selected',
|
'search_item_selected' => 'div.menu > div.item.selected',
|
||||||
'slug' => '#sylius_product_translations_en_US_slug',
|
'slug' => '#sylius_product_translations_en_US_slug',
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,9 @@ class CreateSimpleProductPage extends BaseCreatePage implements CreateSimpleProd
|
||||||
public function hasMainTaxonWithName(string $taxonName): bool
|
public function hasMainTaxonWithName(string $taxonName): bool
|
||||||
{
|
{
|
||||||
$this->openTaxonBookmarks();
|
$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
|
public function checkProductTaxon(TaxonInterface $taxon): void
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ namespace Sylius\Behat\Page\Admin\Product;
|
||||||
use Behat\Mink\Element\NodeElement;
|
use Behat\Mink\Element\NodeElement;
|
||||||
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
|
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
|
||||||
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
|
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
|
||||||
|
use Sylius\Behat\Service\AutocompleteHelper;
|
||||||
use Sylius\Component\Taxonomy\Model\TaxonInterface;
|
use Sylius\Component\Taxonomy\Model\TaxonInterface;
|
||||||
use Webmozart\Assert\Assert;
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
|
|
@ -35,7 +36,9 @@ class UpdateConfigurableProductPage extends BaseUpdatePage implements UpdateConf
|
||||||
|
|
||||||
public function isProductOptionChosen(string $option): bool
|
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
|
public function isProductOptionsDisabled(): bool
|
||||||
|
|
@ -46,9 +49,9 @@ class UpdateConfigurableProductPage extends BaseUpdatePage implements UpdateConf
|
||||||
public function hasMainTaxonWithName(string $taxonName): bool
|
public function hasMainTaxonWithName(string $taxonName): bool
|
||||||
{
|
{
|
||||||
$this->openTaxonBookmarks();
|
$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
|
public function selectMainTaxon(TaxonInterface $taxon): void
|
||||||
|
|
@ -184,6 +187,7 @@ class UpdateConfigurableProductPage extends BaseUpdatePage implements UpdateConf
|
||||||
'channels' => '#sylius_product_channels',
|
'channels' => '#sylius_product_channels',
|
||||||
'code' => '#sylius_product_code',
|
'code' => '#sylius_product_code',
|
||||||
'images' => '#sylius_product_images',
|
'images' => '#sylius_product_images',
|
||||||
|
'main_taxon' => '#sylius_product_mainTaxon',
|
||||||
'name' => '#sylius_product_translations_en_US_name',
|
'name' => '#sylius_product_translations_en_US_name',
|
||||||
'options' => '#sylius_product_options',
|
'options' => '#sylius_product_options',
|
||||||
'price' => '#sylius_product_variant_price',
|
'price' => '#sylius_product_variant_price',
|
||||||
|
|
|
||||||
|
|
@ -177,8 +177,9 @@ class UpdateSimpleProductPage extends BaseUpdatePage implements UpdateSimpleProd
|
||||||
public function hasMainTaxonWithName(string $taxonName): bool
|
public function hasMainTaxonWithName(string $taxonName): bool
|
||||||
{
|
{
|
||||||
$this->openTaxonBookmarks();
|
$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
|
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\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
|
||||||
use Sylius\Behat\Service\AutocompleteHelper;
|
use Sylius\Behat\Service\AutocompleteHelper;
|
||||||
use Sylius\Behat\Service\DriverHelper;
|
use Sylius\Behat\Service\DriverHelper;
|
||||||
|
use Sylius\Behat\Service\JQueryHelper;
|
||||||
use Sylius\Behat\Service\SlugGenerationHelper;
|
use Sylius\Behat\Service\SlugGenerationHelper;
|
||||||
use Sylius\Component\Core\Model\TaxonInterface;
|
use Sylius\Component\Core\Model\TaxonInterface;
|
||||||
use Webmozart\Assert\Assert;
|
use Webmozart\Assert\Assert;
|
||||||
|
|
@ -32,6 +33,7 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
|
||||||
public function chooseParent(TaxonInterface $taxon): void
|
public function chooseParent(TaxonInterface $taxon): void
|
||||||
{
|
{
|
||||||
AutocompleteHelper::chooseValue($this->getSession(), $this->getElement('parent')->getParent(), $taxon->getName());
|
AutocompleteHelper::chooseValue($this->getSession(), $this->getElement('parent')->getParent(), $taxon->getName());
|
||||||
|
JQueryHelper::waitForAsynchronousActionsToFinish($this->getSession());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function describeItAs(string $description, string $languageCode): void
|
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\Request;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionFactoryInterface;
|
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\TokenInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||||
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
|
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
|
||||||
|
|
||||||
final class SecurityService implements SecurityServiceInterface
|
final class SecurityService implements RememberMeAwareSecurityServiceInterface
|
||||||
{
|
{
|
||||||
private string $sessionTokenVariable;
|
private string $sessionTokenVariable;
|
||||||
|
|
||||||
|
|
@ -48,6 +49,13 @@ final class SecurityService implements SecurityServiceInterface
|
||||||
$this->setToken($token);
|
$this->setToken($token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function logInWithRememberMe(UserInterface $user): void
|
||||||
|
{
|
||||||
|
$token = new RememberMeToken($user, $this->firewallContextName, 'secret');
|
||||||
|
|
||||||
|
$this->setToken($token);
|
||||||
|
}
|
||||||
|
|
||||||
public function logOut(): void
|
public function logOut(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -62,5 +62,9 @@
|
||||||
<service id="sylius.validator.zone_cannot_contain_itself" class="Sylius\Bundle\AddressingBundle\Validator\Constraints\ZoneCannotContainItselfValidator">
|
<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" />
|
<tag name="validator.constraint_validator" alias="sylius_zone_cannot_contain_itself_validator" />
|
||||||
</service>
|
</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>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@
|
||||||
</constraint>
|
</constraint>
|
||||||
</property>
|
</property>
|
||||||
<property name="provinces">
|
<property name="provinces">
|
||||||
|
<constraint name="Sylius\Bundle\AddressingBundle\Validator\Constraints\UniqueProvinceCollection">
|
||||||
|
<option name="groups">sylius</option>
|
||||||
|
</constraint>
|
||||||
<constraint name="Valid" />
|
<constraint name="Valid" />
|
||||||
</property>
|
</property>
|
||||||
</class>
|
</class>
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ sylius:
|
||||||
not_blank: Please enter country ISO code.
|
not_blank: Please enter country ISO code.
|
||||||
regex: Country code can only be comprised of letters, numbers, dashes and underscores.
|
regex: Country code can only be comprised of letters, numbers, dashes and underscores.
|
||||||
unique: Country ISO code must be unique.
|
unique: Country ISO code must be unique.
|
||||||
|
unique_provinces: All provinces within this country need to have unique codes and names.
|
||||||
province:
|
province:
|
||||||
code:
|
code:
|
||||||
min_length: Province code must be at least 5 characters long|Province code must be at least 5 characters long.
|
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"
|
resource: "@SyliusAdminBundle/Resources/config/routing/ajax/customer_group.yml"
|
||||||
prefix: /customer-groups
|
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:
|
sylius_admin_ajax_render_province_form:
|
||||||
path: /render-province-form
|
path: /render-province-form
|
||||||
defaults:
|
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>
|
<parameters>
|
||||||
<parameter key="sylius.ajax.product.autocomplete_limit">25</parameter>
|
<parameter key="sylius.ajax.product.autocomplete_limit">25</parameter>
|
||||||
<parameter key="sylius.ajax.product.variant_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.ajax.customer_group.autocomplete_limit">25</parameter>
|
||||||
<parameter key="sylius.message.admin_user_create.validation_groups" type="collection">
|
<parameter key="sylius.message.admin_user_create.validation_groups" type="collection">
|
||||||
<parameter>sylius</parameter>
|
<parameter>sylius</parameter>
|
||||||
|
|
|
||||||
|
|
@ -11,26 +11,21 @@ import $ from 'jquery';
|
||||||
|
|
||||||
const updateSlug = function updateSlug(element) {
|
const updateSlug = function updateSlug(element) {
|
||||||
const slugInput = element.parents('.content').find('[name*="[slug]"]');
|
const slugInput = element.parents('.content').find('[name*="[slug]"]');
|
||||||
const loadableParent = slugInput.parents('.field.loadable');
|
|
||||||
|
|
||||||
if (slugInput.attr('readonly') === 'readonly') {
|
if (slugInput.attr('readonly') === 'readonly') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadableParent = slugInput.parents('.field.loadable');
|
||||||
|
const parentTaxonInput = $('#sylius_taxon_parent');
|
||||||
|
|
||||||
loadableParent.addClass('loading');
|
loadableParent.addClass('loading');
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
if (slugInput.attr('data-parent') != '' && slugInput.attr('data-parent') != undefined) {
|
if (parentTaxonInput.length > 0 && parentTaxonInput.val() !== '') {
|
||||||
data = {
|
data = {
|
||||||
name: element.val(),
|
name: element.val(),
|
||||||
locale: element.closest('[data-locale]').data('locale'),
|
locale: element.closest('[data-locale]').data('locale'),
|
||||||
parentId: slugInput.attr('data-parent'),
|
parentCode: parentTaxonInput.val(),
|
||||||
};
|
|
||||||
} 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(),
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
data = {
|
data = {
|
||||||
|
|
@ -51,6 +46,11 @@ const updateSlug = function updateSlug(element) {
|
||||||
slugInput.parents('.field').removeClass('error');
|
slugInput.parents('.field').removeClass('error');
|
||||||
slugInput.parents('.field').find('.sylius-validation-error').remove();
|
slugInput.parents('.field').find('.sylius-validation-error').remove();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
error() {
|
||||||
|
slugInput.val('');
|
||||||
|
},
|
||||||
|
complete() {
|
||||||
loadableParent.removeClass('loading');
|
loadableParent.removeClass('loading');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -78,6 +78,14 @@ $.fn.extend({
|
||||||
updateSlug(element);
|
updateSlug(element);
|
||||||
}, 1000);
|
}, 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) => {
|
$('.toggle-taxon-slug-modification').on('click', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
.attribute-row {
|
.attribute-row {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 10px 0;
|
margin: 10px 0 0;
|
||||||
|
|
||||||
@media (min-width: 1152px) {
|
@media (min-width: 1152px) {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -50,11 +50,20 @@
|
||||||
align-self: center;
|
align-self: center;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
padding-top: 5px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
&.field.required {
|
||||||
|
margin: 0 20px 0 0;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
margin: 0 !important;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span.language {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|
@ -85,6 +94,10 @@
|
||||||
min-height: 6em !important;
|
min-height: 6em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attribute-action {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.attribute-action > * {
|
.attribute-action > * {
|
||||||
margin: 4px 0 !important;
|
margin: 4px 0 !important;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="ui segment">
|
|
||||||
{{ form_errors(form) }}
|
{{ form_errors(form) }}
|
||||||
|
<div class="ui segment">
|
||||||
{{ form_row(form.code) }}
|
{{ form_row(form.code) }}
|
||||||
{{ form_row(form.enabled) }}
|
{{ form_row(form.enabled) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@
|
||||||
<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>
|
<label>
|
||||||
{% if localeCode %}
|
{% if localeCode %}
|
||||||
{{ flags.fromLocaleCode(localeCode) }}
|
<span class="language">{{ flags.fromLocaleCode(localeCode) }} {{ localeCode|sylius_locale_name }}</span>
|
||||||
|
<br>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="globe icon"></i>
|
<i class="globe icon"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,16 @@
|
||||||
|
|
||||||
<div class="attribute" data-id="{{ form.vars.data.attribute.code }}">
|
<div class="attribute" data-id="{{ form.vars.data.attribute.code }}">
|
||||||
<div class="attribute-row">
|
<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 %}
|
{% 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 %}
|
{% else %}
|
||||||
<i class="globe icon"></i>
|
<i class="globe icon"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ form.vars.value.attribute.translation(form.vars.value.localeCode).name }}
|
{{ form.vars.value.attribute.translation(form.vars.value.localeCode).name }}
|
||||||
|
</label>
|
||||||
</div>
|
</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 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"' : '' }}>
|
<div {{ 'checkbox' in form.children.value.vars.block_prefixes ? 'class="ui toggle checkbox"' : '' }}>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
{% if product.simple %}
|
{% if product.simple %}
|
||||||
{{ form_row(form.variant.shippingRequired) }}
|
{{ form_row(form.variant.shippingRequired) }}
|
||||||
{% else %}
|
{% 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) }}
|
{{ form_row(form.variantSelectionMethod) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,4 +46,43 @@ class ProductOptionRepository extends EntityRepository implements ProductOptionR
|
||||||
->getResult()
|
->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;
|
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\Model\ProductInterface;
|
||||||
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
|
use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
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 */
|
/** Options should be disabled for configurable product if it has at least one defined variant */
|
||||||
$disableOptions = (null !== $this->variantResolver->getVariant($product)) && $product->hasVariants();
|
$disableOptions = (null !== $this->variantResolver->getVariant($product)) && $product->hasVariants();
|
||||||
|
|
||||||
$form->add('options', ProductOptionChoiceType::class, [
|
$form->add('options', ProductOptionAutocompleteType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'disabled' => $disableOptions,
|
'disabled' => $disableOptions,
|
||||||
'multiple' => true,
|
'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
|
expose: true
|
||||||
type: integer
|
type: integer
|
||||||
xml_attribute: true
|
xml_attribute: true
|
||||||
groups: [Default, Detailed]
|
groups: [Default, Detailed, Autocomplete]
|
||||||
code:
|
code:
|
||||||
expose: true
|
expose: true
|
||||||
type: string
|
type: string
|
||||||
groups: [Default, Detailed]
|
groups: [Default, Detailed, Autocomplete]
|
||||||
position:
|
position:
|
||||||
expose: true
|
expose: true
|
||||||
type: integer
|
type: integer
|
||||||
|
|
@ -23,3 +23,7 @@ Sylius\Component\Product\Model\ProductOption:
|
||||||
expose: true
|
expose: true
|
||||||
type: iterable
|
type: iterable
|
||||||
groups: [Default, Detailed]
|
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_widget(form.comment, sylius_test_form_attribute('comment')|sylius_merge_recursive( {'attr': {'placeholder': 'sylius.ui.write_your_own_review'|trans}})) }}
|
||||||
{{ form_errors(form.comment) }}
|
{{ form_errors(form.comment) }}
|
||||||
</div>
|
</div>
|
||||||
{% if not is_granted("IS_AUTHENTICATED_FULLY") %}
|
{% if not is_granted("IS_AUTHENTICATED_REMEMBERED") %}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{ form_widget(form.author.email, sylius_test_form_attribute('author-email')|sylius_merge_recursive( {'attr': {'placeholder': 'sylius.ui.email'|trans}})) }}
|
{{ 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) }}
|
{{ form_errors(form.author.email) }}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use Sylius\Component\Resource\Factory\FactoryInterface;
|
||||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||||
use Sylius\Component\Taxonomy\Generator\TaxonSlugGeneratorInterface;
|
use Sylius\Component\Taxonomy\Generator\TaxonSlugGeneratorInterface;
|
||||||
use Sylius\Component\Taxonomy\Model\TaxonInterface;
|
use Sylius\Component\Taxonomy\Model\TaxonInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
@ -33,8 +34,11 @@ final class TaxonSlugController
|
||||||
public function generateAction(Request $request): Response
|
public function generateAction(Request $request): Response
|
||||||
{
|
{
|
||||||
$name = (string) $request->query->get('name');
|
$name = (string) $request->query->get('name');
|
||||||
|
if ('' === trim($name)) {
|
||||||
|
throw new BadRequestException('Cannot generate slug without a name');
|
||||||
|
}
|
||||||
|
|
||||||
$locale = (string) $request->query->get('locale');
|
$locale = (string) $request->query->get('locale');
|
||||||
$parentId = $request->query->get('parentId');
|
|
||||||
|
|
||||||
/** @var TaxonInterface $taxon */
|
/** @var TaxonInterface $taxon */
|
||||||
$taxon = $this->taxonFactory->createNew();
|
$taxon = $this->taxonFactory->createNew();
|
||||||
|
|
@ -42,12 +46,25 @@ final class TaxonSlugController
|
||||||
$taxon->setFallbackLocale($locale);
|
$taxon->setFallbackLocale($locale);
|
||||||
$taxon->setName($name);
|
$taxon->setName($name);
|
||||||
|
|
||||||
if (null !== $parentId) {
|
$taxon->setParent($this->getParentTaxon($request));
|
||||||
$taxon->setParent($this->taxonRepository->find($parentId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
'slug' => $this->taxonSlugGenerator->generate($taxon, $locale),
|
'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 %}
|
{%- endblock sylius_translations_row %}
|
||||||
|
|
||||||
{% block sylius_resource_autocomplete_choice_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) -}}
|
{{- form_label(form) -}}
|
||||||
<div
|
<div
|
||||||
class="sylius-autocomplete ui fluid search selection dropdown {% if multiple %}multiple{% endif %}"
|
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;
|
public function createListQueryBuilder(string $locale): QueryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array|ProductOptionInterface[]
|
* @return ProductOptionInterface[]
|
||||||
*/
|
*/
|
||||||
public function findByName(string $name, string $locale): array;
|
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