diff --git a/assets/js/frontend/utils/Window.js b/assets/js/frontend/utils/Window.js index dcf756a922..ee0c200aaf 100644 --- a/assets/js/frontend/utils/Window.js +++ b/assets/js/frontend/utils/Window.js @@ -7,8 +7,6 @@ const defaults = { buttonClose: true, buttonCancel: false, buttonContinue: false, - textContinue: Translator.trans('Yes'), - textCancel: Translator.trans('No'), textHeading: '', urlContinue: '#', cssClass: 'window-popup--standard', @@ -40,7 +38,7 @@ export default class Window { constructor (inputOptions) { this.$activeWindow = null; - this.options = $.extend(defaults, inputOptions); + this.options = { textContinue: Translator.trans('Yes'), textCancel: Translator.trans('No'), ...defaults, ...inputOptions }; if (this.$activeWindow !== null) { this.$activeWindow.trigger('windowFastClose'); diff --git a/build.xml b/build.xml index c63fbbd185..607edccb20 100644 --- a/build.xml +++ b/build.xml @@ -7,7 +7,7 @@ - + diff --git a/composer.json b/composer.json index bc17cf76ee..91a8b057a3 100644 --- a/composer.json +++ b/composer.json @@ -73,7 +73,7 @@ "shopsys/product-feed-zbozi": "9.1.x-dev", "shopsys/product-feed-google": "9.1.x-dev", "shopsys/read-model": "9.1.x-dev", - "snc/redis-bundle": "^3.2.1", + "snc/redis-bundle": "^3.2.2", "stof/doctrine-extensions-bundle": "^1.3.0", "symfony-cmf/routing": "^2.0.3", "symfony-cmf/routing-bundle": "^2.0.3", diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 6ca5d18471..ad0713945a 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -80,6 +80,7 @@ security: - { path: ^/login-as-remembered-user/$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/admin/superadmin/, roles: ROLE_SUPER_ADMIN } - { path: ^/admin/cron/*, roles: ROLE_SUPER_ADMIN } + - { path: ^/admin/currency/, roles: ROLE_SUPER_ADMIN } - { path: ^/admin/translation/list/$, roles: ROLE_SUPER_ADMIN } - { path: ^/admin/$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/admin/authorization/$, roles: IS_AUTHENTICATED_ANONYMOUSLY } diff --git a/config/packages/snc_redis.yaml b/config/packages/snc_redis.yaml index 451375c677..4fe524a648 100644 --- a/config/packages/snc_redis.yaml +++ b/config/packages/snc_redis.yaml @@ -36,4 +36,5 @@ snc_redis: dsn: 'redis://%redis_host%' session: client: 'session' + ttl: 604800 prefix: '%env(REDIS_PREFIX)%session:' diff --git a/docker/conf/docker-compose.prod.yml.dist b/docker/conf/docker-compose.prod.yml.dist index 493e186f81..539ada5878 100644 --- a/docker/conf/docker-compose.prod.yml.dist +++ b/docker/conf/docker-compose.prod.yml.dist @@ -26,6 +26,7 @@ services: extra_hosts: - postgres:192.168.0.1 - redis:192.168.0.1 + - elasticsearch:192.168.0.1 networks: - shopsys-network diff --git a/docker/php-fpm/Dockerfile b/docker/php-fpm/Dockerfile index fe76c79721..0d20c5ff7d 100644 --- a/docker/php-fpm/Dockerfile +++ b/docker/php-fpm/Dockerfile @@ -43,7 +43,6 @@ RUN apt-get update && \ autoconf && \ apt-get clean -# "gd" extension needs to have specified jpeg and freetype dir for jpg/jpeg images support RUN docker-php-ext-configure gd --with-freetype --with-jpeg # install necessary tools for running application diff --git a/docker/php-fpm/docker-php-entrypoint b/docker/php-fpm/docker-php-entrypoint index 44a1aebcbe..40853b078c 100755 --- a/docker/php-fpm/docker-php-entrypoint +++ b/docker/php-fpm/docker-php-entrypoint @@ -11,7 +11,7 @@ PIPE=/tmp/log-pipe rm -rf $PIPE mkfifo $PIPE chmod 666 $PIPE -tail -f $PIPE & +tail -n +1 -f $PIPE & # first arg is `-f` or `--some-option` if [ "${1#-}" != "$1" ]; then diff --git a/src/Controller/Front/OrderController.php b/src/Controller/Front/OrderController.php index 13102db52e..614dc194f4 100644 --- a/src/Controller/Front/OrderController.php +++ b/src/Controller/Front/OrderController.php @@ -213,7 +213,7 @@ public function indexAction() if ($isValid) { if ($orderFlow->nextStep()) { $form = $orderFlow->createForm(); - } elseif ($this->isFlashMessageBagEmpty()) { + } elseif (count($this->getErrorMessages()) === 0 && count($this->getInfoMessages()) === 0) { $deliveryAddress = $orderData->deliveryAddressSameAsBillingAddress === false ? $frontOrderFormData->deliveryAddress : null; $order = $this->orderFacade->createOrderFromFront($orderData, $deliveryAddress); $this->orderFacade->sendHeurekaOrderInfo($order, $frontOrderFormData->disallowHeurekaVerifiedByCustomers); diff --git a/src/DataFixtures/Demo/ProductDataFixture.php b/src/DataFixtures/Demo/ProductDataFixture.php index 78babe1cef..63c44f7460 100644 --- a/src/DataFixtures/Demo/ProductDataFixture.php +++ b/src/DataFixtures/Demo/ProductDataFixture.php @@ -9,6 +9,7 @@ use DateTime; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\ORM\EntityManagerInterface; use Shopsys\FrameworkBundle\Component\DataFixture\AbstractReferenceFixture; use Shopsys\FrameworkBundle\Component\Domain\Domain; use Shopsys\FrameworkBundle\Component\Money\Money; @@ -72,26 +73,26 @@ class ProductDataFixture extends AbstractReferenceFixture implements DependentFi */ private $parameterDataFactory; - /** - * @var \Shopsys\FrameworkBundle\Model\Product\Parameter\Parameter[] - */ - private $parameters; - /** * @var int */ private $productNo = 1; /** - * @var \App\Model\Product\Product[] + * @var int[] */ - private $productsByCatnum = []; + private $productIdsByCatnum = []; /** * @var \Shopsys\FrameworkBundle\Model\Pricing\PriceConverter */ private $priceConverter; + /** + * @var \Doctrine\ORM\EntityManagerInterface + */ + private $em; + /** * @param \Shopsys\FrameworkBundle\Model\Product\ProductFacade $productFacade * @param \Shopsys\FrameworkBundle\Model\Product\ProductVariantFacade $productVariantFacade @@ -103,6 +104,7 @@ class ProductDataFixture extends AbstractReferenceFixture implements DependentFi * @param \Shopsys\FrameworkBundle\Model\Product\Parameter\ParameterFacade $parameterFacade * @param \Shopsys\FrameworkBundle\Model\Product\Parameter\ParameterDataFactory $parameterDataFactory * @param \Shopsys\FrameworkBundle\Model\Pricing\PriceConverter $priceConverter + * @param \Doctrine\ORM\EntityManagerInterface $em */ public function __construct( ProductFacade $productFacade, @@ -114,7 +116,8 @@ public function __construct( ParameterValueDataFactory $parameterValueDataFactory, ParameterFacade $parameterFacade, ParameterDataFactory $parameterDataFactory, - PriceConverter $priceConverter + PriceConverter $priceConverter, + EntityManagerInterface $em ) { $this->productFacade = $productFacade; $this->productVariantFacade = $productVariantFacade; @@ -126,6 +129,7 @@ public function __construct( $this->parameterFacade = $parameterFacade; $this->parameterDataFactory = $parameterDataFactory; $this->priceConverter = $priceConverter; + $this->em = $em; } /** @@ -5636,11 +5640,11 @@ private function createVariants(): void foreach ($variantCatnumsByMainVariantCatnum as $mainVariantCatnum => $variantsCatnums) { /** @var \App\Model\Product\Product $mainProduct */ - $mainProduct = $this->productsByCatnum[$mainVariantCatnum]; + $mainProduct = $this->getProductFromCacheByCatnum($mainVariantCatnum); $variants = []; foreach ($variantsCatnums as $variantCatnum) { - $variants[] = $this->productsByCatnum[$variantCatnum]; + $variants[] = $this->getProductFromCacheByCatnum($variantCatnum); } $mainVariant = $this->productVariantFacade->createVariant($mainProduct, $variants); @@ -5654,12 +5658,6 @@ private function createVariants(): void */ private function findParameterByNamesOrCreateNew(array $parameterNamesByLocale): Parameter { - $cacheId = json_encode($parameterNamesByLocale); - - if (isset($this->parameters[$cacheId])) { - return $this->parameters[$cacheId]; - } - $parameter = $this->parameterFacade->findParameterByNames($parameterNamesByLocale); if ($parameter === null) { @@ -5669,8 +5667,6 @@ private function findParameterByNamesOrCreateNew(array $parameterNamesByLocale): $parameter = $this->parameterFacade->create($parameterData); } - $this->parameters[$cacheId] = $parameter; - return $parameter; } @@ -5817,8 +5813,33 @@ private function setVat(ProductData $productData, ?string $vatReference): void public function addProductReference(Product $product) { $this->addReference(self::PRODUCT_PREFIX . $this->productNo, $product); - $this->productsByCatnum[$product->getCatnum()] = $product; $this->productNo++; + + if (in_array($product->getCatnum(), $this->getAllVariantCatnumsFromAssociativeArray(self::getVariantCatnumsByMainVariantCatnum()), true)) { + $this->saveProductToCache($product); + } + + $this->em->clear(); + } + + /** + * @param \App\Model\Product\Product $product + */ + private function saveProductToCache(Product $product): void + { + $this->productIdsByCatnum[$product->getCatnum()] = $product->getId(); + } + + /** + * @param string $catnum + * @return \App\Model\Product\Product + */ + private function getProductFromCacheByCatnum(string $catnum): Product + { + /** @var \App\Model\Product\Product $product */ + $product = $this->productFacade->getById($this->productIdsByCatnum[$catnum]); + + return $product; } /** @@ -5836,4 +5857,21 @@ public function getDependencies(): array SettingValueDataFixture::class, ]; } + + /** + * @param array $productCatnumsByMainVariantCatnum + * + * @return string[] + */ + private function getAllVariantCatnumsFromAssociativeArray(array $productCatnumsByMainVariantCatnum): array + { + $catnums = []; + + foreach ($productCatnumsByMainVariantCatnum as $mainVariantCatnum => $variantCatnums) { + $catnums[] = $mainVariantCatnum; + $catnums = array_merge($catnums, $variantCatnums); + } + + return array_unique($catnums); + } } diff --git a/symfony.lock b/symfony.lock index bd34a2ac50..a640caaa8d 100644 --- a/symfony.lock +++ b/symfony.lock @@ -533,6 +533,9 @@ "psr/container": { "version": "1.0.0" }, + "psr/event-dispatcher": { + "version": "1.0.0" + }, "psr/http-message": { "version": "1.0.1" }, @@ -903,6 +906,9 @@ "symfony/polyfill-intl-idn": { "version": "v1.12.0" }, + "symfony/polyfill-intl-normalizer": { + "version": "v1.18.0" + }, "symfony/polyfill-mbstring": { "version": "v1.12.0" }, @@ -918,6 +924,9 @@ "symfony/polyfill-php73": { "version": "v1.12.0" }, + "symfony/polyfill-php80": { + "version": "v1.18.0" + }, "symfony/polyfill-util": { "version": "v1.12.0" }, @@ -1089,12 +1098,21 @@ "symplify/coding-standard": { "version": "v5.4.16" }, + "symplify/console-color-diff": { + "version": "v7.3.18" + }, "symplify/easy-coding-standard": { "version": "v5.4.13" }, "symplify/package-builder": { "version": "v5.4.16" }, + "symplify/parameter-name-guard": { + "version": "v7.3.18" + }, + "symplify/phpstan-extensions": { + "version": "v7.3.18" + }, "symplify/set-config-resolver": { "version": "v7.2.3" }, diff --git a/templates/Front/Content/Login/windowForm.html.twig b/templates/Front/Content/Login/windowForm.html.twig index e96e62c853..327a6defb5 100644 --- a/templates/Front/Content/Login/windowForm.html.twig +++ b/templates/Front/Content/Login/windowForm.html.twig @@ -1,4 +1,3 @@ -{{ init_js_validation(null, false) }} {{ form_start(form, {attr: {class: 'js-front-login-window'}}) }} {{ form_errors(form) }} @@ -34,3 +33,4 @@ {{ form_end(form) }} +{{ init_js_validation(form, false) }} diff --git a/templates/Front/Content/Product/detail.html.twig b/templates/Front/Content/Product/detail.html.twig index f05c3c0fe1..2cedcf5204 100644 --- a/templates/Front/Content/Product/detail.html.twig +++ b/templates/Front/Content/Product/detail.html.twig @@ -156,6 +156,7 @@ {{ 'Name'|trans }} + {{ 'Availability'|trans }} {{ 'Price including VAT'|trans }} {% if getProductSellingPrice(product) is not null %} @@ -194,6 +195,11 @@ {{ variant.name }} {% endif %} + + {% if variant.calculatedAvailability %} + {{ variant.calculatedAvailability.name }} + {% endif %} + {{ getProductSellingPrice(variant).priceWithVat|price }} diff --git a/templates/bundles/FMElfinderBundle/Elfinder/ckeditor.html.twig b/templates/bundles/FMElfinderBundle/Elfinder/ckeditor.html.twig new file mode 100644 index 0000000000..efff309779 --- /dev/null +++ b/templates/bundles/FMElfinderBundle/Elfinder/ckeditor.html.twig @@ -0,0 +1,90 @@ + + + + + + + + + + + + +
+ + + diff --git a/tests/App/Acceptance/acceptance/CartCest.php b/tests/App/Acceptance/acceptance/CartCest.php index 7f27b18c2f..da9ba6d0e6 100644 --- a/tests/App/Acceptance/acceptance/CartCest.php +++ b/tests/App/Acceptance/acceptance/CartCest.php @@ -63,10 +63,10 @@ public function testAddToCartFromProductListPage( // tv-audio $me->amOnLocalizedRoute('front_product_list', ['id' => 3]); $productListPage->addProductToCartByName('Defender 2.0 SPK-480', 1); - $me->seeTranslationFrontend('Product %name% (%quantity% %unitName%) added to the cart', 'messages', [ - '%name%' => t('Defender 2.0 SPK-480', [], 'dataFixtures', $me->getFrontendLocale()), - '%quantity%' => 1, - '%unitName%' => $me->getDefaultUnitName(), + $me->seeTranslationFrontend('Product {{ name }} ({{ quantity|formatNumber }} {{ unitName }}) added to the cart', 'messages', [ + '{{ name }}' => t('Defender 2.0 SPK-480', [], 'dataFixtures', $me->getFrontendLocale()), + '{{ quantity|formatNumber }}' => 1, + '{{ unitName }}' => $me->getDefaultUnitName(), ]); $floatingWindowPage->closeFloatingWindow(); $cartBoxPage->seeCountAndPriceRoundedByCurrencyInCartBox(1, '119'); @@ -91,10 +91,10 @@ public function testAddToCartFromHomepage( $me->wantTo('add product to cart from homepage'); $me->amOnPage('/'); $homepagePage->addTopProductToCartByName('22" Sencor SLE 22F46DM4 HELLO KITTY', 1); - $me->seeTranslationFrontend('Product %name% (%quantity% %unitName%) added to the cart', 'messages', [ - '%name%' => t('22" Sencor SLE 22F46DM4 HELLO KITTY', [], 'dataFixtures', $me->getFrontendLocale()), - '%quantity%' => 1, - '%unitName%' => $me->getDefaultUnitName(), + $me->seeTranslationFrontend('Product {{ name }} ({{ quantity|formatNumber }} {{ unitName }}) added to the cart', 'messages', [ + '{{ name }}' => t('22" Sencor SLE 22F46DM4 HELLO KITTY', [], 'dataFixtures', $me->getFrontendLocale()), + '{{ quantity|formatNumber }}' => 1, + '{{ unitName }}' => $me->getDefaultUnitName(), ]); $floatingWindowPage->closeFloatingWindow(); $cartBoxPage->seeCountAndPriceRoundedByCurrencyInCartBox(1, '3499'); @@ -119,10 +119,10 @@ public function testAddToCartFromProductDetail( $me->amOnLocalizedRoute('front_product_detail', ['id' => 1]); $me->seeTranslationFrontend('Add to cart'); $productDetailPage->addProductIntoCart(3); - $me->seeTranslationFrontend('Product %name% (%quantity% %unitName%) added to the cart', 'messages', [ - '%name%' => t('22" Sencor SLE 22F46DM4 HELLO KITTY', [], 'dataFixtures', $me->getFrontendLocale()), - '%quantity%' => 3, - '%unitName%' => $me->getDefaultUnitName(), + $me->seeTranslationFrontend('Product {{ name }} ({{ quantity|formatNumber }} {{ unitName }}) added to the cart', 'messages', [ + '{{ name }}' => t('22" Sencor SLE 22F46DM4 HELLO KITTY', [], 'dataFixtures', $me->getFrontendLocale()), + '{{ quantity|formatNumber }}' => 3, + '{{ unitName }}' => $me->getDefaultUnitName(), ]); $floatingWindowPage->closeFloatingWindow(); $cartBoxPage->seeCountAndPriceRoundedByCurrencyInCartBox(1, '10497'); @@ -184,8 +184,8 @@ public function testRemovingItemsFromCart( $cartPage->assertProductIsNotInCartByName('JURA Impressa J9 TFT Carbon'); $cartPage->removeProductFromCart('PANASONIC DMC FT5EP'); - $me->seeTranslationFrontend('Your cart is unfortunately empty. To create order, you have to choose some product first', 'messages', [ - '{{ url }}' => '', + $me->seeTranslationFrontend('Your cart is unfortunately empty. To create order, you have to choose some product first', 'messages', [ + '%url%' => '', ]); } diff --git a/tests/App/Smoke/Http/RouteConfigCustomization.php b/tests/App/Smoke/Http/RouteConfigCustomization.php index 882d9fb8cf..6dce0062f6 100644 --- a/tests/App/Smoke/Http/RouteConfigCustomization.php +++ b/tests/App/Smoke/Http/RouteConfigCustomization.php @@ -260,6 +260,20 @@ private function configureAdminRoutes(RouteConfigCustomizer $routeConfigCustomiz $config->changeDefaultRequestDataSet($debugNote) ->setParameter('id', $vat->getId()) ->setParameter('newId', $newVat->getId()); + }) + ->customizeByRouteName('admin_currency_list', function (RouteConfig $config) { + $config->changeDefaultRequestDataSet('Currency setting is available only to superadmin.') + ->setExpectedStatusCode(302); + $config->addExtraRequestDataSet('Should be OK when logged in as "superadmin".') + ->setAuth(new BasicHttpAuth('superadmin', 'admin123')) + ->setExpectedStatusCode(200); + }) + ->customizeByRouteName('admin_currency_deleteconfirm', function (RouteConfig $config) { + $config->changeDefaultRequestDataSet('Currency setting is available only to superadmin.') + ->setExpectedStatusCode(302); + $config->addExtraRequestDataSet('Should be OK when logged in as "superadmin".') + ->setAuth(new BasicHttpAuth('superadmin', 'admin123')) + ->setExpectedStatusCode(200); }); } diff --git a/webpack.config.js b/webpack.config.js index ec1ed06807..de3fb1c167 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -60,7 +60,7 @@ Encore sources.getFrameworkVendorDir() + '/src/Resources/translations/*.po', './translations/*.po', ]; - const outputDirForExportedTranslations = Encore.isProduction() ? './web/build/' : './assets/js/'; + const outputDirForExportedTranslations = './assets/js/'; try { processTrans(dirWithJsFiles, dirWithTranslations, outputDirForExportedTranslations);