vendor/shopware/administration/Controller/AdministrationController.php line 121

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Administration\Controller;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Administration\Events\PreResetExcludedSearchTermEvent;
  5. use Shopware\Administration\Framework\Routing\KnownIps\KnownIpsCollectorInterface;
  6. use Shopware\Administration\Snippet\SnippetFinderInterface;
  7. use Shopware\Core\Defaults;
  8. use Shopware\Core\DevOps\Environment\EnvironmentHelper;
  9. use Shopware\Core\Framework\Adapter\Twig\TemplateFinder;
  10. use Shopware\Core\Framework\Context;
  11. use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\AllowHtml;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
  19. use Shopware\Core\Framework\Feature;
  20. use Shopware\Core\Framework\Log\Package;
  21. use Shopware\Core\Framework\Routing\Annotation\Since;
  22. use Shopware\Core\Framework\Routing\Exception\InvalidRequestParameterException;
  23. use Shopware\Core\Framework\Routing\Exception\LanguageNotFoundException;
  24. use Shopware\Core\Framework\Store\Services\FirstRunWizardClient;
  25. use Shopware\Core\Framework\Util\HtmlSanitizer;
  26. use Shopware\Core\Framework\Uuid\Uuid;
  27. use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
  28. use Shopware\Core\PlatformRequest;
  29. use Shopware\Core\System\Currency\CurrencyEntity;
  30. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  31. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  32. use Symfony\Component\HttpFoundation\JsonResponse;
  33. use Symfony\Component\HttpFoundation\Request;
  34. use Symfony\Component\HttpFoundation\Response;
  35. use Symfony\Component\Routing\Annotation\Route;
  36. use Symfony\Component\Validator\ConstraintViolation;
  37. use Symfony\Component\Validator\ConstraintViolationList;
  38. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  39. use function version_compare;
  40. /**
  41.  * @Route(defaults={"_routeScope"={"administration"}})
  42.  */
  43. #[Package('administration')]
  44. class AdministrationController extends AbstractController
  45. {
  46.     private TemplateFinder $finder;
  47.     private FirstRunWizardClient $firstRunWizardClient;
  48.     private SnippetFinderInterface $snippetFinder;
  49.     /**
  50.      * @var list<int>
  51.      */
  52.     private array $supportedApiVersions;
  53.     private KnownIpsCollectorInterface $knownIpsCollector;
  54.     private Connection $connection;
  55.     private EventDispatcherInterface $eventDispatcher;
  56.     private string $shopwareCoreDir;
  57.     private EntityRepositoryInterface $customerRepo;
  58.     private EntityRepositoryInterface $currencyRepository;
  59.     private HtmlSanitizer $htmlSanitizer;
  60.     private DefinitionInstanceRegistry $definitionInstanceRegistry;
  61.     private bool $esAdministrationEnabled;
  62.     /**
  63.      * @internal
  64.      *
  65.      * @param list<int> $supportedApiVersions
  66.      */
  67.     public function __construct(
  68.         TemplateFinder $finder,
  69.         FirstRunWizardClient $firstRunWizardClient,
  70.         SnippetFinderInterface $snippetFinder,
  71.         array $supportedApiVersions,
  72.         KnownIpsCollectorInterface $knownIpsCollector,
  73.         Connection $connection,
  74.         EventDispatcherInterface $eventDispatcher,
  75.         string $shopwareCoreDir,
  76.         EntityRepositoryInterface $customerRepo,
  77.         EntityRepositoryInterface $currencyRepository,
  78.         HtmlSanitizer $htmlSanitizer,
  79.         DefinitionInstanceRegistry $definitionInstanceRegistry,
  80.         ParameterBagInterface $params
  81.     ) {
  82.         $this->finder $finder;
  83.         $this->firstRunWizardClient $firstRunWizardClient;
  84.         $this->snippetFinder $snippetFinder;
  85.         $this->supportedApiVersions $supportedApiVersions;
  86.         $this->knownIpsCollector $knownIpsCollector;
  87.         $this->connection $connection;
  88.         $this->eventDispatcher $eventDispatcher;
  89.         $this->shopwareCoreDir $shopwareCoreDir;
  90.         $this->customerRepo $customerRepo;
  91.         $this->currencyRepository $currencyRepository;
  92.         $this->htmlSanitizer $htmlSanitizer;
  93.         $this->definitionInstanceRegistry $definitionInstanceRegistry;
  94.         // param is only available if the elasticsearch bundle is enabled
  95.         $this->esAdministrationEnabled $params->has('elasticsearch.administration.enabled')
  96.             ? $params->get('elasticsearch.administration.enabled')
  97.             : false;
  98.     }
  99.     /**
  100.      * @Since("6.3.3.0")
  101.      * @Route("/%shopware_administration.path_name%", defaults={"auth_required"=false}, name="administration.index", methods={"GET"})
  102.      */
  103.     public function index(Request $requestContext $context): Response
  104.     {
  105.         $template $this->finder->find('@Administration/administration/index.html.twig');
  106.         /** @var CurrencyEntity $defaultCurrency */
  107.         $defaultCurrency $this->currencyRepository->search(new Criteria([Defaults::CURRENCY]), $context)->first();
  108.         return $this->render($template, [
  109.             'features' => Feature::getAll(),
  110.             'systemLanguageId' => Defaults::LANGUAGE_SYSTEM,
  111.             'defaultLanguageIds' => [Defaults::LANGUAGE_SYSTEM],
  112.             'systemCurrencyId' => Defaults::CURRENCY,
  113.             'disableExtensions' => EnvironmentHelper::getVariable('DISABLE_EXTENSIONS'false),
  114.             'systemCurrencyISOCode' => $defaultCurrency->getIsoCode(),
  115.             'liveVersionId' => Defaults::LIVE_VERSION,
  116.             'firstRunWizard' => $this->firstRunWizardClient->frwShouldRun(),
  117.             'apiVersion' => $this->getLatestApiVersion(),
  118.             'cspNonce' => $request->attributes->get(PlatformRequest::ATTRIBUTE_CSP_NONCE),
  119.             'adminEsEnable' => $this->esAdministrationEnabled,
  120.         ]);
  121.     }
  122.     /**
  123.      * @Since("6.1.0.0")
  124.      * @Route("/api/_admin/snippets", name="api.admin.snippets", methods={"GET"})
  125.      */
  126.     public function snippets(Request $request): Response
  127.     {
  128.         $locale $request->query->get('locale''en-GB');
  129.         $snippets[$locale] = $this->snippetFinder->findSnippets((string) $locale);
  130.         if ($locale !== 'en-GB') {
  131.             $snippets['en-GB'] = $this->snippetFinder->findSnippets('en-GB');
  132.         }
  133.         return new JsonResponse($snippets);
  134.     }
  135.     /**
  136.      * @Since("6.3.1.0")
  137.      * @Route("/api/_admin/known-ips", name="api.admin.known-ips", methods={"GET"})
  138.      */
  139.     public function knownIps(Request $request): Response
  140.     {
  141.         $ips = [];
  142.         foreach ($this->knownIpsCollector->collectIps($request) as $ip => $name) {
  143.             $ips[] = [
  144.                 'name' => $name,
  145.                 'value' => $ip,
  146.             ];
  147.         }
  148.         return new JsonResponse(['ips' => $ips]);
  149.     }
  150.     /**
  151.      * @deprecated tag:v6.5.0 - reason:return-type-change - native return type JsonResponse will be added
  152.      *
  153.      * @Since("6.4.0.1")
  154.      * @Route("/api/_admin/reset-excluded-search-term", name="api.admin.reset-excluded-search-term", methods={"POST"}, defaults={"_acl"={"system_config:update", "system_config:create", "system_config:delete"}})
  155.      *
  156.      * @return JsonResponse
  157.      */
  158.     public function resetExcludedSearchTerm(Context $context)
  159.     {
  160.         $searchConfigId $this->connection->fetchOne('SELECT id FROM product_search_config WHERE language_id = :language_id', ['language_id' => Uuid::fromHexToBytes($context->getLanguageId())]);
  161.         if ($searchConfigId === false) {
  162.             throw new LanguageNotFoundException($context->getLanguageId());
  163.         }
  164.         $deLanguageId $this->fetchLanguageIdByName('de-DE'$this->connection);
  165.         $enLanguageId $this->fetchLanguageIdByName('en-GB'$this->connection);
  166.         switch ($context->getLanguageId()) {
  167.             case $deLanguageId:
  168.                 $defaultExcludedTerm = require $this->shopwareCoreDir '/Migration/Fixtures/stopwords/de.php';
  169.                 break;
  170.             case $enLanguageId:
  171.                 $defaultExcludedTerm = require $this->shopwareCoreDir '/Migration/Fixtures/stopwords/en.php';
  172.                 break;
  173.             default:
  174.                 /** @var PreResetExcludedSearchTermEvent $preResetExcludedSearchTermEvent */
  175.                 $preResetExcludedSearchTermEvent $this->eventDispatcher->dispatch(new PreResetExcludedSearchTermEvent($searchConfigId, [], $context));
  176.                 $defaultExcludedTerm $preResetExcludedSearchTermEvent->getExcludedTerms();
  177.         }
  178.         $this->connection->executeStatement(
  179.             'UPDATE `product_search_config` SET `excluded_terms` = :excludedTerms WHERE `id` = :id',
  180.             [
  181.                 'excludedTerms' => json_encode($defaultExcludedTerm),
  182.                 'id' => $searchConfigId,
  183.             ]
  184.         );
  185.         return new JsonResponse([
  186.             'success' => true,
  187.         ]);
  188.     }
  189.     /**
  190.      * @Since("6.4.0.1")
  191.      * @Route("/api/_admin/check-customer-email-valid", name="api.admin.check-customer-email-valid", methods={"POST"})
  192.      */
  193.     public function checkCustomerEmailValid(Request $requestContext $context): JsonResponse
  194.     {
  195.         if (!$request->request->has('email')) {
  196.             throw new \InvalidArgumentException('Parameter "email" is missing.');
  197.         }
  198.         $email = (string) $request->request->get('email');
  199.         $boundSalesChannelId $request->request->get('bound_sales_channel_id');
  200.         if ($boundSalesChannelId !== null && !\is_string($boundSalesChannelId)) {
  201.             throw new InvalidRequestParameterException('bound_sales_channel_id');
  202.         }
  203.         if ($this->isEmailValid((string) $request->request->get('id'), $email$context$boundSalesChannelId)) {
  204.             return new JsonResponse(
  205.                 ['isValid' => true]
  206.             );
  207.         }
  208.         $message 'The email address {{ email }} is already in use';
  209.         $params['{{ email }}'] = $email;
  210.         if ($boundSalesChannelId !== null) {
  211.             $message .= ' in the sales channel {{ salesChannelId }}';
  212.             $params['{{ salesChannelId }}'] = $boundSalesChannelId;
  213.         }
  214.         $violations = new ConstraintViolationList();
  215.         $violations->add(new ConstraintViolation(
  216.             str_replace(array_keys($params), array_values($params), $message),
  217.             $message,
  218.             $params,
  219.             null,
  220.             null,
  221.             $email,
  222.             null,
  223.             '79d30fe0-febf-421e-ac9b-1bfd5c9007f7'
  224.         ));
  225.         throw new ConstraintViolationException($violations$request->request->all());
  226.     }
  227.     /**
  228.      * @Since("6.4.2.0")
  229.      * @Route("/api/_admin/sanitize-html", name="api.admin.sanitize-html", methods={"POST"})
  230.      */
  231.     public function sanitizeHtml(Request $requestContext $context): JsonResponse
  232.     {
  233.         if (!$request->request->has('html')) {
  234.             throw new \InvalidArgumentException('Parameter "html" is missing.');
  235.         }
  236.         $html = (string) $request->request->get('html');
  237.         $field = (string) $request->request->get('field');
  238.         if ($field === '') {
  239.             return new JsonResponse(
  240.                 ['preview' => $this->htmlSanitizer->sanitize($html)]
  241.             );
  242.         }
  243.         [$entityName$propertyName] = explode('.'$field);
  244.         $property $this->definitionInstanceRegistry->getByEntityName($entityName)->getField($propertyName);
  245.         if ($property === null) {
  246.             throw new \InvalidArgumentException('Invalid field property provided.');
  247.         }
  248.         $flag $property->getFlag(AllowHtml::class);
  249.         if ($flag === null) {
  250.             return new JsonResponse(
  251.                 ['preview' => strip_tags($html)]
  252.             );
  253.         }
  254.         if ($flag instanceof AllowHtml && !$flag->isSanitized()) {
  255.             return new JsonResponse(
  256.                 ['preview' => $html]
  257.             );
  258.         }
  259.         return new JsonResponse(
  260.             ['preview' => $this->htmlSanitizer->sanitize($html, [], false$field)]
  261.         );
  262.     }
  263.     private function fetchLanguageIdByName(string $isoCodeConnection $connection): ?string
  264.     {
  265.         $languageId $connection->fetchOne(
  266.             '
  267.             SELECT `language`.id FROM `language`
  268.             INNER JOIN locale ON language.translation_code_id = locale.id
  269.             WHERE `code` = :code',
  270.             ['code' => $isoCode]
  271.         );
  272.         return $languageId === false null Uuid::fromBytesToHex($languageId);
  273.     }
  274.     private function getLatestApiVersion(): ?int
  275.     {
  276.         $sortedSupportedApiVersions array_values($this->supportedApiVersions);
  277.         usort($sortedSupportedApiVersions, function (int $version1int $version2) {
  278.             return version_compare((string) $version1, (string) $version2);
  279.         });
  280.         return array_pop($sortedSupportedApiVersions);
  281.     }
  282.     /**
  283.      * @throws InconsistentCriteriaIdsException
  284.      */
  285.     private function isEmailValid(string $customerIdstring $emailContext $context, ?string $boundSalesChannelId): bool
  286.     {
  287.         $criteria = new Criteria();
  288.         $criteria->addFilter(new EqualsFilter('email'$email));
  289.         $criteria->addFilter(new EqualsFilter('guest'false));
  290.         $criteria->addFilter(new NotFilter(
  291.             NotFilter::CONNECTION_AND,
  292.             [new EqualsFilter('id'$customerId)]
  293.         ));
  294.         $criteria->addFilter(new MultiFilter(MultiFilter::CONNECTION_OR, [
  295.             new EqualsFilter('boundSalesChannelId'null),
  296.             new EqualsFilter('boundSalesChannelId'$boundSalesChannelId),
  297.         ]));
  298.         return $this->customerRepo->searchIds($criteria$context)->getTotal() === 0;
  299.     }
  300. }