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);