diff --git a/src/Component/Router/FriendlyUrl/FriendlyUrlMatcher.php b/src/Component/Router/FriendlyUrl/FriendlyUrlMatcher.php index 82405bf74e..2344854451 100644 --- a/src/Component/Router/FriendlyUrl/FriendlyUrlMatcher.php +++ b/src/Component/Router/FriendlyUrl/FriendlyUrlMatcher.php @@ -13,6 +13,8 @@ class FriendlyUrlMatcher { + protected const string IGNORED_INTERNAL_ROUTE = '_fragment'; + /** * @param \Shopsys\FrameworkBundle\Component\Router\FriendlyUrl\FriendlyUrlRepository $friendlyUrlRepository * @param \Shopsys\FrameworkBundle\Model\CategorySeo\ReadyCategorySeoMixRepository $readyCategorySeoMixRepository @@ -32,6 +34,11 @@ public function __construct( public function match(string $pathinfo, RouteCollection $routeCollection, DomainConfig $domainConfig): array { $pathWithoutSlash = substr($pathinfo, 1); + + if ($pathWithoutSlash === self::IGNORED_INTERNAL_ROUTE) { + throw new ResourceNotFoundException(); + } + $friendlyUrl = $this->friendlyUrlRepository->findByDomainIdAndSlug($domainConfig->getId(), $pathWithoutSlash); if ($friendlyUrl === null) { diff --git a/src/Controller/Admin/DefaultController.php b/src/Controller/Admin/DefaultController.php index 29d477b154..4eb34546ee 100644 --- a/src/Controller/Admin/DefaultController.php +++ b/src/Controller/Admin/DefaultController.php @@ -7,18 +7,13 @@ use Shopsys\FrameworkBundle\Component\Cron\Config\CronConfig; use Shopsys\FrameworkBundle\Component\Cron\CronFacade; use Shopsys\FrameworkBundle\Component\Cron\CronModuleFacade; -use Shopsys\FrameworkBundle\Component\Domain\Domain; use Shopsys\FrameworkBundle\Component\Grid\ArrayDataSource; use Shopsys\FrameworkBundle\Component\Grid\GridFactory; use Shopsys\FrameworkBundle\Component\Grid\GridView; use Shopsys\FrameworkBundle\Component\Grid\QueryBuilderDataSource; -use Shopsys\FrameworkBundle\Component\Setting\Setting; use Shopsys\FrameworkBundle\Form\Admin\QuickSearch\QuickSearchFormData; use Shopsys\FrameworkBundle\Form\Admin\QuickSearch\QuickSearchFormType; use Shopsys\FrameworkBundle\Model\AdminNavigation\BreadcrumbOverrider; -use Shopsys\FrameworkBundle\Model\Mail\MailTemplateFacade; -use Shopsys\FrameworkBundle\Model\Product\Parameter\ParameterFacade; -use Shopsys\FrameworkBundle\Model\Product\Unit\UnitFacade; use Shopsys\FrameworkBundle\Model\Security\Roles; use Shopsys\FrameworkBundle\Model\Statistics\StatisticsFacade; use Shopsys\FrameworkBundle\Model\Statistics\StatisticsProcessingFacade; @@ -34,9 +29,6 @@ class DefaultController extends AdminBaseController /** * @param \Shopsys\FrameworkBundle\Model\Statistics\StatisticsFacade $statisticsFacade * @param \Shopsys\FrameworkBundle\Model\Statistics\StatisticsProcessingFacade $statisticsProcessingFacade - * @param \Shopsys\FrameworkBundle\Model\Mail\MailTemplateFacade $mailTemplateFacade - * @param \Shopsys\FrameworkBundle\Model\Product\Unit\UnitFacade $unitFacade - * @param \Shopsys\FrameworkBundle\Component\Setting\Setting $setting * @param \Shopsys\FrameworkBundle\Component\Cron\CronModuleFacade $cronModuleFacade * @param \Shopsys\FrameworkBundle\Component\Grid\GridFactory $gridFactory * @param \Shopsys\FrameworkBundle\Component\Cron\Config\CronConfig $cronConfig @@ -44,15 +36,10 @@ class DefaultController extends AdminBaseController * @param \Shopsys\FrameworkBundle\Model\AdminNavigation\BreadcrumbOverrider $breadcrumbOverrider * @param \Shopsys\FrameworkBundle\Twig\DateTimeFormatterExtension $dateTimeFormatterExtension * @param \Shopsys\FrameworkBundle\Model\Transfer\Issue\TransferIssueFacade $transferIssueFacade - * @param \Shopsys\FrameworkBundle\Component\Domain\Domain $domain - * @param \Shopsys\FrameworkBundle\Model\Product\Parameter\ParameterFacade $parameterFacade */ public function __construct( protected readonly StatisticsFacade $statisticsFacade, protected readonly StatisticsProcessingFacade $statisticsProcessingFacade, - protected readonly MailTemplateFacade $mailTemplateFacade, - protected readonly UnitFacade $unitFacade, - protected readonly Setting $setting, protected readonly CronModuleFacade $cronModuleFacade, protected readonly GridFactory $gridFactory, protected readonly CronConfig $cronConfig, @@ -60,8 +47,6 @@ public function __construct( protected readonly BreadcrumbOverrider $breadcrumbOverrider, protected readonly DateTimeFormatterExtension $dateTimeFormatterExtension, protected readonly TransferIssueFacade $transferIssueFacade, - protected readonly Domain $domain, - protected readonly ParameterFacade $parameterFacade, ) { } @@ -116,8 +101,6 @@ public function dashboardAction(): Response $ordersValueTrend = $this->getTrendDifference($previousValueOfOrders, $currentValueOfOrders); - $this->addWarningMessagesOnDashboard(); - return $this->render( '@ShopsysFramework/Admin/Content/Default/index.html.twig', [ @@ -138,15 +121,6 @@ public function dashboardAction(): Response ); } - protected function addWarningMessagesOnDashboard(): void - { - $this->checkEnabledMailTemplatesHaveTheirBodyAndSubjectFilled(); - $this->checkAtLeastOneUnitExists(); - $this->checkDefaultUnitIsSet(); - $this->checkMandatoryArticlesExist(); - $this->checkAllSliderNumericValuesAreSet(); - } - /** * @param int $previous * @param int $current @@ -377,103 +351,4 @@ function ($data) { ], ); } - - protected function checkEnabledMailTemplatesHaveTheirBodyAndSubjectFilled(): void - { - if ($this->mailTemplateFacade->existsTemplateWithEnabledSendingHavingEmptyBodyOrSubject()) { - $this->addErrorFlashTwig( - t('Some required email templates are not fully set.'), - [ - 'url' => $this->generateUrl('admin_mail_template'), - ], - ); - } - } - - protected function checkAtLeastOneUnitExists(): void - { - if (count($this->unitFacade->getAll()) === 0) { - $this->addErrorFlashTwig( - t('There are no units, you need to create some.'), - [ - 'url' => $this->generateUrl('admin_unit_list'), - ], - ); - } - } - - protected function checkDefaultUnitIsSet(): void - { - if ($this->setting->get(Setting::DEFAULT_UNIT) === 0) { - $this->addErrorFlashTwig( - t('Default unit is not set.'), - [ - 'url' => $this->generateUrl('admin_unit_list'), - ], - ); - } - } - - protected function checkMandatoryArticlesExist(): void - { - foreach ($this->domain->getAdminEnabledDomainIds() as $domainId) { - $domainConfig = $this->domain->getDomainConfigById($domainId); - - if ($this->setting->getForDomain(Setting::TERMS_AND_CONDITIONS_ARTICLE_ID, $domainConfig->getId()) === null) { - $this->addErrorFlashTwig( - t('Terms and conditions article for domain {{ domainName }} is not set.'), - [ - 'url' => $this->generateUrl('admin_legalconditions_termsandconditions'), - 'domainName' => $domainConfig->getName(), - ], - ); - } - - if ($this->setting->getForDomain(Setting::PRIVACY_POLICY_ARTICLE_ID, $domainConfig->getId()) === null) { - $this->addErrorFlashTwig( - t('Privacy policy article for domain {{ domainName }} is not set.'), - [ - 'url' => $this->generateUrl('admin_legalconditions_privacypolicy'), - 'domainName' => $domainConfig->getName(), - ], - ); - } - - if ($this->setting->getForDomain(Setting::USER_CONSENT_POLICY_ARTICLE_ID, $domainConfig->getId()) === null) { - $this->addErrorFlashTwig( - t('User consent policy article for domain {{ domainName }} is not set.'), - [ - 'url' => $this->generateUrl('admin_userconsentpolicy_setting'), - 'domainName' => $domainConfig->getName(), - ], - ); - } - } - } - - protected function checkAllSliderNumericValuesAreSet(): void - { - $countOfSliderParametersWithoutNumericValueSet = $this->parameterFacade->getCountOfSliderParametersWithoutTheirsNumericValueFilled(); - - if ($countOfSliderParametersWithoutNumericValueSet <= 0) { - return; - } - - $this->addErrorFlashTwig( - t( - '{1} There is one parameter slider that does not have its numeric values filled in.|[2,Inf] There are %count% parameter sliders that does not have its numeric values filled in.', - [ - '%count%' => $countOfSliderParametersWithoutNumericValueSet, - ], - ), - ); - - $sliderParametersWithoutTheirsNumericValueFilled = $this->parameterFacade->getSliderParametersWithoutTheirsNumericValueFilled(); - - foreach ($sliderParametersWithoutTheirsNumericValueFilled as $parameter) { - $this->addErrorFlashTwig( - sprintf('%s', $this->generateUrl('admin_parametervalues_edit', ['id' => $parameter->getId()]), $parameter->getName()), - ); - } - } } diff --git a/src/Model/Country/CountryFacade.php b/src/Model/Country/CountryFacade.php index 9fb358bffa..b2a5921efb 100644 --- a/src/Model/Country/CountryFacade.php +++ b/src/Model/Country/CountryFacade.php @@ -108,4 +108,12 @@ public function findByCode(string $countryCode): ?Country { return $this->countryRepository->findByCode($countryCode); } + + /** + * @return int + */ + public function getCount(): int + { + return $this->countryRepository->getCount(); + } } diff --git a/src/Model/Country/CountryRepository.php b/src/Model/Country/CountryRepository.php index d74722f4e8..1a03e897c1 100644 --- a/src/Model/Country/CountryRepository.php +++ b/src/Model/Country/CountryRepository.php @@ -116,4 +116,15 @@ public function findByCode(string $countryCode): ?Country { return $this->getCountryRepository()->findOneBy(['code' => $countryCode]); } + + /** + * @return int + */ + public function getCount(): int + { + return $this->getCountryRepository()->createQueryBuilder('c') + ->select('COUNT(c.id)') + ->getQuery() + ->getSingleScalarResult(); + } } diff --git a/src/Model/Product/Unit/UnitFacade.php b/src/Model/Product/Unit/UnitFacade.php index 34476585c3..d43e20f8eb 100644 --- a/src/Model/Product/Unit/UnitFacade.php +++ b/src/Model/Product/Unit/UnitFacade.php @@ -168,4 +168,12 @@ protected function dispatchUnitEvent(Unit $unit, string $eventType): void { $this->eventDispatcher->dispatch(new UnitEvent($unit), $eventType); } + + /** + * @return int + */ + public function getCount(): int + { + return $this->unitRepository->getCount(); + } } diff --git a/src/Model/Product/Unit/UnitRepository.php b/src/Model/Product/Unit/UnitRepository.php index 702fabba0d..2262d86a0e 100644 --- a/src/Model/Product/Unit/UnitRepository.php +++ b/src/Model/Product/Unit/UnitRepository.php @@ -118,4 +118,15 @@ public function replaceUnit(Unit $oldUnit, Unit $newUnit) ->where('p.unit = :oldUnit')->setParameter('oldUnit', $oldUnit) ->getQuery()->execute(); } + + /** + * @return int + */ + public function getCount(): int + { + return $this->getUnitRepository()->createQueryBuilder('u') + ->select('COUNT(u.id)') + ->getQuery() + ->getSingleScalarResult(); + } } diff --git a/src/Model/Security/MenuItemsGrantedRolesSetting.php b/src/Model/Security/MenuItemsGrantedRolesSetting.php index 5f83ae37ac..66474f869c 100644 --- a/src/Model/Security/MenuItemsGrantedRolesSetting.php +++ b/src/Model/Security/MenuItemsGrantedRolesSetting.php @@ -52,6 +52,9 @@ protected function getPagesGrantedRoles(): array 'products' . static::MENU_ITEM_PATH_SEPARATOR . 'categories' => [ Roles::ROLE_CATEGORY_VIEW, ], + 'files' => [ + Roles::ROLE_FILES_VIEW, + ], 'pricing' . static::MENU_ITEM_PATH_SEPARATOR . 'pricing_groups' => [ Roles::ROLE_PRICING_GROUP_VIEW, ], diff --git a/src/Model/Stock/StockFacade.php b/src/Model/Stock/StockFacade.php index c00054c09b..509adb1a3b 100644 --- a/src/Model/Stock/StockFacade.php +++ b/src/Model/Stock/StockFacade.php @@ -139,4 +139,12 @@ public function getStocksEnabledOnDomainIndexedByStockId(int $domainId): array return $stocksById; } + + /** + * @return int + */ + public function getCount(): int + { + return $this->stockRepository->getCount(); + } } diff --git a/src/Model/Stock/StockRepository.php b/src/Model/Stock/StockRepository.php index cbb482b28f..daba1f1a21 100644 --- a/src/Model/Stock/StockRepository.php +++ b/src/Model/Stock/StockRepository.php @@ -120,4 +120,15 @@ public function changeDefaultStock(Stock $stock): void $stock->setDefault(); $this->em->flush(); } + + /** + * @return int + */ + public function getCount(): int + { + return $this->getQueryBuilder() + ->select('count(s.id)') + ->getQuery() + ->getSingleScalarResult(); + } } diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index 211ca46154..583807fa60 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -52,23 +52,29 @@ msgstr "301 (Trvalé přesměrování)" msgid "302 (Temporary redirect)" msgstr "302 (Dočasné přesměrování)" -msgid "Default unit is not set." -msgstr "Není nastavena výchozí jednotka." +msgid "Default unit is not set." +msgstr "Není nastavena výchozí jednotka." -msgid "Privacy policy article for domain {{ domainName }} is not set." -msgstr "Není nastaven článek zásady ochrany osobních údajů pro doménu {{ domainName }}." +msgid "Privacy policy article for domain %domainName% is not set." +msgstr "Není nastaven článek zásady ochrany osobních údajů pro doménu %domainName%." -msgid "Some required email templates are not fully set." -msgstr "Některé povinné e-mailové šablony nejsou vyplněny." +msgid "Some required email templates are not fully set." +msgstr "Některé povinné e-mailové šablony nejsou vyplněny." -msgid "Terms and conditions article for domain {{ domainName }} is not set." -msgstr "Není nastaven článek obchodní podmínky pro doménu {{ domainName }}." +msgid "Term and conditions article for domain %domainName% is not set." +msgstr "Není nastaven článek obchodní podmínky pro doménu %domainName%." -msgid "There are no units, you need to create some." -msgstr "Neexistují žádné jednotky, musíte nějakou vytvořit." +msgid "There are no countries, you need to create some." +msgstr "Neexistují žádné státy, musíte nějaký vytvořit." -msgid "User consent policy article for domain {{ domainName }} is not set." -msgstr "Není nastaven článek správa osobních údajů pro doménu {{ domainName }}." +msgid "There are no units, you need to create some." +msgstr "Neexistují žádné jednotky, musíte nějakou vytvořit." + +msgid "There are no warehouses, you need to create some." +msgstr "Neexistují žádné sklady, musíte nějaký vytvořit." + +msgid "User consent policy article for domain %domainName% is not set." +msgstr "Není nastaven článek správa osobních údajů pro doménu %domainName%." msgid "SEO category has been saved" msgstr "SEO kombinace kategorie byla uložena" @@ -1903,8 +1909,8 @@ msgstr "Vyplňte také URL pro zvolenou doménu" msgid "Fill URL only for selected domain" msgstr "Vyplňte pouze URL pro zvolenou doménu" -msgid "Fill missing settings to enable creating products. More information here." -msgstr "Pro vytvoření produktu doplňte chybějící nastavení. Více informací zde." +msgid "Fill missing settings to enable creating products." +msgstr "Pro vytvoření produktu doplňte chybějící nastavení." msgid "Filter by" msgstr "Filtrovat podle" diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index a0d97c4585..a2c16dc3b5 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -52,22 +52,28 @@ msgstr "" msgid "302 (Temporary redirect)" msgstr "" -msgid "Default unit is not set." +msgid "Default unit is not set." msgstr "" -msgid "Privacy policy article for domain {{ domainName }} is not set." +msgid "Privacy policy article for domain %domainName% is not set." msgstr "" -msgid "Some required email templates are not fully set." +msgid "Some required email templates are not fully set." msgstr "" -msgid "Terms and conditions article for domain {{ domainName }} is not set." +msgid "Term and conditions article for domain %domainName% is not set." msgstr "" -msgid "There are no units, you need to create some." +msgid "There are no countries, you need to create some." msgstr "" -msgid "User consent policy article for domain {{ domainName }} is not set." +msgid "There are no units, you need to create some." +msgstr "" + +msgid "There are no warehouses, you need to create some." +msgstr "" + +msgid "User consent policy article for domain %domainName% is not set." msgstr "" msgid "SEO category has been saved" @@ -1903,7 +1909,7 @@ msgstr "" msgid "Fill URL only for selected domain" msgstr "" -msgid "Fill missing settings to enable creating products. More information here." +msgid "Fill missing settings to enable creating products." msgstr "" msgid "Filter by" diff --git a/src/Resources/views/Admin/Content/Product/list.html.twig b/src/Resources/views/Admin/Content/Product/list.html.twig index 47572a36e9..73c2de9a20 100644 --- a/src/Resources/views/Admin/Content/Product/list.html.twig +++ b/src/Resources/views/Admin/Content/Product/list.html.twig @@ -18,9 +18,9 @@ +{{ 'Create variant'|trans }} - - {{ 'Fill missing settings to enable creating products. More information here.'|trans }} - + + {{ 'Fill missing settings to enable creating products.'|trans }} + {% endif %} {% endblock %} diff --git a/src/Resources/views/Admin/Layout/layoutWithPanel.html.twig b/src/Resources/views/Admin/Layout/layoutWithPanel.html.twig index 42b18572e5..6795e1becb 100644 --- a/src/Resources/views/Admin/Layout/layoutWithPanel.html.twig +++ b/src/Resources/views/Admin/Layout/layoutWithPanel.html.twig @@ -19,6 +19,8 @@ {{ render(controller('Shopsys\\FrameworkBundle\\Controller\\Admin\\FlashMessageController::indexAction')) }} + {{ render_required_settings() }} + {% if showHeading is not defined or showHeading %}

diff --git a/src/Resources/views/Components/RequiredSettings/requiredSettings.html.twig b/src/Resources/views/Components/RequiredSettings/requiredSettings.html.twig new file mode 100644 index 0000000000..b19163cc03 --- /dev/null +++ b/src/Resources/views/Components/RequiredSettings/requiredSettings.html.twig @@ -0,0 +1,11 @@ +{# @var requiredSettingsMessages string[] #} +
+
    + {% for message in requiredSettingsMessages %} +
  • + {{ icon('warning') }} + {{ message|raw }} +
  • + {% endfor %} +
+
diff --git a/src/Twig/RequiredSettingExtension.php b/src/Twig/RequiredSettingExtension.php new file mode 100644 index 0000000000..9e402d5022 --- /dev/null +++ b/src/Twig/RequiredSettingExtension.php @@ -0,0 +1,224 @@ +renderRequiredSettings(...), ['is_safe' => ['html']]), + ]; + } + + /** + * @return string|null + */ + public function renderRequiredSettings(): ?string + { + $this->loadRequiredSettingMessages(); + + if (count($this->requiredSettingsMessages) === 0) { + return null; + } + + return $this->twig->render( + '@ShopsysFramework/Components/RequiredSettings/requiredSettings.html.twig', + [ + 'requiredSettingsMessages' => $this->requiredSettingsMessages, + ], + ); + } + + protected function loadRequiredSettingMessages(): void + { + $this->requiredSettingsMessages = []; + + $this->checkEnabledMailTemplatesHaveTheirBodyAndSubjectFilled(); + $this->checkAtLeastOneUnitExists(); + $this->checkDefaultUnitIsSet(); + $this->checkAtLeastOneStockExists(); + $this->checkAtLeastOneCountryExists(); + $this->checkMandatoryArticlesExist(); + $this->checkAllSliderNumericValuesAreSet(); + } + + protected function checkEnabledMailTemplatesHaveTheirBodyAndSubjectFilled(): void + { + if ($this->mailTemplateFacade->existsTemplateWithEnabledSendingHavingEmptyBodyOrSubject()) { + $this->requiredSettingsMessages[] = t( + 'Some required email templates are not fully set.', + [ + '%url%' => $this->router->generate('admin_mail_template'), + ], + ); + } + } + + protected function checkAtLeastOneUnitExists(): void + { + if ($this->unitFacade->getCount() === 0) { + $this->requiredSettingsMessages[] = t( + 'There are no units, you need to create some.', + [ + '%url%' => $this->router->generate('admin_unit_list'), + ], + ); + } + } + + protected function checkDefaultUnitIsSet(): void + { + try { + $this->unitFacade->getDefaultUnit(); + } catch (UnitNotFoundException) { + $this->requiredSettingsMessages[] = t( + 'Default unit is not set.', + [ + '%url%' => $this->router->generate('admin_unit_list'), + ], + ); + } + } + + protected function checkAtLeastOneStockExists(): void + { + if ($this->stockFacade->getCount() === 0) { + $this->requiredSettingsMessages[] = t( + 'There are no warehouses, you need to create some.', + [ + '%url%' => $this->router->generate('admin_stock_list'), + ], + ); + } + } + + protected function checkAtLeastOneCountryExists(): void + { + if ($this->countryFacade->getCount() === 0) { + $this->requiredSettingsMessages[] = t( + 'There are no countries, you need to create some.', + [ + '%url%' => $this->router->generate('admin_country_list'), + ], + ); + } + } + + protected function checkMandatoryArticlesExist(): void + { + foreach ($this->domain->getAdminEnabledDomainIds() as $domainId) { + $domainConfig = $this->domain->getDomainConfigById($domainId); + + if ($this->setting->getForDomain(Setting::TERMS_AND_CONDITIONS_ARTICLE_ID, $domainConfig->getId()) === null) { + $this->requiredSettingsMessages[] = t( + 'Term and conditions article for domain %domainName% is not set.', + [ + '%url%' => $this->router->generate('admin_legalconditions_termsandconditions'), + '%domainName%' => $domainConfig->getName(), + ], + ); + } + + if ($this->setting->getForDomain(Setting::PRIVACY_POLICY_ARTICLE_ID, $domainConfig->getId()) === null) { + $this->requiredSettingsMessages[] = t( + 'Privacy policy article for domain %domainName% is not set.', + [ + '%url%' => $this->router->generate('admin_legalconditions_privacypolicy'), + '%domainName%' => $domainConfig->getName(), + ], + ); + } + + if ($this->setting->getForDomain(Setting::USER_CONSENT_POLICY_ARTICLE_ID, $domainConfig->getId()) === null) { + $this->requiredSettingsMessages[] = t( + 'User consent policy article for domain %domainName% is not set.', + [ + '%url%' => $this->router->generate('admin_userconsentpolicy_setting'), + '%domainName%' => $domainConfig->getName(), + ], + ); + } + } + } + + protected function checkAllSliderNumericValuesAreSet(): void + { + $countOfSliderParametersWithoutNumericValueSet = $this->parameterFacade->getCountOfSliderParametersWithoutTheirsNumericValueFilled(); + + if ($countOfSliderParametersWithoutNumericValueSet <= 0) { + return; + } + + $message = t( + '{1} There is one parameter slider that does not have its numeric values filled in.|[2,Inf] There are %count% parameter sliders that does not have its numeric values filled in.', + [ + '%count%' => $countOfSliderParametersWithoutNumericValueSet, + ], + ); + + $sliderParametersWithoutTheirsNumericValueFilled = $this->parameterFacade->getSliderParametersWithoutTheirsNumericValueFilled(); + + $message .= ''; + + $this->requiredSettingsMessages[] = $message; + } +}