mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
Merge branch '2.0' into api-platform-3
* 2.0: [Behat] Add tests for filtering and sorting customer groups [Behat] Enable customer group browsing and deleting scenarios [Behat] Remove redundant at sign UserDropdownComponent - fix return type Remove unnecessary function [Admin][Locale] Fix path to form's template [Admin][Locale] Minor improvements [Admin][Locale] Add filtering tests [Admin][Locale] Remove update action [Admin][Locale] Enable behat tests [Admin][Locale] Add create/update page [Core] Remove LocaleTypeExtension [Admin] Change the name of the service to provide the logged in admin user [Admin] Extract logged in admin user providing [Admin] Fix 404 page when there is no route for url [Admin] Remove icon from existing avatar
This commit is contained in:
commit
9138cc4a1a
54 changed files with 1047 additions and 363 deletions
|
|
@ -11,14 +11,14 @@ Feature: Browsing administration panel in a proper locale
|
|||
And I am logged in as an administrator
|
||||
And I am using "Polish (Poland)" locale for my panel
|
||||
|
||||
@todo @ui
|
||||
@todo-api @ui
|
||||
Scenario: Getting errors in my language
|
||||
Given I am editing my details
|
||||
When I change its email to "wrong-email"
|
||||
And I save my changes
|
||||
Then I should be notified that this email is not valid in "Polish (Poland)" locale
|
||||
|
||||
@todo @ui
|
||||
@no-api @ui
|
||||
Scenario: Seeing menu in my language
|
||||
When I open administration dashboard
|
||||
Then I should see sidebar catalog section configuration in "Polish (Poland)" locale
|
||||
Then I should be viewing the administration panel in "Polish (Poland)" locale
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Feature: Deleting multiple channels
|
|||
And the store operates on another channel named "DE Store"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@@ui @mink:chromedriver @no-api
|
||||
@no-api @ui @mink:chromedriver
|
||||
Scenario: Deleting multiple channels at once
|
||||
When I browse channels
|
||||
And I check the "PL Store" channel
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ Feature: Channel independent translatable entities locales
|
|||
And it uses the "English (United States)" locale by default
|
||||
And I am logged in as an administrator
|
||||
|
||||
@no-api @todo @ui
|
||||
@no-api @ui
|
||||
Scenario: Using all locales to translate an entity
|
||||
Given the store has locale "German (Germany)"
|
||||
When I want to create a new translatable entity
|
||||
Then I should be able to translate it in "English (United States)"
|
||||
And I should be able to translate it in "German (Germany)"
|
||||
But I should not be able to translate it in "Polish (Poland)"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Feature: Adding a new locale
|
|||
Background:
|
||||
Given I am logged in as an administrator
|
||||
|
||||
@api @ui
|
||||
@api @ui @mink:chromedriver
|
||||
Scenario: Adding a new locale
|
||||
When I want to create a new locale
|
||||
And I choose Norwegian
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
@managing_locales
|
||||
Feature: Filtering locales
|
||||
In order to quickly find the locale I need
|
||||
As an Administrator
|
||||
I want to filter available locales
|
||||
|
||||
Background:
|
||||
Given the store has locale "English (United States)"
|
||||
And the store has locale "Norwegian (Norway)"
|
||||
And the store has locale "Polish (Poland)"
|
||||
And I am logged in as an administrator
|
||||
And I am browsing locales
|
||||
|
||||
@todo-api @ui
|
||||
Scenario: Filtering locales by code
|
||||
When I filter by code containing "pl"
|
||||
Then I should see a single locale in the list
|
||||
And I should see the locale "Polish (Poland)"
|
||||
|
|
@ -8,7 +8,7 @@ Feature: Locale unique code validation
|
|||
Given the store has locale "Norwegian (Norway)"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@api @ui
|
||||
@api @ui @mink:chromedriver
|
||||
Scenario: Trying to add new locale with used code
|
||||
When I want to add a new locale
|
||||
Then I should not be able to choose "Norwegian (Norway)"
|
||||
|
|
|
|||
|
|
@ -11,36 +11,36 @@ Feature: Switching locales on admin's panel
|
|||
And I am logged in as an administrator
|
||||
And I am using "English (United States)" locale for my panel
|
||||
|
||||
@todo @ui
|
||||
@no-api @ui
|
||||
Scenario: Changing my preferred locale
|
||||
Given I am editing my details
|
||||
When I set my locale to "Spanish (Mexico)"
|
||||
Then I should be viewing the administration panel in "Spanish (Mexico)"
|
||||
Then I should be viewing the administration panel in "Spanish (Mexico)" locale
|
||||
|
||||
@todo @ui
|
||||
@no-api @ui
|
||||
Scenario: Changing my preferred language to a locale that does not exist in the store
|
||||
Given the locale "French (France)" does not exist in the store
|
||||
And I am editing my details
|
||||
When I set my locale to "French (France)"
|
||||
Then I should be viewing the administration panel in "French (France)"
|
||||
Then I should be viewing the administration panel in "French (France)" locale
|
||||
|
||||
@todo @ui
|
||||
@no-api @ui
|
||||
Scenario: Changing panel's locale doesn't change shop's locale
|
||||
Given I am editing my details
|
||||
When I set my locale to "Spanish (Mexico)"
|
||||
And I browse that channel
|
||||
Then I should still shop using the "English (United States)" locale
|
||||
|
||||
@todo @ui
|
||||
@no-api @ui
|
||||
Scenario: Changing shop's locale has doesn't affect admin panel's locale
|
||||
Given I switched the shop's locale to "Spanish (Mexico)"
|
||||
When I open administration dashboard
|
||||
Then I should still be viewing the administration panel in "English (United States)"
|
||||
Then I should still be viewing the administration panel in "English (United States)" locale
|
||||
|
||||
@todo @ui
|
||||
@no-api @ui
|
||||
Scenario: Locales are saved per each admin's preference
|
||||
Given I am using "Spanish (Mexico)" locale for my panel
|
||||
And there is an administrator "admin@example.com" identified by "sylius"
|
||||
And this administrator is using "French (France)" locale
|
||||
When this administrator logs in using "sylius" password
|
||||
Then they should be viewing the administration panel in "French (France)"
|
||||
Then they should be viewing the administration panel in "French (France)" locale
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: Deleting multiple customer groups
|
|||
Given the store has customer groups "Retail", "Wholesale" and "General"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@todo @ui @mink:chromedriver @no-api
|
||||
@no-api @ui @mink:chromedriver
|
||||
Scenario: Deleting multiple customer groups at once
|
||||
When I browse customer groups
|
||||
And I check the "Retail" customer group
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
@managing_customer_groups
|
||||
Feature: Filtering customer groups
|
||||
In order to see specific customer groups in the store
|
||||
As an Administrator
|
||||
I want to filter customer groups
|
||||
|
||||
Background:
|
||||
Given the store has a customer group "Online sale" with "sale" code
|
||||
And the store has a customer group "Retail" with "retail" code
|
||||
And the store has a customer group "Wholesale" with "whole" code
|
||||
And I am logged in as an administrator
|
||||
|
||||
@api-todo @ui
|
||||
Scenario: Filtering customer groups
|
||||
When I browse customer groups
|
||||
And I search for them by "sale"
|
||||
Then there should be 2 customer groups in the list
|
||||
And the 1st customer group on the list should have code "sale" and name "Online sale"
|
||||
And the 2nd customer group on the list should have code "whole" and name "Wholesale"
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@managing_customer_groups
|
||||
Feature: Sorting customer groups
|
||||
In order to see all customer groups in the store sorted in a specific way
|
||||
As an Administrator
|
||||
I want to sort customer groups
|
||||
|
||||
Background:
|
||||
Given the store has a customer group "Wholesale" with "aaa" code
|
||||
And the store has a customer group "Retail" with "bbb" code
|
||||
And the store has a customer group "Online sale" with "ccc" code
|
||||
And I am logged in as an administrator
|
||||
|
||||
@api-todo @ui
|
||||
Scenario: Sorting customer groups by name in ascending order
|
||||
When I browse customer groups
|
||||
And I sort them by the name in ascending order
|
||||
Then there should be 3 customer groups in the list
|
||||
And the 1st customer group on the list should have code "ccc" and name "Online sale"
|
||||
And the 2nd customer group on the list should have code "bbb" and name "Retail"
|
||||
And the 3rd customer group on the list should have code "aaa" and name "Wholesale"
|
||||
|
||||
@api-todo @ui
|
||||
Scenario: Sorting customer groups by name in descending order
|
||||
When I browse customer groups
|
||||
And I sort them by the name in descending order
|
||||
Then there should be 3 customer groups in the list
|
||||
And the 1st customer group on the list should have code "aaa" and name "Wholesale"
|
||||
And the 2nd customer group on the list should have code "bbb" and name "Retail"
|
||||
And the 3rd customer group on the list should have code "ccc" and name "Online sale"
|
||||
|
||||
@api-todo @ui
|
||||
Scenario: Sorting customer groups by code in ascending order
|
||||
When I browse customer groups
|
||||
And I sort them by the code in ascending order
|
||||
Then there should be 3 customer groups in the list
|
||||
And the 1st customer group on the list should have code "aaa" and name "Wholesale"
|
||||
And the 2nd customer group on the list should have code "bbb" and name "Retail"
|
||||
And the 3rd customer group on the list should have code "ccc" and name "Online sale"
|
||||
|
||||
@api-todo @ui
|
||||
Scenario: Sorting customer groups by code in descending order
|
||||
When I browse customer groups
|
||||
And I sort them by the code in descending order
|
||||
Then there should be 3 customer groups in the list
|
||||
And the 1st customer group on the list should have code "ccc" and name "Online sale"
|
||||
And the 2nd customer group on the list should have code "bbb" and name "Retail"
|
||||
And the 3rd customer group on the list should have code "aaa" and name "Wholesale"
|
||||
|
|
@ -3520,11 +3520,6 @@ parameters:
|
|||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Form/EventSubscriber/CustomerRegistrationFormSubscriber.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\CoreBundle\\\\Form\\\\Extension\\\\LocaleTypeExtension\\:\\:__construct\\(\\) has parameter \\$localeRepository with generic interface Sylius\\\\Component\\\\Resource\\\\Repository\\\\RepositoryInterface but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Sylius/Bundle/CoreBundle/Form/Extension/LocaleTypeExtension.php
|
||||
|
||||
-
|
||||
message: "#^Method Sylius\\\\Bundle\\\\CoreBundle\\\\Form\\\\Type\\\\CatalogPromotionScope\\\\ForProductsScopeConfigurationType\\:\\:__construct\\(\\) has parameter \\$productsToCodesTransformer with generic interface Symfony\\\\Component\\\\Form\\\\DataTransformerInterface but does not specify its types\\: T, R$#"
|
||||
count: 1
|
||||
|
|
|
|||
|
|
@ -65,14 +65,6 @@ final class LocaleContext implements Context
|
|||
return $localeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Transform :localeName
|
||||
*/
|
||||
public function castToLocaleName(string $localeCode): string
|
||||
{
|
||||
return $this->localeNameConverter->convertCodeToName($localeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Transform :locale
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use Sylius\Behat\Page\Admin\DashboardPageInterface;
|
|||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class LocaleContext implements Context
|
||||
final readonly class LocaleContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private DashboardPageInterface $dashboardPage,
|
||||
|
|
@ -29,15 +29,17 @@ final class LocaleContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then I should be viewing the administration panel in :localeCode
|
||||
* @Then I should still be viewing the administration panel in :localeCode
|
||||
* @Then they should be viewing the administration panel in :localeCode
|
||||
* @Then I should be viewing the administration panel in :localeCode locale
|
||||
* @Then I should still be viewing the administration panel in :localeCode locale
|
||||
* @Then they should be viewing the administration panel in :localeCode locale
|
||||
*/
|
||||
public function iShouldBeViewingTheAdministrationPanelIn($localeCode)
|
||||
public function iShouldBeViewingTheAdministrationPanelIn(string $localeCode): void
|
||||
{
|
||||
$this->dashboardPage->open();
|
||||
if (!$this->dashboardPage->isOpen()) {
|
||||
$this->dashboardPage->open();
|
||||
}
|
||||
|
||||
Assert::same($this->dashboardPage->getSubHeader(), $this->translate('sylius.ui.overview_of_your_store', $localeCode));
|
||||
Assert::same($this->dashboardPage->getDashboardHeader(), $this->translate('sylius.ui.dashboard', $localeCode));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -45,15 +47,7 @@ final class LocaleContext implements Context
|
|||
*/
|
||||
public function iShouldBeNotifiedThatThisEmailIsNotValidInLocale(string $localeCode): void
|
||||
{
|
||||
Assert::same($this->createPage->getValidationMessage('email'), $this->translate('sylius.contact.email.invalid', $localeCode, 'validators'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see sidebar catalog section configuration in :localeCode locale
|
||||
*/
|
||||
public function iShouldSeeSidebarSectionConfigurationInLocale(string $localeCode): void
|
||||
{
|
||||
Assert::true($this->dashboardPage->isSectionWithLabelVisible($this->translate('sylius.menu.admin.main.catalog.header', $localeCode)));
|
||||
Assert::same($this->createPage->getValidationMessage('field_email'), $this->translate('sylius.contact.email.invalid', $localeCode, 'validators'));
|
||||
}
|
||||
|
||||
private function translate(string $text, string $localeCode, ?string $domain = null): string
|
||||
|
|
|
|||
|
|
@ -70,16 +70,6 @@ final readonly class ManagingCustomerGroupsContext implements Context
|
|||
$this->createPage->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the customer group :customerGroup should appear in the store
|
||||
*/
|
||||
public function theCustomerGroupShouldAppearInTheStore(CustomerGroupInterface $customerGroup): void
|
||||
{
|
||||
$this->indexPage->open();
|
||||
|
||||
Assert::true($this->indexPage->isSingleResourceOnPage(['name' => $customerGroup->getName()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I want to edit (this customer group)$/
|
||||
*/
|
||||
|
|
@ -113,6 +103,33 @@ final readonly class ManagingCustomerGroupsContext implements Context
|
|||
$this->indexPage->bulkDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I browse customer groups
|
||||
* @When I want to browse customer groups
|
||||
*/
|
||||
public function iWantToBrowseCustomerGroups(): void
|
||||
{
|
||||
$this->indexPage->open();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I sort them by the (code|name) in (asc|desc)ending order$/
|
||||
*/
|
||||
public function iSortThemByTheField(string $field, string $order): void
|
||||
{
|
||||
$this->indexPage->sortBy($field, $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the customer group :customerGroup should appear in the store
|
||||
*/
|
||||
public function theCustomerGroupShouldAppearInTheStore(CustomerGroupInterface $customerGroup): void
|
||||
{
|
||||
$this->indexPage->open();
|
||||
|
||||
Assert::true($this->indexPage->isSingleResourceOnPage(['name' => $customerGroup->getName()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then this customer group with name :name should appear in the store
|
||||
* @Then I should see the customer group :name in the list
|
||||
|
|
@ -125,17 +142,18 @@ final readonly class ManagingCustomerGroupsContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @When I browse customer groups
|
||||
* @When I want to browse customer groups
|
||||
* @Then there should be :amountOfCustomerGroups customer groups in the list
|
||||
*/
|
||||
public function iWantToBrowseCustomerGroups(): void
|
||||
public function thereShouldBeCustomerGroupsInTheList(int $amountOfCustomerGroups = 1): void
|
||||
{
|
||||
$this->indexPage->open();
|
||||
Assert::same($this->indexPage->countItems(), $amountOfCustomerGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see a single customer group in the list
|
||||
* @Then I should see :amountOfCustomerGroups customer groups in the list
|
||||
*
|
||||
* This step is a duplicate of the above because some scenarios require to open the index page before checking anything
|
||||
*/
|
||||
public function iShouldSeeCustomerGroupsInTheList(int $amountOfCustomerGroups = 1): void
|
||||
{
|
||||
|
|
@ -144,6 +162,25 @@ final readonly class ManagingCustomerGroupsContext implements Context
|
|||
Assert::same($this->indexPage->countItems(), $amountOfCustomerGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the (\d+)(?:|st|nd|rd|th) customer group on the list should have (name|code) "([^"]+)" and (name|code) "([^"]+)"$/
|
||||
*/
|
||||
public function theFirstCustomerGroupOnTheListShouldHave(
|
||||
int $position,
|
||||
string $firstField,
|
||||
string $firstValue,
|
||||
string $secondField,
|
||||
string $secondValue,
|
||||
): void {
|
||||
$fields = $this->indexPage->getColumnFields($firstField);
|
||||
|
||||
Assert::same($fields[$position - 1], $firstValue);
|
||||
|
||||
$fields = $this->indexPage->getColumnFields($secondField);
|
||||
|
||||
Assert::same($fields[$position - 1], $secondValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^(this customer group) should still be named "([^"]+)"$/
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -14,28 +14,36 @@ declare(strict_types=1);
|
|||
namespace Sylius\Behat\Context\Ui\Admin;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Element\Admin\Locale\FormElementInterface;
|
||||
use Sylius\Behat\NotificationType;
|
||||
use Sylius\Behat\Page\Admin\Crud\IndexPageInterface;
|
||||
use Sylius\Behat\Page\Admin\Locale\CreatePageInterface;
|
||||
use Sylius\Behat\Page\Admin\Crud\CreatePageInterface;
|
||||
use Sylius\Behat\Page\Admin\Locale\IndexPageInterface;
|
||||
use Sylius\Behat\Service\NotificationCheckerInterface;
|
||||
use Sylius\Component\Locale\Converter\LocaleConverterInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ManagingLocalesContext implements Context
|
||||
final readonly class ManagingLocalesContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private CreatePageInterface $createPage,
|
||||
private IndexPageInterface $indexPage,
|
||||
private FormElementInterface $formElement,
|
||||
private NotificationCheckerInterface $notificationChecker,
|
||||
private LocaleConverterInterface $localeConverter,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given I am browsing locales
|
||||
*/
|
||||
public function iAmBrowsingLocales(): void
|
||||
{
|
||||
$this->indexPage->open();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I want to create a new locale
|
||||
* @When I want to add a new locale
|
||||
*/
|
||||
public function iWantToCreateNewLocale()
|
||||
public function iWantToCreateNewLocale(): void
|
||||
{
|
||||
$this->createPage->open();
|
||||
}
|
||||
|
|
@ -43,15 +51,15 @@ final class ManagingLocalesContext implements Context
|
|||
/**
|
||||
* @When I choose :name
|
||||
*/
|
||||
public function iChoose($name)
|
||||
public function iChoose(string $name): void
|
||||
{
|
||||
$this->createPage->chooseName($name);
|
||||
$this->formElement->chooseLocale($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I add it
|
||||
*/
|
||||
public function iAdd()
|
||||
public function iAdd(): void
|
||||
{
|
||||
$this->createPage->create();
|
||||
}
|
||||
|
|
@ -65,10 +73,19 @@ final class ManagingLocalesContext implements Context
|
|||
$this->indexPage->deleteResourceOnPage(['code' => $localeCode]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I filter by code containing :phrase
|
||||
*/
|
||||
public function iFilterByCodeContaining(string $phrase): void
|
||||
{
|
||||
$this->indexPage->filterByCode($phrase);
|
||||
$this->indexPage->filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the store should be available in the :name language
|
||||
*/
|
||||
public function storeShouldBeAvailableInLanguage($name)
|
||||
public function storeShouldBeAvailableInLanguage(string $name): void
|
||||
{
|
||||
$doesLocaleExist = $this->indexPage->isSingleResourceOnPage(['name' => $name]);
|
||||
|
||||
|
|
@ -78,9 +95,9 @@ final class ManagingLocalesContext implements Context
|
|||
/**
|
||||
* @Then I should not be able to choose :name
|
||||
*/
|
||||
public function iShouldNotBeAbleToChoose($name)
|
||||
public function iShouldNotBeAbleToChoose(string $name): void
|
||||
{
|
||||
Assert::false($this->createPage->isOptionAvailable($name));
|
||||
Assert::false($this->formElement->isLocaleAvailable($name));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,4 +138,20 @@ final class ManagingLocalesContext implements Context
|
|||
{
|
||||
Assert::true($this->indexPage->isSingleResourceOnPage(['code' => $localeCode]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see a single locale in the list
|
||||
*/
|
||||
public function iShouldSeeLocaleInTheList(): void
|
||||
{
|
||||
Assert::same($this->indexPage->countItems(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see the locale :localeName
|
||||
*/
|
||||
public function iShouldSeeTheLocale(string $localeName): void
|
||||
{
|
||||
Assert::true($this->indexPage->isSingleResourceOnPage(['name' => $localeName]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,18 +14,23 @@ declare(strict_types=1);
|
|||
namespace Sylius\Behat\Context\Ui\Admin;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
use Sylius\Behat\Element\Admin\Taxon\FormElementInterface;
|
||||
use Sylius\Behat\Page\Admin\Crud\CreatePageInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final readonly class ManagingTranslatableEntitiesContext implements Context
|
||||
{
|
||||
public function __construct(private CreatePageInterface $taxonCreatePage)
|
||||
{
|
||||
public function __construct(
|
||||
private CreatePageInterface $taxonCreatePage,
|
||||
private FormElementInterface $taxonFormElement,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I want to create a new translatable entity
|
||||
*/
|
||||
public function iWantToCreateANewTranslatableEntity()
|
||||
public function iWantToCreateANewTranslatableEntity(): void
|
||||
{
|
||||
$this->taxonCreatePage->open();
|
||||
}
|
||||
|
|
@ -33,8 +38,19 @@ final readonly class ManagingTranslatableEntitiesContext implements Context
|
|||
/**
|
||||
* @Then I should be able to translate it in :localeCode
|
||||
*/
|
||||
public function iShouldBeAbleToTranslateItIn($localeCode)
|
||||
public function iShouldBeAbleToTranslateItIn(string $localeCode): void
|
||||
{
|
||||
$this->taxonCreatePage->describeItAs('Foo bar', $localeCode);
|
||||
$this->taxonFormElement->describeItAs('Description', $localeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should not be able to translate it in :localeCode
|
||||
*/
|
||||
public function iShouldNotBeAbleToTranslateItIn(string $localeCode): void
|
||||
{
|
||||
Assert::throws(
|
||||
fn () => $this->taxonFormElement->describeItAs('Description', $localeCode),
|
||||
ElementNotFoundException::class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Sylius\Behat\Context\Ui\Admin;
|
|||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Element\Admin\Crud\Index\SearchFilterElementInterface;
|
||||
|
||||
final class SearchFilterContext implements Context
|
||||
final readonly class SearchFilterContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private SearchFilterElementInterface $searchFilterElement,
|
||||
|
|
|
|||
64
src/Sylius/Behat/Element/Admin/Locale/FormElement.php
Normal file
64
src/Sylius/Behat/Element/Admin/Locale/FormElement.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?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\Element\Admin\Locale;
|
||||
|
||||
use Behat\Mink\Session;
|
||||
use FriendsOfBehat\SymfonyExtension\Mink\MinkParameters;
|
||||
use Sylius\Behat\Element\Admin\Crud\FormElement as BaseFormElement;
|
||||
use Sylius\Behat\Service\Helper\AutocompleteHelperInterface;
|
||||
|
||||
final class FormElement extends BaseFormElement implements FormElementInterface
|
||||
{
|
||||
public function __construct(
|
||||
Session $session,
|
||||
array|MinkParameters $minkParameters,
|
||||
private readonly AutocompleteHelperInterface $autocompleteHelper,
|
||||
) {
|
||||
parent::__construct($session, $minkParameters);
|
||||
}
|
||||
|
||||
public function chooseLocale(string $localeName): void
|
||||
{
|
||||
$this->autocompleteHelper->selectByName(
|
||||
$this->getDriver(),
|
||||
$this->getElement('code')->getXpath(),
|
||||
$localeName,
|
||||
);
|
||||
}
|
||||
|
||||
public function isLocaleAvailable(string $localeName): bool
|
||||
{
|
||||
$elements = $this->autocompleteHelper->search(
|
||||
$this->getDriver(),
|
||||
$this->getElement('code')->getXpath(),
|
||||
$localeName,
|
||||
);
|
||||
|
||||
foreach ($elements as $element) {
|
||||
if (str_contains($element, $localeName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getDefinedElements(): array
|
||||
{
|
||||
return array_merge(parent::getDefinedElements(), [
|
||||
'code' => '[data-test-code]',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?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\Element\Admin\Locale;
|
||||
|
||||
use Sylius\Behat\Element\Admin\Crud\FormElementInterface as BaseFormElementInterface;
|
||||
use Sylius\Component\Core\Model\TaxonInterface;
|
||||
|
||||
interface FormElementInterface extends BaseFormElementInterface
|
||||
{
|
||||
public function chooseLocale(string $localeName): void;
|
||||
|
||||
public function isLocaleAvailable(string $localeName): bool;
|
||||
}
|
||||
|
|
@ -73,16 +73,9 @@ class DashboardPage extends SymfonyPage implements DashboardPageInterface
|
|||
return $this->getElement('average_order_value')->getText();
|
||||
}
|
||||
|
||||
/** @throws ElementNotFoundException */
|
||||
public function getSubHeader(): string
|
||||
public function getDashboardHeader(): string
|
||||
{
|
||||
return trim($this->getElement('sub_header')->getText());
|
||||
}
|
||||
|
||||
/** @throws ElementNotFoundException */
|
||||
public function isSectionWithLabelVisible(string $name): bool
|
||||
{
|
||||
return $this->getElement('admin_menu')->find('css', sprintf('div:contains(%s)', $name)) !== null;
|
||||
return $this->getElement('dashboard_header')->getText();
|
||||
}
|
||||
|
||||
/** @throws ElementNotFoundException */
|
||||
|
|
@ -142,11 +135,11 @@ class DashboardPage extends SymfonyPage implements DashboardPageInterface
|
|||
protected function getDefinedElements(): array
|
||||
{
|
||||
return array_merge(parent::getDefinedElements(), [
|
||||
'admin_menu' => '.sylius-admin-menu',
|
||||
'average_order_value' => '[data-test-average-order-value]',
|
||||
'channel_choosing_button' => '[data-test-choose-channel-button]',
|
||||
'channel_choosing_list' => '[data-test-choose-channel-list] a:contains("%channelName%")',
|
||||
'customer_list' => '#customers',
|
||||
'dashboard_header' => '[data-test-dashboard-header]',
|
||||
'dropdown' => 'i.dropdown',
|
||||
'logout' => '[data-test-user-dropdown-item="Logout"]',
|
||||
'month_split_by_days_statistics_button' => 'button[data-stats-button="month"]',
|
||||
|
|
@ -157,7 +150,6 @@ class DashboardPage extends SymfonyPage implements DashboardPageInterface
|
|||
'previous_period' => '[data-test-previous-period]',
|
||||
'product_navbar_search' => '[data-test-navbar-product-search]',
|
||||
'statistics_component' => '[data-test-statistics-component]',
|
||||
'sub_header' => '.ui.header .content .sub.header',
|
||||
'total_sales' => '[data-test-total-sales]',
|
||||
'year_split_by_months_statistics_button' => '[data-test-year-split-into-months]',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -30,9 +30,7 @@ interface DashboardPageInterface extends SymfonyPageInterface
|
|||
|
||||
public function getAverageOrderValue(): string;
|
||||
|
||||
public function getSubHeader(): string;
|
||||
|
||||
public function isSectionWithLabelVisible(string $name): bool;
|
||||
public function getDashboardHeader(): string;
|
||||
|
||||
public function logOut(): void;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
<?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\Page\Admin\Locale;
|
||||
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
use Sylius\Behat\Behaviour\ChoosesName;
|
||||
use Sylius\Behat\Page\Admin\Crud\CreatePage as BaseCreatePage;
|
||||
|
||||
class CreatePage extends BaseCreatePage implements CreatePageInterface
|
||||
{
|
||||
use ChoosesName;
|
||||
|
||||
public function isOptionAvailable(string $name): bool
|
||||
{
|
||||
try {
|
||||
$this->chooseName($name);
|
||||
|
||||
return true;
|
||||
} catch (ElementNotFoundException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<?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\Page\Admin\Locale;
|
||||
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
use Sylius\Behat\Page\Admin\Crud\CreatePageInterface as BaseCreatePageInterface;
|
||||
|
||||
interface CreatePageInterface extends BaseCreatePageInterface
|
||||
{
|
||||
/**
|
||||
* @throws ElementNotFoundException
|
||||
*/
|
||||
public function chooseName(string $name): void;
|
||||
|
||||
public function isOptionAvailable(string $name): bool;
|
||||
}
|
||||
31
src/Sylius/Behat/Page/Admin/Locale/IndexPage.php
Normal file
31
src/Sylius/Behat/Page/Admin/Locale/IndexPage.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?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\Page\Admin\Locale;
|
||||
|
||||
use Sylius\Behat\Page\Admin\Crud\IndexPage as BaseIndexPage;
|
||||
|
||||
class IndexPage extends BaseIndexPage implements IndexPageInterface
|
||||
{
|
||||
public function filterByCode(string $code): void
|
||||
{
|
||||
$this->getElement('code_filter')->setValue($code);
|
||||
}
|
||||
|
||||
protected function getDefinedElements(): array
|
||||
{
|
||||
return array_merge(parent::getDefinedElements(), [
|
||||
'code_filter' => '#criteria_code_value',
|
||||
]);
|
||||
}
|
||||
}
|
||||
21
src/Sylius/Behat/Page/Admin/Locale/IndexPageInterface.php
Normal file
21
src/Sylius/Behat/Page/Admin/Locale/IndexPageInterface.php
Normal file
|
|
@ -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\Page\Admin\Locale;
|
||||
|
||||
use Sylius\Behat\Page\Admin\Crud\IndexPageInterface as BaseIndexPageInterface;
|
||||
|
||||
interface IndexPageInterface extends BaseIndexPageInterface
|
||||
{
|
||||
public function filterByCode(string $code): void;
|
||||
}
|
||||
|
|
@ -125,8 +125,8 @@
|
|||
<service id="sylius.behat.context.ui.admin.managing_locales" class="Sylius\Behat\Context\Ui\Admin\ManagingLocalesContext">
|
||||
<argument type="service" id="sylius.behat.page.admin.locale.create" />
|
||||
<argument type="service" id="sylius.behat.page.admin.locale.index" />
|
||||
<argument type="service" id="sylius.behat.element.admin.locale.form" />
|
||||
<argument type="service" id="sylius.behat.notification_checker.admin" />
|
||||
<argument type="service" id="sylius.locale_converter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.ui.admin.managing_orders" class="Sylius\Behat\Context\Ui\Admin\ManagingOrdersContext">
|
||||
|
|
@ -302,6 +302,7 @@
|
|||
|
||||
<service id="sylius.behat.context.ui.admin.managing_translatable_entities" class="Sylius\Behat\Context\Ui\Admin\ManagingTranslatableEntitiesContext">
|
||||
<argument type="service" id="sylius.behat.page.admin.taxon.create" />
|
||||
<argument type="service" id="sylius.behat.element.admin.taxon.form" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.ui.admin.managing_taxons" class="Sylius\Behat\Context\Ui\Admin\ManagingTaxonsContext">
|
||||
|
|
|
|||
|
|
@ -129,5 +129,13 @@
|
|||
>
|
||||
<argument type="service" id="Sylius\Behat\Service\Helper\AutocompleteHelperInterface" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
id="sylius.behat.element.admin.locale.form"
|
||||
class="Sylius\Behat\Element\Admin\Locale\FormElement"
|
||||
parent="sylius.behat.element.admin.crud.form"
|
||||
>
|
||||
<argument type="service" id="Sylius\Behat\Service\Helper\AutocompleteHelperInterface" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
|
||||
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
<parameters>
|
||||
<parameter key="sylius.behat.page.admin.locale.create.class">Sylius\Behat\Page\Admin\Locale\CreatePage</parameter>
|
||||
<parameter key="sylius.behat.page.admin.locale.index.class">%sylius.behat.page.admin.crud.index.class%</parameter>
|
||||
<parameter key="sylius.behat.page.admin.locale.create.class">%sylius.behat.page.admin.crud.create.class%</parameter>
|
||||
<parameter key="sylius.behat.page.admin.locale.index.class">Sylius\Behat\Page\Admin\Locale\IndexPage</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ default:
|
|||
|
||||
- sylius.behat.context.ui.admin.managing_customer_groups
|
||||
- sylius.behat.context.ui.admin.notification
|
||||
- sylius.behat.context.ui.admin.search_filter
|
||||
|
||||
filters:
|
||||
tags: "@managing_customer_groups&&@ui"
|
||||
|
|
|
|||
|
|
@ -14,10 +14,45 @@ declare(strict_types=1);
|
|||
namespace Sylius\Bundle\AdminBundle\Form\Type;
|
||||
|
||||
use Sylius\Bundle\LocaleBundle\Form\Type\LocaleType as BaseLocaleType;
|
||||
use Sylius\Component\Locale\Model\LocaleInterface;
|
||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\LocaleType as SymfonyLocaleType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Intl\Locales;
|
||||
|
||||
final class LocaleType extends AbstractType
|
||||
{
|
||||
/** @param RepositoryInterface<LocaleInterface> $localeRepository */
|
||||
public function __construct(private readonly RepositoryInterface $localeRepository)
|
||||
{
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void {
|
||||
$options = [
|
||||
'label' => 'sylius.form.locale.name',
|
||||
'choice_loader' => null,
|
||||
'placeholder' => 'sylius.form.locale.select',
|
||||
'autocomplete' => true,
|
||||
];
|
||||
|
||||
$locale = $event->getData();
|
||||
if ($locale instanceof LocaleInterface && null !== $locale->getCode()) {
|
||||
$options['disabled'] = true;
|
||||
$options['choices'] = [ Locales::getName($locale->getCode()) => $locale->getCode()];
|
||||
} else {
|
||||
$options['choices'] = array_flip($this->getAvailableLocales());
|
||||
}
|
||||
|
||||
$form = $event->getForm();
|
||||
$form->add('code', SymfonyLocaleType::class, $options);
|
||||
});
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'sylius_admin_locale';
|
||||
|
|
@ -27,4 +62,18 @@ final class LocaleType extends AbstractType
|
|||
{
|
||||
return BaseLocaleType::class;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
private function getAvailableLocales(): array
|
||||
{
|
||||
$availableLocales = Locales::getNames();
|
||||
|
||||
$definedLocales = $this->localeRepository->findAll();
|
||||
|
||||
foreach ($definedLocales as $locale) {
|
||||
unset($availableLocales[$locale->getCode()]);
|
||||
}
|
||||
|
||||
return $availableLocales;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Provider;
|
||||
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
|
||||
final readonly class LoggedInAdminUserProvider implements LoggedInAdminUserProviderInterface
|
||||
{
|
||||
private const SECURITY_SESSION_KEY = '_security_admin';
|
||||
|
||||
/** @param UserRepositoryInterface<AdminUserInterface> $adminUserRepository */
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private TokenStorageInterface $tokenStorage,
|
||||
private RequestStack $requestStack,
|
||||
private UserRepositoryInterface $adminUserRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUser(): ?AdminUserInterface
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
if ($user instanceof AdminUserInterface) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$user = $this->getUserFromTokenStorage();
|
||||
if (null !== $user) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
return $this->getUserFromSession();
|
||||
}
|
||||
|
||||
public function hasUser(): bool
|
||||
{
|
||||
return
|
||||
$this->security->getUser() instanceof AdminUserInterface ||
|
||||
null !== $this->getUserFromTokenStorage() ||
|
||||
null !== $this->getSerializedTokenFromSession()
|
||||
;
|
||||
}
|
||||
|
||||
private function getUserFromTokenStorage(): ?AdminUserInterface
|
||||
{
|
||||
$user = $this->tokenStorage->getToken()?->getUser();
|
||||
if ($user instanceof AdminUserInterface) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getUserFromSession(): ?AdminUserInterface
|
||||
{
|
||||
$serializedToken = $this->getSerializedTokenFromSession();
|
||||
if (null === $serializedToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = unserialize($serializedToken);
|
||||
if (!$token instanceof TokenInterface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = $token->getUser();
|
||||
if (!$user instanceof AdminUserInterface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->adminUserRepository->find($user->getId());
|
||||
}
|
||||
|
||||
private function getSerializedTokenFromSession(): ?string
|
||||
{
|
||||
try {
|
||||
$serializedToken = $this->requestStack->getMainRequest()?->getSession()->get(self::SECURITY_SESSION_KEY);
|
||||
if (null !== $serializedToken) {
|
||||
return $serializedToken;
|
||||
}
|
||||
|
||||
return $this->requestStack->getSession()->get(self::SECURITY_SESSION_KEY);
|
||||
} catch (SessionNotFoundException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\AdminBundle\Provider;
|
||||
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
|
||||
interface LoggedInAdminUserProviderInterface
|
||||
{
|
||||
public function hasUser(): bool;
|
||||
|
||||
public function getUser(): ?AdminUserInterface;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
twig_hooks:
|
||||
hooks:
|
||||
'sylius_admin.locale.create.content':
|
||||
form:
|
||||
template: '@SyliusAdmin/shared/crud/common/content/form.html.twig'
|
||||
configuration:
|
||||
render_rest: false
|
||||
|
||||
'sylius_admin.locale.create.content.form':
|
||||
sections:
|
||||
template: '@SyliusAdmin/locale/form/sections.html.twig'
|
||||
|
||||
'sylius_admin.locale.create.content.form.sections':
|
||||
details:
|
||||
template: '@SyliusAdmin/locale/form/sections/details.html.twig'
|
||||
|
||||
'sylius_admin.locale.create.content.form.sections.details':
|
||||
code:
|
||||
template: '@SyliusAdmin/locale/form/sections/details/code.html.twig'
|
||||
|
|
@ -30,7 +30,5 @@ sylius_grid:
|
|||
create:
|
||||
type: create
|
||||
item:
|
||||
update:
|
||||
type: update
|
||||
delete:
|
||||
type: delete
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ sylius_admin_locale:
|
|||
alias: sylius.locale
|
||||
section: admin
|
||||
templates: "@SyliusAdmin\\shared\\crud"
|
||||
except: ['show', 'delete']
|
||||
only: ['index', 'create']
|
||||
redirect: index
|
||||
grid: sylius_admin_locale
|
||||
form:
|
||||
|
|
|
|||
|
|
@ -121,5 +121,13 @@
|
|||
<argument type="service" id="sylius.generator.taxon_slug" />
|
||||
</service>
|
||||
<service id="Sylius\Bundle\AdminBundle\Generator\TaxonSlugGeneratorInterface" alias="sylius_admin.generator.taxon_slug" />
|
||||
|
||||
<service id="sylius_admin.logged_in_admin_user_provider" class="Sylius\Bundle\AdminBundle\Provider\LoggedInAdminUserProvider">
|
||||
<argument type="service" id="security.helper" />
|
||||
<argument type="service" id="security.token_storage" />
|
||||
<argument type="service" id="request_stack" />
|
||||
<argument type="service" id="sylius.repository.admin_user" />
|
||||
</service>
|
||||
<service id="Sylius\Bundle\AdminBundle\Provider\LoggedInAdminUserProviderInterface" alias="sylius_admin.logged_in_admin_user_provider" />
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@
|
|||
</service>
|
||||
|
||||
<service id="sylius_admin.form.type.locale" class="Sylius\Bundle\AdminBundle\Form\Type\LocaleType" >
|
||||
<argument type="service" id="sylius.repository.locale" />
|
||||
<tag name="form.type" />
|
||||
</service>
|
||||
|
||||
|
|
|
|||
|
|
@ -308,8 +308,7 @@
|
|||
class="Sylius\Bundle\AdminBundle\Twig\Component\Shared\Navbar\UserDropdownComponent"
|
||||
>
|
||||
<argument type="service" id="router" />
|
||||
<argument type="service" id="translator" />
|
||||
<argument type="service" id="security.helper" />
|
||||
<argument type="service" id="sylius_admin.logged_in_admin_user_provider" />
|
||||
|
||||
<tag
|
||||
name="twig.component"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<services>
|
||||
<service id="sylius_admin.twig.error_template_finder" class="Sylius\Bundle\AdminBundle\Twig\ErrorTemplateFinder\ErrorTemplateFinder">
|
||||
<argument type="service" id="Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface" />
|
||||
<argument type="service" id="security.token_storage" />
|
||||
<argument type="service" id="sylius_admin.logged_in_admin_user_provider" />
|
||||
<argument type="service" id="twig" />
|
||||
<tag name="sylius.twig.error_template_finder" />
|
||||
</service>
|
||||
|
|
|
|||
|
|
@ -13,75 +13,63 @@ declare(strict_types=1);
|
|||
|
||||
namespace Sylius\Bundle\AdminBundle\Twig\Component\Shared\Navbar;
|
||||
|
||||
use Sylius\Component\User\Model\UserInterface;
|
||||
use Sylius\Bundle\AdminBundle\Provider\LoggedInAdminUserProviderInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;
|
||||
|
||||
class UserDropdownComponent
|
||||
{
|
||||
public function __construct(
|
||||
private UrlGeneratorInterface $urlGenerator,
|
||||
private TranslatorInterface $translator,
|
||||
private Security $security,
|
||||
private readonly UrlGeneratorInterface $urlGenerator,
|
||||
private readonly LoggedInAdminUserProviderInterface $loggedInAdminUserProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
#[ExposeInTemplate(name: 'user')]
|
||||
public function getUser(): UserInterface
|
||||
public function getUser(): AdminUserInterface
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof UserInterface) {
|
||||
throw new \RuntimeException('User must be an instance of Sylius\Component\User\Model\UserInterface');
|
||||
$user = $this->loggedInAdminUserProvider->getUser();
|
||||
if (!$user instanceof AdminUserInterface) {
|
||||
throw new \RuntimeException('User must be an instance of ' . AdminUserInterface::class . '.');
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, array{title?: string, url?: string, icon?: string, type?: string, class?: string}>>
|
||||
*
|
||||
* @psalm-suppress InvalidReturnType
|
||||
* @return array<array-key, array{title?: string, url?: string, icon?: string, type?: string, class?: string}>
|
||||
*/
|
||||
#[ExposeInTemplate(name: 'menu_items')]
|
||||
public function getMenuItems(): array
|
||||
{
|
||||
/**
|
||||
* @phpstan-ignore-next-line PHPStan complains the declared return type does not match the returned value
|
||||
*
|
||||
* @psalm-suppress InvalidReturnStatement
|
||||
*/
|
||||
// TODO: Would be nice to have these set via hook //
|
||||
return [
|
||||
[
|
||||
'title' => $this->translator->trans('sylius.ui.my_account'),
|
||||
'title' => 'sylius.ui.my_account',
|
||||
'url' => $this->urlGenerator->generate('sylius_admin_admin_user_update', ['id' => $this->getUser()->getId()]),
|
||||
'icon' => 'user',
|
||||
],
|
||||
[
|
||||
'title' => $this->translator->trans('sylius.ui.logout'),
|
||||
'title' => 'sylius.ui.logout',
|
||||
'url' => $this->urlGenerator->generate('sylius_admin_logout'),
|
||||
'icon' => 'logout',
|
||||
'attr' => [
|
||||
'data-test-logout' => null,
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'divider',
|
||||
],
|
||||
[
|
||||
'title' => $this->translator->trans('sylius.ui.documentation'),
|
||||
'title' => 'sylius.ui.documentation',
|
||||
'url' => 'https://docs.sylius.com',
|
||||
'class' => 'small text-muted',
|
||||
],
|
||||
[
|
||||
'title' => $this->translator->trans('sylius.ui.join_slack'),
|
||||
'title' => 'sylius.ui.join_slack',
|
||||
'url' => 'https://sylius.com/slack',
|
||||
'class' => 'small text-muted',
|
||||
],
|
||||
[
|
||||
'title' => $this->translator->trans('sylius.ui.report_an_issue'),
|
||||
'title' => 'sylius.ui.report_an_issue',
|
||||
'url' => 'https://github.com/Sylius/Sylius/issues',
|
||||
'class' => 'small text-muted',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -13,18 +13,17 @@ declare(strict_types=1);
|
|||
|
||||
namespace Sylius\Bundle\AdminBundle\Twig\ErrorTemplateFinder;
|
||||
|
||||
use Sylius\Bundle\AdminBundle\Provider\LoggedInAdminUserProviderInterface;
|
||||
use Sylius\Bundle\AdminBundle\SectionResolver\AdminSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Bundle\UiBundle\Twig\ErrorTemplateFinder\ErrorTemplateFinderInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
final readonly class ErrorTemplateFinder implements ErrorTemplateFinderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private SectionProviderInterface $sectionProvider,
|
||||
private TokenStorageInterface $tokenStorage,
|
||||
private LoggedInAdminUserProviderInterface $loggedInAdminUserProvider,
|
||||
private Environment $twig,
|
||||
) {
|
||||
}
|
||||
|
|
@ -33,7 +32,7 @@ final readonly class ErrorTemplateFinder implements ErrorTemplateFinderInterface
|
|||
{
|
||||
$section = $this->sectionProvider->getSection();
|
||||
|
||||
if ($section instanceof AdminSection && $this->isLoggedInAdmin()) {
|
||||
if ($section instanceof AdminSection && $this->loggedInAdminUserProvider->hasUser()) {
|
||||
$template = sprintf('@SyliusAdmin/errors/error%s.html.twig', $statusCode);
|
||||
if ($this->twig->getLoader()->exists($template)) {
|
||||
return $template;
|
||||
|
|
@ -47,9 +46,4 @@ final readonly class ErrorTemplateFinder implements ErrorTemplateFinderInterface
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function isLoggedInAdmin(): bool
|
||||
{
|
||||
return $this->tokenStorage->getToken()?->getUser() instanceof AdminUserInterface;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,272 @@
|
|||
<?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\AdminBundle\Provider;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\AdminBundle\Provider\LoggedInAdminUserProviderInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
|
||||
final class LoggedInAdminUserProviderSpec extends ObjectBehavior
|
||||
{
|
||||
private const SECURITY_SESSION_KEY = '_security_admin';
|
||||
private const SERIALIZED_TOKEN = 'O:74:"Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken":3:{i:0;N;i:1;s:5:"admin";i:2;a:5:{i:0;O:37:"Sylius\Component\Core\Model\AdminUser":8:{i:0;s:38:"sylius{x33enl1y0askgkgw8k0skocc4ko0kg}";i:1;s:30:"x33enl1y0askgkgw8k0skocc4ko0kg";i:2;s:6:"sylius";i:3;s:6:"sylius";i:4;b:0;i:5;b:1;i:6;i:404;i:7;s:9:"plaintext";}i:1;b:1;i:2;N;i:3;a:0:{}i:4;a:1:{i:0;s:26:"ROLE_ADMINISTRATION_ACCESS";}}}';
|
||||
|
||||
function let(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
UserRepositoryInterface $adminUserRepository,
|
||||
) {
|
||||
$this->beConstructedWith($security, $tokenStorage, $requestStack, $adminUserRepository);
|
||||
}
|
||||
|
||||
function it_implements_logged_in_admin_user_provider(): void
|
||||
{
|
||||
$this->shouldImplement(LoggedInAdminUserProviderInterface::class);
|
||||
}
|
||||
|
||||
function it_returns_true_when_user_is_in_security(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$security->getUser()->willReturn($adminUser);
|
||||
|
||||
$tokenStorage->getToken()->shouldNotBeCalled();
|
||||
$requestStack->getMainRequest()->shouldNotBeCalled();
|
||||
$requestStack->getSession()->shouldNotBeCalled();
|
||||
|
||||
$this->hasUser()->shouldReturn(true);
|
||||
}
|
||||
|
||||
function it_returns_true_when_user_is_in_token_storage(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$tokenStorage->getToken()->willReturn($token);
|
||||
|
||||
$requestStack->getMainRequest()->shouldNotBeCalled();
|
||||
$requestStack->getSession()->shouldNotBeCalled();
|
||||
|
||||
$this->hasUser()->shouldReturn(true);
|
||||
}
|
||||
|
||||
function it_returns_true_when_user_is_in_main_request_session_token(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
Request $request,
|
||||
Session $session,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$session->get(self::SECURITY_SESSION_KEY)->willReturn('serialized_token');
|
||||
$request->getSession()->willReturn($session);
|
||||
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
$requestStack->getSession()->shouldNotBeCalled();
|
||||
|
||||
$this->hasUser()->shouldReturn(true);
|
||||
}
|
||||
|
||||
function it_returns_true_when_user_is_in_current_request_session_token(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
Session $session,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$session->get(self::SECURITY_SESSION_KEY)->willReturn('serialized_token');
|
||||
|
||||
$requestStack->getMainRequest()->willReturn(null);
|
||||
$requestStack->getSession()->willReturn($session);
|
||||
|
||||
$this->hasUser()->shouldReturn(true);
|
||||
}
|
||||
|
||||
function it_returns_false_when_there_is_no_user(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
Session $session,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
|
||||
$requestStack->getMainRequest()->willReturn(null);
|
||||
|
||||
$session->get(self::SECURITY_SESSION_KEY)->willReturn(null);
|
||||
$requestStack->getSession()->willReturn($session);
|
||||
|
||||
$this->hasUser()->shouldReturn(false);
|
||||
}
|
||||
|
||||
function it_returns_false_when_user_cannot_be_provided_and_session_is_not_available_in_current_request(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
|
||||
$requestStack->getMainRequest()->willReturn(null);
|
||||
$requestStack->getSession()->willThrow(SessionNotFoundException::class);
|
||||
|
||||
$this->hasUser()->shouldReturn(false);
|
||||
}
|
||||
|
||||
function it_returns_false_when_user_cannot_be_provided_and_session_is_not_available_in_main_request(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
Request $request,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
$request->getSession()->willThrow(SessionNotFoundException::class);
|
||||
|
||||
$this->hasUser()->shouldReturn(false);
|
||||
}
|
||||
|
||||
function it_gets_user_from_security(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
UserRepositoryInterface $adminUserRepository,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$security->getUser()->willReturn($adminUser);
|
||||
|
||||
$tokenStorage->getToken()->shouldNotBeCalled();
|
||||
$requestStack->getMainRequest()->shouldNotBeCalled();
|
||||
$requestStack->getSession()->shouldNotBeCalled();
|
||||
$adminUserRepository->find(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->getUser()->shouldReturn($adminUser);
|
||||
}
|
||||
|
||||
function it_gets_user_from_token_storage(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
UserRepositoryInterface $adminUserRepository,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$tokenStorage->getToken()->willReturn($token);
|
||||
|
||||
$requestStack->getMainRequest()->shouldNotBeCalled();
|
||||
$requestStack->getSession()->shouldNotBeCalled();
|
||||
$adminUserRepository->find(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->getUser()->shouldReturn($adminUser);
|
||||
}
|
||||
|
||||
function it_gets_user_from_main_request_session(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
UserRepositoryInterface $adminUserRepository,
|
||||
Request $request,
|
||||
Session $session,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$session->get(self::SECURITY_SESSION_KEY)->willReturn(self::SERIALIZED_TOKEN);
|
||||
$request->getSession()->willReturn($session);
|
||||
$requestStack->getMainRequest()->willReturn($request);
|
||||
|
||||
$adminUser->getId()->willReturn(404);
|
||||
$adminUserRepository->find(404)->willReturn($adminUser);
|
||||
|
||||
$requestStack->getSession()->shouldNotBeCalled();
|
||||
|
||||
$this->getUser()->shouldReturn($adminUser);
|
||||
}
|
||||
|
||||
function it_gets_user_from_current_request_session(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
UserRepositoryInterface $adminUserRepository,
|
||||
Request $request,
|
||||
Session $session,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
$requestStack->getMainRequest()->willReturn(null);
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$session->get(self::SECURITY_SESSION_KEY)->willReturn(self::SERIALIZED_TOKEN);
|
||||
$request->getSession()->willReturn($session);
|
||||
$requestStack->getSession()->willReturn($session);
|
||||
|
||||
$adminUser->getId()->willReturn(404);
|
||||
$adminUserRepository->find(404)->willReturn($adminUser);
|
||||
|
||||
$this->getUser()->shouldReturn($adminUser);
|
||||
}
|
||||
|
||||
function it_returns_null_when_user_cannot_be_provided(
|
||||
Security $security,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
RequestStack $requestStack,
|
||||
UserRepositoryInterface $adminUserRepository,
|
||||
): void {
|
||||
$security->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
$requestStack->getMainRequest()->willReturn(null);
|
||||
$requestStack->getSession()->willThrow(SessionNotFoundException::class);
|
||||
|
||||
$adminUserRepository->find(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->getUser()->shouldReturn(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,17 +14,11 @@ declare(strict_types=1);
|
|||
namespace spec\Sylius\Bundle\AdminBundle\Twig\ErrorTemplateFinder;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\AdminBundle\Provider\LoggedInAdminUserProviderInterface;
|
||||
use Sylius\Bundle\AdminBundle\SectionResolver\AdminSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionInterface;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Bundle\UiBundle\Twig\ErrorTemplateFinder\ErrorTemplateFinderInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\LoaderInterface;
|
||||
|
||||
|
|
@ -32,9 +26,12 @@ final class ErrorTemplateFinderSpec extends ObjectBehavior
|
|||
{
|
||||
private const TEMPLATE_PREFIX = '@SyliusAdmin/errors';
|
||||
|
||||
function let(SectionProviderInterface $sectionProvider, TokenStorageInterface $tokenStorage, Environment $twig): void
|
||||
{
|
||||
$this->beConstructedWith($sectionProvider, $tokenStorage, $twig);
|
||||
function let(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
LoggedInAdminUserProviderInterface $loggedInAdminUserProvider,
|
||||
Environment $twig,
|
||||
): void {
|
||||
$this->beConstructedWith($sectionProvider, $loggedInAdminUserProvider, $twig);
|
||||
}
|
||||
|
||||
function it_implements_error_template_finder_interface(): void
|
||||
|
|
@ -44,58 +41,26 @@ final class ErrorTemplateFinderSpec extends ObjectBehavior
|
|||
|
||||
function it_does_not_find_template_for_other_sections_than_admin(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
LoggedInAdminUserProviderInterface $loggedInAdminUserProvider,
|
||||
Environment $twig,
|
||||
SectionInterface $section,
|
||||
): void {
|
||||
$sectionProvider->getSection()->willReturn($section);
|
||||
|
||||
$tokenStorage->getToken()->shouldNotBeCalled();
|
||||
$loggedInAdminUserProvider->hasUser()->shouldNotBeCalled();
|
||||
$twig->getLoader()->shouldNotBeCalled();
|
||||
|
||||
$this->findTemplate(404)->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_does_not_find_template_when_there_is_no_token_in_admin_section(
|
||||
function it_does_not_find_template_when_there_is_no_admin_user(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
LoggedInAdminUserProviderInterface $loggedInAdminUserProvider,
|
||||
Environment $twig,
|
||||
): void {
|
||||
$sectionProvider->getSection()->willReturn(new AdminSection());
|
||||
$tokenStorage->getToken()->willReturn(null);
|
||||
|
||||
$twig->getLoader()->shouldNotBeCalled();
|
||||
|
||||
$this->findTemplate(404)->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_does_not_find_template_when_there_is_no_token_user_in_admin_section(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
Environment $twig,
|
||||
TokenInterface $token,
|
||||
): void {
|
||||
$sectionProvider->getSection()->willReturn(new AdminSection());
|
||||
|
||||
$token->getUser()->willReturn(null);
|
||||
$tokenStorage->getToken()->willReturn($token);
|
||||
|
||||
$twig->getLoader()->shouldNotBeCalled();
|
||||
|
||||
$this->findTemplate(404)->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_does_not_find_template_when_the_token_user_is_not_an_admin_in_admin_section(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
Environment $twig,
|
||||
TokenInterface $token,
|
||||
UserInterface $user,
|
||||
): void {
|
||||
$sectionProvider->getSection()->willReturn(new AdminSection());
|
||||
|
||||
$token->getUser()->willReturn($user);
|
||||
$tokenStorage->getToken()->willReturn($token);
|
||||
$loggedInAdminUserProvider->hasUser()->willReturn(false);
|
||||
|
||||
$twig->getLoader()->shouldNotBeCalled();
|
||||
|
||||
|
|
@ -104,18 +69,14 @@ final class ErrorTemplateFinderSpec extends ObjectBehavior
|
|||
|
||||
function it_finds_template_for_admin(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
LoggedInAdminUserProviderInterface $loggedInAdminUserProvider,
|
||||
Environment $twig,
|
||||
LoaderInterface $loader,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$templateName = self::TEMPLATE_PREFIX . '/error404.html.twig';
|
||||
|
||||
$sectionProvider->getSection()->willReturn(new AdminSection());
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$tokenStorage->getToken()->willReturn($token);
|
||||
$loggedInAdminUserProvider->hasUser()->willReturn(true);
|
||||
|
||||
$twig->getLoader()->willReturn($loader);
|
||||
$loader->exists($templateName)->shouldBeCalled()->willReturn(true);
|
||||
|
|
@ -125,19 +86,15 @@ final class ErrorTemplateFinderSpec extends ObjectBehavior
|
|||
|
||||
function it_returns_null_if_neither_template_can_be_found(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
LoggedInAdminUserProviderInterface $loggedInAdminUserProvider,
|
||||
Environment $twig,
|
||||
LoaderInterface $loader,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$templateName = self::TEMPLATE_PREFIX . '/error404.html.twig';
|
||||
$fallbackTemplateName = self::TEMPLATE_PREFIX . '/error.html.twig';
|
||||
|
||||
$sectionProvider->getSection()->willReturn(new AdminSection());
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$tokenStorage->getToken()->willReturn($token);
|
||||
$loggedInAdminUserProvider->hasUser()->willReturn(true);
|
||||
|
||||
$twig->getLoader()->willReturn($loader);
|
||||
$loader->exists($templateName)->shouldBeCalled()->willReturn(false);
|
||||
|
|
@ -148,19 +105,15 @@ final class ErrorTemplateFinderSpec extends ObjectBehavior
|
|||
|
||||
function it_finds_fallback_template_for_admin(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
LoggedInAdminUserProviderInterface $loggedInAdminUserProvider,
|
||||
Environment $twig,
|
||||
LoaderInterface $loader,
|
||||
TokenInterface $token,
|
||||
AdminUserInterface $adminUser,
|
||||
): void {
|
||||
$templateName = self::TEMPLATE_PREFIX . '/error404.html.twig';
|
||||
$fallbackTemplateName = self::TEMPLATE_PREFIX . '/error.html.twig';
|
||||
|
||||
$sectionProvider->getSection()->willReturn(new AdminSection());
|
||||
|
||||
$token->getUser()->willReturn($adminUser);
|
||||
$tokenStorage->getToken()->willReturn($token);
|
||||
$loggedInAdminUserProvider->hasUser()->willReturn(true);
|
||||
|
||||
$twig->getLoader()->willReturn($loader);
|
||||
$loader->exists($templateName)->shouldBeCalled()->willReturn(false);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% import '@SyliusAdmin/shared/helper/header.html.twig' as _header %}
|
||||
|
||||
<div class="col-12 col-md-6 mb-3 mt-3">
|
||||
<div class="d-md-flex gap-2 align-items-center">
|
||||
<div class="d-md-flex gap-2 align-items-center" {{ sylius_test_html_attribute('dashboard-header') }}>
|
||||
{{ _header.h1('sylius.ui.dashboard'|trans) }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
{% extends '@SyliusAdmin/shared/layout/base.html.twig' %}
|
||||
|
||||
{%- block title -%}
|
||||
{{ 'sylius.ui.page_not_found'|trans }} | {{ parent() }}
|
||||
{%- endblock -%}
|
||||
|
||||
{% block body %}
|
||||
{% hook 'sylius_admin.errors.404' %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<div class="row">
|
||||
{% hook 'sections' %}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<div class="col-12 p-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
{{ 'sylius.ui.details'|trans }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
{% hook 'details' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<div class="mb-3">
|
||||
{{ form_row(hookable_metadata.context.form.code, sylius_test_form_attribute('code')) }}
|
||||
</div>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
</div>
|
||||
<div class="d-none d-xl-block ps-2">
|
||||
<div class="mb-1 small text-muted">Hello</div>
|
||||
<div>{{ user.firstName }}</div>
|
||||
<div>{{ user.firstName|default(user.username) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
{% macro default(img, content, class) %}
|
||||
{% macro default(img, class) %}
|
||||
{% from '@SyliusAdmin/shared/helper/icon.html.twig' import icon %}
|
||||
|
||||
<span class="sylius avatar {{ class }}" {% if img %} style="background-image: url({{ img }})" {% endif %}>
|
||||
{% if content %}
|
||||
{{ content }}
|
||||
{% else %}
|
||||
{% if img is not defined or img is empty %}
|
||||
{{ icon({ icon: 'user' }) }}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro small(img, content, class) %}
|
||||
{% macro small(img, class) %}
|
||||
{% import _self as _ %}
|
||||
{{ _.default(img, content, 'avatar-sm ' ~ class) }}
|
||||
{{ _.default(img, 'avatar-sm ' ~ class) }}
|
||||
{% endmacro %}
|
||||
|
|
|
|||
|
|
@ -37,14 +37,14 @@
|
|||
}|merge(item) %}
|
||||
|
||||
{% if item.type == 'header' %}
|
||||
<span class="dropdown-header">{{ item.title }}</span>
|
||||
<span class="dropdown-header">{{ item.title|trans }}</span>
|
||||
{% elseif item.type == 'divider' %}
|
||||
<hr class="dropdown-divider">
|
||||
{% else %}
|
||||
{% set extra_class = item.class ? ' ' ~ item.class %}
|
||||
<a class="dropdown-item{{ item.class ? extra_class }}" href="{{ item.url }}" {{ sylius_test_html_attribute('user-dropdown-item', item.title) }}>
|
||||
<a class="dropdown-item{{ item.class ? extra_class }}" href="{{ item.url }}" {{ sylius_test_html_attribute('user-dropdown-item', item.title|trans({}, null, 'en_US')) }}>
|
||||
{{ item.icon ? icon({ icon: item.icon, class: 'icon dropdown-item-icon icon-tabler' ~ extra_class }) }}
|
||||
{{ item.title }}
|
||||
{{ item.title|trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
<?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\CoreBundle\Form\Extension;
|
||||
|
||||
use Sylius\Bundle\LocaleBundle\Form\Type\LocaleType;
|
||||
use Sylius\Component\Locale\Model\LocaleInterface;
|
||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Intl\Locales;
|
||||
|
||||
trigger_deprecation(
|
||||
'sylius/core-bundle',
|
||||
'1.14',
|
||||
'The "%s" class is deprecated and will be removed in Sylius 2.0.',
|
||||
LocaleTypeExtension::class,
|
||||
);
|
||||
|
||||
/** @deprecated since Sylius 1.14 and will be removed in Sylius 2.0. */
|
||||
final class LocaleTypeExtension extends AbstractTypeExtension
|
||||
{
|
||||
public function __construct(private RepositoryInterface $localeRepository)
|
||||
{
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
|
||||
$options = [
|
||||
'label' => 'sylius.form.locale.name',
|
||||
'choice_loader' => null,
|
||||
];
|
||||
|
||||
$locale = $event->getData();
|
||||
if ($locale instanceof LocaleInterface && null !== $locale->getCode()) {
|
||||
$options['disabled'] = true;
|
||||
|
||||
$options['choices'] = [$this->getLocaleName($locale->getCode()) => $locale->getCode()];
|
||||
} else {
|
||||
$options['choices'] = array_flip($this->getAvailableLocales());
|
||||
}
|
||||
|
||||
$form = $event->getForm();
|
||||
$form->add('code', \Symfony\Component\Form\Extension\Core\Type\LocaleType::class, $options);
|
||||
});
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [LocaleType::class];
|
||||
}
|
||||
|
||||
private function getLocaleName(string $code): string
|
||||
{
|
||||
return Locales::getName($code);
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
private function getAvailableLocales(): array
|
||||
{
|
||||
$availableLocales = Locales::getNames();
|
||||
|
||||
/** @var LocaleInterface[] $definedLocales */
|
||||
$definedLocales = $this->localeRepository->findAll();
|
||||
|
||||
foreach ($definedLocales as $locale) {
|
||||
unset($availableLocales[$locale->getCode()]);
|
||||
}
|
||||
|
||||
return $availableLocales;
|
||||
}
|
||||
}
|
||||
|
|
@ -70,10 +70,6 @@
|
|||
<service id="sylius.form.extension.type.channel" class="Sylius\Bundle\CoreBundle\Form\Extension\ChannelTypeExtension">
|
||||
<tag name="form.type_extension" extended-type="Sylius\Bundle\ChannelBundle\Form\Type\ChannelType" priority="100" />
|
||||
</service>
|
||||
<service id="sylius.form.extension.type.locale" class="Sylius\Bundle\CoreBundle\Form\Extension\LocaleTypeExtension">
|
||||
<argument type="service" id="sylius.repository.locale" />
|
||||
<tag name="form.type_extension" extended-type="Sylius\Bundle\LocaleBundle\Form\Type\LocaleType" priority="100" />
|
||||
</service>
|
||||
<service id="sylius.form.extension.type.order" class="Sylius\Bundle\CoreBundle\Form\Extension\OrderTypeExtension">
|
||||
<tag name="form.type_extension" extended-type="Sylius\Bundle\OrderBundle\Form\Type\OrderType" priority="100" />
|
||||
</service>
|
||||
|
|
|
|||
121
tests/Functional/AdminSectionNotFoundPageTest.php
Normal file
121
tests/Functional/AdminSectionNotFoundPageTest.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?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\Tests\Functional;
|
||||
|
||||
use ApiTestCase\JsonApiTestCase;
|
||||
|
||||
final class AdminSectionNotFoundPageTest extends JsonApiTestCase
|
||||
{
|
||||
private const ADMIN_404_PAGE_HOOK = 'data-test-back-to-dashboard-link';
|
||||
private const SHOP_404_PAGE_HOOK = 'data-test-shop-not-found-page';
|
||||
|
||||
/** @before */
|
||||
public function setUpClient(): void
|
||||
{
|
||||
$this->client = self::createClient(['debug' => false], ['HTTP_ACCEPT' => 'text/html']);
|
||||
$this->client->followRedirects();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getSyliusResourcesUrlPart
|
||||
*/
|
||||
public function it_shows_admin_not_found_page_for_a_logged_in_admin_when_accessing_nonexistent_resource_edit_page(
|
||||
string $syliusResourceUrlPart,
|
||||
): void {
|
||||
$this->loginAdminUser();
|
||||
|
||||
$this->client->request('GET', '/admin/'. $syliusResourceUrlPart . '/0/edit');
|
||||
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
|
||||
$content = $this->client->getResponse()->getContent();
|
||||
$this->assertStringContainsString(self::ADMIN_404_PAGE_HOOK, $content);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_shows_admin_not_found_page_for_a_logged_in_admin_when_accessing_an_unknown_url(): void
|
||||
{
|
||||
$this->loginAdminUser();
|
||||
|
||||
$this->client->request('GET', '/admin/this-url-does-not-exist');
|
||||
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
|
||||
$content = $this->client->getResponse()->getContent();
|
||||
$this->assertStringContainsString(self::ADMIN_404_PAGE_HOOK, $content);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_shows_shop_not_found_page_for_a_visitor_when_accessing_an_unknown_url(): void
|
||||
{
|
||||
$this->loadFixtures();
|
||||
|
||||
$this->client->request('GET', '/admin/this-section-does-not-exist');
|
||||
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
|
||||
$content = $this->client->getResponse()->getContent();
|
||||
$this->assertStringContainsString(self::SHOP_404_PAGE_HOOK, $content);
|
||||
}
|
||||
|
||||
/** @return iterable<string[]> */
|
||||
private static function getSyliusResourcesUrlPart(): iterable
|
||||
{
|
||||
yield ['users'];
|
||||
yield ['catalog-promotions'];
|
||||
yield ['channels'];
|
||||
yield ['countries'];
|
||||
yield ['customers'];
|
||||
yield ['customer-groups'];
|
||||
yield ['exchange-rates'];
|
||||
yield ['locales'];
|
||||
yield ['orders'];
|
||||
yield ['payment-methods'];
|
||||
yield ['products'];
|
||||
yield ['product-association-types'];
|
||||
yield ['product-attributes'];
|
||||
yield ['product-options'];
|
||||
yield ['product-reviews'];
|
||||
yield ['promotions'];
|
||||
yield ['shipping-categories'];
|
||||
yield ['shipping-methods'];
|
||||
yield ['taxons'];
|
||||
yield ['tax-categories'];
|
||||
yield ['tax-rates'];
|
||||
yield ['zones'];
|
||||
}
|
||||
|
||||
private function loginAdminUser(): void
|
||||
{
|
||||
$this->loadFixtures();
|
||||
|
||||
$this->client->request('GET', '/admin/login');
|
||||
$this->client->submitForm('Login', [
|
||||
'_username' => 'sylius',
|
||||
'_password' => 'sylius',
|
||||
]);
|
||||
$afterLoginContent = $this->client->getResponse()->getContent();
|
||||
|
||||
$this->assertStringNotContainsString('Invalid credentials', $afterLoginContent);
|
||||
}
|
||||
|
||||
private function loadFixtures(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles([
|
||||
'authentication/administrator.yml',
|
||||
'resources/channels.yml',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue