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

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