vendor/shopware/core/System/SystemConfig/SystemConfigService.php line 350

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\System\SystemConfig;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Framework\Bundle;
  5. use Shopware\Core\Framework\Context;
  6. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  7. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Field\ConfigJsonField;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  12. use Shopware\Core\Framework\Log\Package;
  13. use Shopware\Core\Framework\Util\XmlReader;
  14. use Shopware\Core\Framework\Uuid\Exception\InvalidUuidException;
  15. use Shopware\Core\Framework\Uuid\Uuid;
  16. use Shopware\Core\System\SystemConfig\Event\BeforeSystemConfigChangedEvent;
  17. use Shopware\Core\System\SystemConfig\Event\SystemConfigChangedEvent;
  18. use Shopware\Core\System\SystemConfig\Event\SystemConfigDomainLoadedEvent;
  19. use Shopware\Core\System\SystemConfig\Exception\BundleConfigNotFoundException;
  20. use Shopware\Core\System\SystemConfig\Exception\InvalidDomainException;
  21. use Shopware\Core\System\SystemConfig\Exception\InvalidKeyException;
  22. use Shopware\Core\System\SystemConfig\Exception\InvalidSettingValueException;
  23. use Shopware\Core\System\SystemConfig\Util\ConfigReader;
  24. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  25. use function json_decode;
  26. #[Package('system-settings')]
  27. class SystemConfigService
  28. {
  29.     private Connection $connection;
  30.     private EntityRepositoryInterface $systemConfigRepository;
  31.     private ConfigReader $configReader;
  32.     /**
  33.      * @var array<string, bool>
  34.      */
  35.     private array $keys = ['all' => true];
  36.     /**
  37.      * @var array<mixed>
  38.      */
  39.     private array $traces = [];
  40.     private AbstractSystemConfigLoader $loader;
  41.     private EventDispatcherInterface $eventDispatcher;
  42.     /**
  43.      * @internal
  44.      */
  45.     public function __construct(
  46.         Connection $connection,
  47.         EntityRepository $systemConfigRepository,
  48.         ConfigReader $configReader,
  49.         AbstractSystemConfigLoader $loader,
  50.         EventDispatcherInterface $eventDispatcher
  51.     ) {
  52.         $this->connection $connection;
  53.         $this->systemConfigRepository $systemConfigRepository;
  54.         $this->configReader $configReader;
  55.         $this->loader $loader;
  56.         $this->eventDispatcher $eventDispatcher;
  57.     }
  58.     public static function buildName(string $key): string
  59.     {
  60.         return 'config.' $key;
  61.     }
  62.     /**
  63.      * @return array<mixed>|bool|float|int|string|null
  64.      */
  65.     public function get(string $key, ?string $salesChannelId null)
  66.     {
  67.         foreach (array_keys($this->keys) as $trace) {
  68.             $this->traces[$trace][self::buildName($key)] = true;
  69.         }
  70.         $config $this->loader->load($salesChannelId);
  71.         $parts explode('.'$key);
  72.         $pointer $config;
  73.         foreach ($parts as $part) {
  74.             if (!\is_array($pointer)) {
  75.                 return null;
  76.             }
  77.             if (\array_key_exists($part$pointer)) {
  78.                 $pointer $pointer[$part];
  79.                 continue;
  80.             }
  81.             return null;
  82.         }
  83.         return $pointer;
  84.     }
  85.     public function getString(string $key, ?string $salesChannelId null): string
  86.     {
  87.         $value $this->get($key$salesChannelId);
  88.         if (!\is_array($value)) {
  89.             return (string) $value;
  90.         }
  91.         throw new InvalidSettingValueException($key'string', \gettype($value));
  92.     }
  93.     public function getInt(string $key, ?string $salesChannelId null): int
  94.     {
  95.         $value $this->get($key$salesChannelId);
  96.         if (!\is_array($value)) {
  97.             return (int) $value;
  98.         }
  99.         throw new InvalidSettingValueException($key'int', \gettype($value));
  100.     }
  101.     public function getFloat(string $key, ?string $salesChannelId null): float
  102.     {
  103.         $value $this->get($key$salesChannelId);
  104.         if (!\is_array($value)) {
  105.             return (float) $value;
  106.         }
  107.         throw new InvalidSettingValueException($key'float', \gettype($value));
  108.     }
  109.     public function getBool(string $key, ?string $salesChannelId null): bool
  110.     {
  111.         return (bool) $this->get($key$salesChannelId);
  112.     }
  113.     /**
  114.      * @internal should not be used in storefront or store api. The cache layer caches all accessed config keys and use them as cache tag.
  115.      *
  116.      * gets all available shop configs and returns them as an array
  117.      *
  118.      * @return array<mixed>
  119.      */
  120.     public function all(?string $salesChannelId null): array
  121.     {
  122.         return $this->loader->load($salesChannelId);
  123.     }
  124.     /**
  125.      * @internal should not be used in storefront or store api. The cache layer caches all accessed config keys and use them as cache tag.
  126.      *
  127.      * @throws InvalidDomainException
  128.      *
  129.      * @return array<mixed>
  130.      */
  131.     public function getDomain(string $domain, ?string $salesChannelId nullbool $inherit false): array
  132.     {
  133.         $domain trim($domain);
  134.         if ($domain === '') {
  135.             throw new InvalidDomainException('Empty domain');
  136.         }
  137.         $queryBuilder $this->connection->createQueryBuilder()
  138.             ->select(['configuration_key''configuration_value'])
  139.             ->from('system_config');
  140.         if ($inherit) {
  141.             $queryBuilder->where('sales_channel_id IS NULL OR sales_channel_id = :salesChannelId');
  142.         } elseif ($salesChannelId === null) {
  143.             $queryBuilder->where('sales_channel_id IS NULL');
  144.         } else {
  145.             $queryBuilder->where('sales_channel_id = :salesChannelId');
  146.         }
  147.         $domain rtrim($domain'.') . '.';
  148.         $escapedDomain str_replace('%''\\%'$domain);
  149.         $salesChannelId $salesChannelId Uuid::fromHexToBytes($salesChannelId) : null;
  150.         $queryBuilder->andWhere('configuration_key LIKE :prefix')
  151.             ->addOrderBy('sales_channel_id''ASC')
  152.             ->setParameter('prefix'$escapedDomain '%')
  153.             ->setParameter('salesChannelId'$salesChannelId);
  154.         $configs $queryBuilder->executeQuery()->fetchAllNumeric();
  155.         if ($configs === []) {
  156.             return [];
  157.         }
  158.         $merged = [];
  159.         foreach ($configs as [$key$value]) {
  160.             if ($value !== null) {
  161.                 $value json_decode($valuetrue);
  162.                 if ($value === false || !isset($value[ConfigJsonField::STORAGE_KEY])) {
  163.                     $value null;
  164.                 } else {
  165.                     $value $value[ConfigJsonField::STORAGE_KEY];
  166.                 }
  167.             }
  168.             $inheritedValuePresent = \array_key_exists($key$merged);
  169.             $valueConsideredEmpty = !\is_bool($value) && empty($value);
  170.             if ($inheritedValuePresent && $valueConsideredEmpty) {
  171.                 continue;
  172.             }
  173.             $merged[$key] = $value;
  174.         }
  175.         $event = new SystemConfigDomainLoadedEvent($domain$merged$inherit$salesChannelId);
  176.         $this->eventDispatcher->dispatch($event);
  177.         return $event->getConfig();
  178.     }
  179.     /**
  180.      * @param array<mixed>|bool|float|int|string|null $value
  181.      */
  182.     public function set(string $key$value, ?string $salesChannelId null): void
  183.     {
  184.         $key trim($key);
  185.         $this->validate($key$salesChannelId);
  186.         $event = new BeforeSystemConfigChangedEvent($key$value$salesChannelId);
  187.         $this->eventDispatcher->dispatch($event);
  188.         $id $this->getId($key$salesChannelId);
  189.         if ($value === null) {
  190.             if ($id) {
  191.                 $this->systemConfigRepository->delete([['id' => $id]], Context::createDefaultContext());
  192.             }
  193.             $this->eventDispatcher->dispatch(new SystemConfigChangedEvent($key$value$salesChannelId));
  194.             return;
  195.         }
  196.         $data = [
  197.             'id' => $id ?? Uuid::randomHex(),
  198.             'configurationKey' => $key,
  199.             'configurationValue' => $event->getValue(),
  200.             'salesChannelId' => $salesChannelId,
  201.         ];
  202.         $this->systemConfigRepository->upsert([$data], Context::createDefaultContext());
  203.         $this->eventDispatcher->dispatch(new SystemConfigChangedEvent($key$event->getValue(), $salesChannelId));
  204.     }
  205.     public function delete(string $key, ?string $salesChannel null): void
  206.     {
  207.         $this->set($keynull$salesChannel);
  208.     }
  209.     /**
  210.      * Fetches default values from bundle configuration and saves it to database
  211.      */
  212.     public function savePluginConfiguration(Bundle $bundlebool $override false): void
  213.     {
  214.         try {
  215.             $config $this->configReader->getConfigFromBundle($bundle);
  216.         } catch (BundleConfigNotFoundException $e) {
  217.             return;
  218.         }
  219.         $prefix $bundle->getName() . '.config.';
  220.         $this->saveConfig($config$prefix$override);
  221.     }
  222.     /**
  223.      * @param array<mixed> $config
  224.      */
  225.     public function saveConfig(array $configstring $prefixbool $override): void
  226.     {
  227.         $relevantSettings $this->getDomain($prefix);
  228.         foreach ($config as $card) {
  229.             foreach ($card['elements'] as $element) {
  230.                 $key $prefix $element['name'];
  231.                 if (!isset($element['defaultValue'])) {
  232.                     continue;
  233.                 }
  234.                 $value XmlReader::phpize($element['defaultValue']);
  235.                 if ($override || !isset($relevantSettings[$key]) || $relevantSettings[$key] === null) {
  236.                     $this->set($key$value);
  237.                 }
  238.             }
  239.         }
  240.     }
  241.     public function deletePluginConfiguration(Bundle $bundle): void
  242.     {
  243.         try {
  244.             $config $this->configReader->getConfigFromBundle($bundle);
  245.         } catch (BundleConfigNotFoundException $e) {
  246.             return;
  247.         }
  248.         $this->deleteExtensionConfiguration($bundle->getName(), $config);
  249.     }
  250.     /**
  251.      * @param array<mixed> $config
  252.      */
  253.     public function deleteExtensionConfiguration(string $extensionName, array $config): void
  254.     {
  255.         $prefix $extensionName '.config.';
  256.         $configKeys = [];
  257.         foreach ($config as $card) {
  258.             foreach ($card['elements'] as $element) {
  259.                 $configKeys[] = $prefix $element['name'];
  260.             }
  261.         }
  262.         if (empty($configKeys)) {
  263.             return;
  264.         }
  265.         $criteria = new Criteria();
  266.         $criteria->addFilter(new EqualsAnyFilter('configurationKey'$configKeys));
  267.         $systemConfigIds $this->systemConfigRepository->searchIds($criteriaContext::createDefaultContext())->getIds();
  268.         if (empty($systemConfigIds)) {
  269.             return;
  270.         }
  271.         $ids array_map(static function ($id) {
  272.             return ['id' => $id];
  273.         }, $systemConfigIds);
  274.         $this->systemConfigRepository->delete($idsContext::createDefaultContext());
  275.     }
  276.     /**
  277.      * @return mixed|null All kind of data could be cached
  278.      */
  279.     public function trace(string $key, \Closure $param)
  280.     {
  281.         $this->traces[$key] = [];
  282.         $this->keys[$key] = true;
  283.         $result $param();
  284.         unset($this->keys[$key]);
  285.         return $result;
  286.     }
  287.     /**
  288.      * @return array<mixed>
  289.      */
  290.     public function getTrace(string $key): array
  291.     {
  292.         $trace = isset($this->traces[$key]) ? array_keys($this->traces[$key]) : [];
  293.         unset($this->traces[$key]);
  294.         return $trace;
  295.     }
  296.     /**
  297.      * @throws InvalidKeyException
  298.      * @throws InvalidUuidException
  299.      */
  300.     private function validate(string $key, ?string $salesChannelId): void
  301.     {
  302.         $key trim($key);
  303.         if ($key === '') {
  304.             throw new InvalidKeyException('key may not be empty');
  305.         }
  306.         if ($salesChannelId && !Uuid::isValid($salesChannelId)) {
  307.             throw new InvalidUuidException($salesChannelId);
  308.         }
  309.     }
  310.     private function getId(string $key, ?string $salesChannelId null): ?string
  311.     {
  312.         $criteria = new Criteria();
  313.         $criteria->addFilter(
  314.             new EqualsFilter('configurationKey'$key),
  315.             new EqualsFilter('salesChannelId'$salesChannelId)
  316.         );
  317.         /** @var array<string> $ids */
  318.         $ids $this->systemConfigRepository->searchIds($criteriaContext::createDefaultContext())->getIds();
  319.         /** @var string|null $id */
  320.         $id array_shift($ids);
  321.         return $id;
  322.     }
  323. }