vendor/pimcore/pimcore/lib/Config/LocationAwareConfigRepository.php line 438

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Config;
  15. use Pimcore\Config;
  16. use Pimcore\Db\PhpArrayFileTable;
  17. use Pimcore\File;
  18. use Pimcore\Helper\StopMessengerWorkersTrait;
  19. use Pimcore\Model\Tool\SettingsStore;
  20. use Symfony\Component\Yaml\Yaml;
  21. class LocationAwareConfigRepository
  22. {
  23.     use StopMessengerWorkersTrait;
  24.     /**
  25.      * @deprecated Will be removed in Pimcore 11
  26.      */
  27.     public const LOCATION_LEGACY 'legacy';
  28.     public const LOCATION_SYMFONY_CONFIG 'symfony-config';
  29.     public const LOCATION_SETTINGS_STORE 'settings-store';
  30.     public const LOCATION_DISABLED 'disabled';
  31.     /**
  32.      * @var array
  33.      */
  34.     protected array $containerConfig = [];
  35.     /**
  36.      * @var string|null
  37.      */
  38.     protected ?string $settingsStoreScope null;
  39.     /**
  40.      * @var string|null
  41.      *
  42.      * @deprecated Will be removed in Pimcore 11
  43.      */
  44.     protected ?string $storageDirectory null;
  45.     /**
  46.      * @var string|null
  47.      *
  48.      * @deprecated Will be removed in Pimcore 11
  49.      */
  50.     protected ?string $writeTargetEnvVariableName null;
  51.     /**
  52.      * @var string|null
  53.      *
  54.      * @deprecated Will be removed in Pimcore 11
  55.      */
  56.     protected ?string $defaultWriteLocation self::LOCATION_SYMFONY_CONFIG;
  57.     /**
  58.      * @deprecated Will be removed in Pimcore 11
  59.      */
  60.     protected mixed $loadLegacyConfigCallback;
  61.     /**
  62.      * @deprecated Will be removed in Pimcore 11
  63.      *
  64.      * @var string|null
  65.      */
  66.     protected ?string $legacyConfigFile null;
  67.     /**
  68.      * @deprecated Will be removed in Pimcore 11
  69.      */
  70.     private ?PhpArrayFileTable $legacyStore null;
  71.     protected ?array $storageConfig null;
  72.     /**
  73.      * @param array $containerConfig
  74.      * @param string|null $settingsStoreScope
  75.      * @param string|array|null $storageDirectory
  76.      * @param string|null $writeTargetEnvVariableName
  77.      * @param string|null $defaultWriteLocation
  78.      * @param string|null $legacyConfigFile
  79.      * @param mixed $loadLegacyConfigCallback
  80.      */
  81.     public function __construct(
  82.         array $containerConfig,
  83.         ?string $settingsStoreScope,
  84.         string|array|null $storageDirectory// will be renamed to `array $storageConfig` in Pimcore 11
  85.         ?string $writeTargetEnvVariableName// @TODO to be removed in Pimcore 11
  86.         ?string $defaultWriteLocation null// @TODO to be removed in Pimcore 11
  87.         ?string $legacyConfigFile null// @TODO to be removed in Pimcore 11
  88.         mixed $loadLegacyConfigCallback null // @TODO to be removed in Pimcore 11
  89.     ) {
  90.         $this->containerConfig $containerConfig;
  91.         $this->settingsStoreScope $settingsStoreScope;
  92.         $this->writeTargetEnvVariableName $writeTargetEnvVariableName;
  93.         $this->defaultWriteLocation $defaultWriteLocation ?: self::LOCATION_SYMFONY_CONFIG;
  94.         $this->legacyConfigFile $legacyConfigFile;
  95.         $this->loadLegacyConfigCallback $loadLegacyConfigCallback;
  96.         if (is_string($storageDirectory)) {
  97.             $this->storageDirectory rtrim($storageDirectory'/\\');
  98.         } elseif (is_array($storageDirectory)) {
  99.             $this->storageConfig $storageDirectory;
  100.         }
  101.     }
  102.     /**
  103.      * @param string $key
  104.      *
  105.      * @return array
  106.      */
  107.     public function loadConfigByKey(string $key)
  108.     {
  109.         $dataSource null;
  110.         // try to load from container config
  111.         $data $this->getDataFromContainerConfig($key$dataSource);
  112.         // try to load from SettingsStore
  113.         if (!$data) {
  114.             $data $this->getDataFromSettingsStore($key$dataSource);
  115.         }
  116.         // try to load from legacy config
  117.         if (!$data) {
  118.             $data $this->getDataFromLegacyConfig($key$dataSource);
  119.         }
  120.         return [
  121.             $data,
  122.             $dataSource,
  123.         ];
  124.     }
  125.     /**
  126.      * @param string $key
  127.      * @param string|null $dataSource
  128.      *
  129.      * @return mixed
  130.      */
  131.     private function getDataFromContainerConfig(string $key, ?string &$dataSource)
  132.     {
  133.         if (isset($this->containerConfig[$key])) {
  134.             $dataSource self::LOCATION_SYMFONY_CONFIG;
  135.         }
  136.         return $this->containerConfig[$key] ?? null;
  137.     }
  138.     /**
  139.      * @param string $key
  140.      * @param string|null $dataSource
  141.      *
  142.      * @return mixed
  143.      */
  144.     private function getDataFromSettingsStore(string $key, ?string &$dataSource)
  145.     {
  146.         $settingsStoreEntryData null;
  147.         $settingsStoreEntry SettingsStore::get($key$this->settingsStoreScope);
  148.         if ($settingsStoreEntry) {
  149.             $settingsStoreEntryData json_decode($settingsStoreEntry->getData(), true);
  150.             $dataSource self::LOCATION_SETTINGS_STORE;
  151.         }
  152.         return $settingsStoreEntryData;
  153.     }
  154.     /**
  155.      * @deprecated Will be removed in Pimcore 11
  156.      *
  157.      * @param string $key
  158.      *
  159.      * @return mixed
  160.      */
  161.     private function getDataFromLegacyConfig(string $key, ?string &$dataSource)
  162.     {
  163.         $callback $this->loadLegacyConfigCallback;
  164.         if (is_callable($callback)) {
  165.             return $callback($this$dataSource);
  166.         }
  167.         if (!$this->legacyConfigFile) {
  168.             return null;
  169.         }
  170.         $data $this->getLegacyStore()->fetchAll();
  171.         if (isset($data[$key])) {
  172.             $dataSource self::LOCATION_LEGACY;
  173.         }
  174.         return $data[$key] ?? null;
  175.     }
  176.     /**
  177.      * @param string|null $key
  178.      * @param string|null $dataSource
  179.      *
  180.      * @return bool
  181.      *
  182.      * @throws \Exception
  183.      */
  184.     public function isWriteable(?string $key null, ?string $dataSource null): bool
  185.     {
  186.         $key $key ?: uniqid('pimcore_random_key_'true);
  187.         $writeTarget $this->getWriteTarget();
  188.         if ($writeTarget === self::LOCATION_SYMFONY_CONFIG && !\Pimcore::getKernel()->isDebug()) {
  189.             return false;
  190.         } elseif ($writeTarget === self::LOCATION_DISABLED) {
  191.             return false;
  192.         } elseif ($dataSource === self::LOCATION_SYMFONY_CONFIG && !file_exists($this->getVarConfigFile($key))) {
  193.             return false;
  194.         } elseif ($dataSource && $dataSource !== self::LOCATION_LEGACY && $dataSource !== $writeTarget) {
  195.             return false;
  196.         }
  197.         return true;
  198.     }
  199.     /**
  200.      * @return string Can be either yaml (var/config/...) or "settings-store". defaults to "yaml"
  201.      *
  202.      * @throws \Exception
  203.      */
  204.     public function getWriteTarget(): string
  205.     {
  206.         //TODO remove in Pimcore 11
  207.         $writeLocation $this->writeTargetEnvVariableName $_SERVER[$this->writeTargetEnvVariableName] ?? null null;
  208.         if ($writeLocation === null) {
  209.             $writeLocation $this->storageConfig['write_target']['type'] ?? $this->defaultWriteLocation;
  210.         }
  211.         if (!in_array($writeLocation, [self::LOCATION_SETTINGS_STOREself::LOCATION_SYMFONY_CONFIGself::LOCATION_DISABLED])) {
  212.             throw new \Exception(sprintf('Invalid write location: %s'$writeLocation));
  213.         }
  214.         return $writeLocation;
  215.     }
  216.     /**
  217.      * @param string $key
  218.      * @param mixed $data
  219.      * @param null|callable $yamlStructureCallback
  220.      *
  221.      * @throws \Exception
  222.      */
  223.     public function saveConfig(string $key$data$yamlStructureCallback null)
  224.     {
  225.         $writeLocation $this->getWriteTarget();
  226.         if ($writeLocation === self::LOCATION_SYMFONY_CONFIG) {
  227.             if (is_callable($yamlStructureCallback)) {
  228.                 $data $yamlStructureCallback($key$data);
  229.             }
  230.             $this->writeYaml($key$data);
  231.         } elseif ($writeLocation === self::LOCATION_SETTINGS_STORE) {
  232.             $settingsStoreData json_encode($data);
  233.             SettingsStore::set($key$settingsStoreData'string'$this->settingsStoreScope);
  234.         }
  235.         $this->stopMessengerWorkers();
  236.     }
  237.     /**
  238.      * @param string $key
  239.      * @param array $data
  240.      *
  241.      * @throws \Exception
  242.      */
  243.     private function writeYaml(string $key$data): void
  244.     {
  245.         $yamlFilename $this->getVarConfigFile($key);
  246.         if (!file_exists($yamlFilename)) {
  247.             list($existingData$dataSource) = $this->loadConfigByKey($key);
  248.             if ($dataSource && $dataSource !== self::LOCATION_LEGACY) {
  249.                 // this configuration already exists so check if it is writeable
  250.                 // this is only the case if it comes from var/config or from the legacy file, or the settings-store
  251.                 // however, we never want to write it back to the legacy file
  252.                 throw new \Exception(sprintf('Configuration can only be written to %s, however the config comes from a different source'$yamlFilename));
  253.             }
  254.         }
  255.         $this->searchAndReplaceMissingParameters($data);
  256.         File::put($yamlFilenameYaml::dump($data50));
  257.         $this->invalidateConfigCache();
  258.     }
  259.     private function searchAndReplaceMissingParameters(array &$data): void
  260.     {
  261.         $container \Pimcore::getContainer();
  262.         foreach ($data as $key => &$value) {
  263.             if (is_array($value)) {
  264.                 $this->searchAndReplaceMissingParameters($value);
  265.                 continue;
  266.             }
  267.             if (!is_scalar($value)) {
  268.                 continue;
  269.             }
  270.             if (preg_match('/%([^%\s]+)%/'$value$match)) {
  271.                 $key $match[1];
  272.                 if (str_starts_with($key'env(') && str_ends_with($key')')  && 'env()' !== $key) {
  273.                     continue;
  274.                 }
  275.                 if (!$container->hasParameter($key)) {
  276.                     $value preg_replace('/%([^%\s]+)%/''%%$1%%'$value);
  277.                 }
  278.             }
  279.         }
  280.     }
  281.     /**
  282.      * @param string $key
  283.      *
  284.      * @return string
  285.      */
  286.     private function getVarConfigFile(string $key): string
  287.     {
  288.         $directory rtrim($this->storageDirectory ?? $this->storageConfig['write_target']['options']['directory'], '/\\');
  289.         return $directory '/' $key '.yaml';
  290.     }
  291.     /**
  292.      * @deprecated Will be removed in Pimcore 11
  293.      *
  294.      * @return PhpArrayFileTable
  295.      */
  296.     private function getLegacyStore(): PhpArrayFileTable
  297.     {
  298.         if ($this->legacyStore === null) {
  299.             $file Config::locateConfigFile($this->legacyConfigFile);
  300.             $this->legacyStore PhpArrayFileTable::get($file);
  301.         }
  302.         return $this->legacyStore;
  303.     }
  304.     /**
  305.      * @param string $key
  306.      * @param string|null $dataSource
  307.      *
  308.      * @throws \Exception
  309.      */
  310.     public function deleteData(string $key, ?string $dataSource): void
  311.     {
  312.         if (!$this->isWriteable($key)) {
  313.             throw new \Exception('You are trying to delete a non-writable configuration.');
  314.         }
  315.         if ($dataSource === self::LOCATION_SYMFONY_CONFIG) {
  316.             unlink($this->getVarConfigFile($key));
  317.             $this->invalidateConfigCache();
  318.         } elseif ($dataSource === self::LOCATION_SETTINGS_STORE) {
  319.             SettingsStore::delete($key$this->settingsStoreScope);
  320.         } elseif ($dataSource === self::LOCATION_LEGACY) {
  321.             $this->getLegacyStore()->delete($key);
  322.         }
  323.         $this->stopMessengerWorkers();
  324.     }
  325.     /**
  326.      * @return array
  327.      */
  328.     public function fetchAllKeys(): array
  329.     {
  330.         return array_unique(array_merge(
  331.             SettingsStore::getIdsByScope($this->settingsStoreScope),
  332.             array_keys($this->containerConfig),
  333.             $this->legacyConfigFile array_keys($this->getLegacyStore()->fetchAll()) : [],
  334.         ));
  335.     }
  336.     private function invalidateConfigCache(): void
  337.     {
  338.         // invalidate container config cache if debug flag on kernel is set
  339.         $systemConfigFile Config::locateConfigFile('system.yml');
  340.         if ($systemConfigFile) {
  341.             touch($systemConfigFile);
  342.         }
  343.     }
  344.     /**
  345.      * @TODO to be removed in Pimcore 11
  346.      *
  347.      * @internal
  348.      *
  349.      * @param array $containerConfig
  350.      * @param string $configId
  351.      *
  352.      * @return array
  353.      */
  354.     public static function getStorageConfigurationCompatibilityLayer(
  355.         array $containerConfig,
  356.         string $configId,
  357.         string $storagePathEnvVarName,
  358.         string $writeTargetEnvVarName,
  359.     ): array {
  360.         $storageConfig $containerConfig['config_location'][$configId];
  361.         if (isset($_SERVER[$writeTargetEnvVarName])) {
  362.             trigger_deprecation('pimcore/pimcore''10.6',
  363.                 sprintf('Setting write targets (%s) using environment variables is deprecated, instead use the symfony config. It will be removed in Pimcore 11.'$writeTargetEnvVarName));
  364.             $storageConfig['write_target']['type'] = $_SERVER[$writeTargetEnvVarName];
  365.         }
  366.         if (isset($_SERVER[$storagePathEnvVarName])) {
  367.             trigger_deprecation('pimcore/pimcore''10.6',
  368.                 sprintf('Setting storage directory (%s) in the environment variables file is deprecated, instead use the symfony config. It will be removed in Pimcore 11.'$storagePathEnvVarName));
  369.             $storageConfig['write_target']['options']['directory'] = $_SERVER[$storagePathEnvVarName];
  370.         }
  371.         return $storageConfig;
  372.     }
  373. }