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

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