diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form.php
index 9c1e38d14f8..b8cdcf6ab40 100644
--- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form.php
+++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form.php
@@ -17,6 +17,7 @@ class Mage_Adminhtml_Block_System_Config_Form extends Mage_Adminhtml_Block_Widge
public const SCOPE_DEFAULT = 'default';
public const SCOPE_WEBSITES = 'websites';
public const SCOPE_STORES = 'stores';
+ public const SCOPE_ENV = 'env';
/**
* Config data array
@@ -71,6 +72,7 @@ public function __construct()
self::SCOPE_DEFAULT => Mage::helper('adminhtml')->__('[GLOBAL]'),
self::SCOPE_WEBSITES => Mage::helper('adminhtml')->__('[WEBSITE]'),
self::SCOPE_STORES => Mage::helper('adminhtml')->__('[STORE VIEW]'),
+ self::SCOPE_ENV => Mage::helper('adminhtml')->__('[ENV]'),
];
}
@@ -368,7 +370,7 @@ public function initFields($fieldset, $group, $section, $fieldPrefix = '', $labe
}
}
- $field = $fieldset->addField($id, $fieldType, [
+ $elementFieldData = [
'name' => $name,
'label' => $label,
'comment' => $comment,
@@ -384,7 +386,14 @@ public function initFields($fieldset, $group, $section, $fieldPrefix = '', $labe
'scope_label' => $this->getScopeLabel($element),
'can_use_default_value' => $this->canUseDefaultValue((int) $element->show_in_default),
'can_use_website_value' => $this->canUseWebsiteValue((int) $element->show_in_website),
- ]);
+ ];
+ if ($this->isOverwrittenByEnvVariable($path)) {
+ $elementFieldData['scope_label'] = $this->_scopeLabels[static::SCOPE_ENV];
+ $elementFieldData['disabled'] = 1;
+ $elementFieldData['can_use_default_value'] = 0;
+ $elementFieldData['can_use_website_value'] = 0;
+ }
+ $field = $fieldset->addField($id, $fieldType, $elementFieldData);
$this->_prepareFieldOriginalData($field, $element);
if (isset($element->validate)) {
@@ -622,6 +631,23 @@ public function getScope()
return $scope;
}
+ /**
+ * Returns true if element was overwritten by ENV variable
+ */
+ public function isOverwrittenByEnvVariable(string $path): bool
+ {
+ /** @var Mage_Core_Helper_EnvironmentConfigLoader $environmentConfigLoaderHelper */
+ $environmentConfigLoaderHelper = Mage::helper('core/environmentConfigLoader');
+ $store = Mage::app()->getRequest()->getParam('store');
+ if ($store) {
+ $scope = $this->getScope();
+ $path = "$scope/$store/$path";
+ return $environmentConfigLoaderHelper->hasPath($path);
+ }
+ $path = "default/$path";
+ return $environmentConfigLoaderHelper->hasPath($path);
+ }
+
/**
* Retrieve label for scope
*
diff --git a/app/code/core/Mage/Adminhtml/Model/Config/Data.php b/app/code/core/Mage/Adminhtml/Model/Config/Data.php
index edc85b37bda..52fee450ee8 100644
--- a/app/code/core/Mage/Adminhtml/Model/Config/Data.php
+++ b/app/code/core/Mage/Adminhtml/Model/Config/Data.php
@@ -341,6 +341,14 @@ protected function _getPathConfig($path, $full = true)
$config[$data->getPath()] = $data->getValue();
}
}
+
+ if (!$full) {
+ /** @var Mage_Core_Helper_EnvironmentConfigLoader $environmentConfigLoaderHelper */
+ $environmentConfigLoaderHelper = Mage::helper('core/environmentConfigLoader');
+ $store = $this->getStore();
+ $envConfig = $environmentConfigLoaderHelper->getAsArray($store);
+ $config = array_merge($config, $envConfig);
+ }
return $config;
}
diff --git a/app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php b/app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php
index 33d78ea3d2b..1f3c6d2d7d0 100644
--- a/app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php
+++ b/app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php
@@ -15,6 +15,7 @@
class Mage_Core_Helper_EnvironmentConfigLoader extends Mage_Core_Helper_Abstract
{
protected const ENV_STARTS_WITH = 'OPENMAGE_CONFIG';
+ protected const ENV_FEATURE_ENABLED = 'OPENMAGE_CONFIG_OVERRIDE_ALLOWED';
protected const ENV_KEY_SEPARATOR = '__';
protected const CONFIG_KEY_DEFAULT = 'DEFAULT';
protected const CONFIG_KEY_WEBSITES = 'WEBSITES';
@@ -50,6 +51,10 @@ class Mage_Core_Helper_EnvironmentConfigLoader extends Mage_Core_Helper_Abstract
*/
public function overrideEnvironment(Varien_Simplexml_Config $xmlConfig)
{
+ $data = Mage::registry('current_env_config');
+ if ($data) {
+ return;
+ }
$env = $this->getEnv();
foreach ($env as $configKey => $value) {
@@ -63,18 +68,120 @@ public function overrideEnvironment(Varien_Simplexml_Config $xmlConfig)
case static::CONFIG_KEY_DEFAULT:
[$unused1, $unused2, $section, $group, $field] = $configKeyParts;
$path = $this->buildPath($section, $group, $field);
- $xmlConfig->setNode($this->buildNodePath($scope, $path), $value);
+ $nodePath = $this->buildNodePath($scope, $path);
+ $xmlConfig->setNode($nodePath, $value);
+ try {
+ foreach (['0', 'admin'] as $store) {
+ $store = Mage::app()->getStore($store);
+ $this->setCache($store, $value, $path);
+ }
+ } catch (Throwable $exception) {
+ // invalid store, intentionally empty
+ }
break;
case static::CONFIG_KEY_WEBSITES:
case static::CONFIG_KEY_STORES:
- [$unused1, $unused2, $code, $section, $group, $field] = $configKeyParts;
+ [$unused1, $unused2, $storeCode, $section, $group, $field] = $configKeyParts;
$path = $this->buildPath($section, $group, $field);
- $nodePath = sprintf('%s/%s/%s', strtolower($scope), strtolower($code), $path);
+ $storeCode = strtolower($storeCode);
+ $scope = strtolower($scope);
+ $nodePath = sprintf('%s/%s/%s', $scope, $storeCode, $path);
$xmlConfig->setNode($nodePath, $value);
+ try {
+ if (!str_contains($nodePath, 'websites')) {
+ foreach ([$storeCode, 'admin'] as $store) {
+ $store = Mage::app()->getStore($store);
+ $this->setCache($store, $value, $path);
+ }
+ }
+ } catch (Throwable $exception) {
+ // invalid store, intentionally empty
+ }
+ break;
+ }
+ }
+ Mage::register('current_env_config', true, true);
+ }
+
+ public function hasPath(string $wantedPath): bool
+ {
+ $data = Mage::registry("config_env_has_path_$wantedPath");
+ if ($data !== null) {
+ return $data;
+ }
+ $env = $this->getEnv();
+ $config = [];
+
+ foreach ($env as $configKey => $value) {
+ if (!$this->isConfigKeyValid($configKey)) {
+ continue;
+ }
+
+ [$configKeyParts, $scope] = $this->getConfigKey($configKey);
+
+ switch ($scope) {
+ case static::CONFIG_KEY_DEFAULT:
+ [$unused1, $unused2, $section, $group, $field] = $configKeyParts;
+ $path = $this->buildPath($section, $group, $field);
+ $nodePath = $this->buildNodePath($scope, $path);
+ $config[$nodePath] = $value;
+ break;
+
+ case static::CONFIG_KEY_WEBSITES:
+ case static::CONFIG_KEY_STORES:
+ [$unused1, $unused2, $storeCode, $section, $group, $field] = $configKeyParts;
+ $path = $this->buildPath($section, $group, $field);
+ $storeCode = strtolower($storeCode);
+ $scope = strtolower($scope);
+ $nodePath = sprintf('%s/%s/%s', $scope, $storeCode, $path);
+ $config[$nodePath] = $value;
break;
}
}
+ $hasConfig = array_key_exists($wantedPath, $config);
+ Mage::register("config_env_has_path_$wantedPath", $hasConfig);
+ return $hasConfig;
+ }
+
+ public function getAsArray(string $wantedStore): array
+ {
+ if (empty($wantedStore)) {
+ $wantedStore = 'default';
+ }
+ $data = Mage::registry("config_env_array_$wantedStore");
+ if ($data !== null) {
+ return $data;
+ }
+ $env = $this->getEnv();
+ $config = [];
+
+ foreach ($env as $configKey => $value) {
+ if (!$this->isConfigKeyValid($configKey)) {
+ continue;
+ }
+
+ [$configKeyParts, $scope] = $this->getConfigKey($configKey);
+
+ switch ($scope) {
+ case static::CONFIG_KEY_DEFAULT:
+ [$unused1, $unused2, $section, $group, $field] = $configKeyParts;
+ $path = $this->buildPath($section, $group, $field);
+ $config[$path] = $value;
+ break;
+ case static::CONFIG_KEY_WEBSITES:
+ case static::CONFIG_KEY_STORES:
+ [$unused1, $unused2, $storeCode, $section, $group, $field] = $configKeyParts;
+ if (strtolower($storeCode) !== strtolower($wantedStore)) {
+ break;
+ }
+ $path = $this->buildPath($section, $group, $field);
+ $config[$path] = $value;
+ break;
+ }
+ }
+ Mage::register("config_env_array_$wantedStore", $config);
+ return $config;
}
/**
@@ -88,11 +195,34 @@ public function setEnvStore(array $envStorage): void
public function getEnv(): array
{
if (empty($this->envStore)) {
- $this->envStore = getenv();
+ $env = getenv();
+ $env = array_filter($env, function ($key) {
+ return str_starts_with($key, static::ENV_STARTS_WITH);
+ }, ARRAY_FILTER_USE_KEY);
+ $this->envStore = $env;
+ }
+ if (!isset($this->envStore[static::ENV_FEATURE_ENABLED]) ||
+ (bool) $this->envStore[static::ENV_FEATURE_ENABLED] === false
+ ) {
+ $this->envStore = [];
+ return $this->envStore;
}
return $this->envStore;
}
+ protected function setCache(Mage_Core_Model_Store $store, $value, string $path): void
+ {
+ $refObject = new ReflectionObject($store);
+ $refProperty = $refObject->getProperty('_configCache');
+ $refProperty->setAccessible(true);
+ $configCache = $refProperty->getValue($store);
+ if (!is_array($configCache)) {
+ $configCache = [];
+ }
+ $configCache[$path] = $value;
+ $store->setConfigCache($configCache);
+ }
+
protected function getConfigKey(string $configKey): array
{
$configKeyParts = array_filter(
@@ -108,10 +238,6 @@ protected function getConfigKey(string $configKey): array
protected function isConfigKeyValid(string $configKey): bool
{
- if (!str_starts_with($configKey, static::ENV_STARTS_WITH)) {
- return false;
- }
-
$sectionGroupFieldRegexp = sprintf('([%s]*)', implode('', static::ALLOWED_CHARS));
$allowedChars = sprintf('[%s]', implode('', static::ALLOWED_CHARS));
$regexp = '/' . static::ENV_STARTS_WITH . static::ENV_KEY_SEPARATOR . '(WEBSITES' . static::ENV_KEY_SEPARATOR
diff --git a/app/code/core/Mage/Core/Model/App.php b/app/code/core/Mage/Core/Model/App.php
index 422c2830cb8..a5f10bba2e3 100644
--- a/app/code/core/Mage/Core/Model/App.php
+++ b/app/code/core/Mage/Core/Model/App.php
@@ -622,6 +622,7 @@ public function reinitStores()
*/
protected function _initStores()
{
+ Mage::unregister('current_env_config');
$this->_stores = [];
$this->_groups = [];
$this->_website = null;
diff --git a/app/code/core/Mage/Core/Model/Store.php b/app/code/core/Mage/Core/Model/Store.php
index b0670a29402..cde4b284ae5 100644
--- a/app/code/core/Mage/Core/Model/Store.php
+++ b/app/code/core/Mage/Core/Model/Store.php
@@ -336,6 +336,9 @@ public function getConfig($path)
}
$config = Mage::getConfig();
+ /** @var Mage_Core_Helper_EnvironmentConfigLoader $environmentConfigLoaderHelper */
+ $environmentConfigLoaderHelper = Mage::helper('core/environmentConfigLoader');
+ $environmentConfigLoaderHelper->overrideEnvironment($config);
$fullPath = 'stores/' . $this->getCode() . '/' . $path;
$data = $config->getNode($fullPath);
diff --git a/tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php b/tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
index 2029fe89bde..662b434c2bc 100644
--- a/tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
+++ b/tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
@@ -18,6 +18,9 @@
use OpenMage\Tests\Unit\OpenMageTest;
use Varien_Simplexml_Config;
+/**
+ * @group Mage_Core_EnvLoader
+ */
class EnvironmentConfigLoaderTest extends OpenMageTest
{
public const XML_PATH_GENERAL = 'general/store_information/name';
@@ -49,6 +52,32 @@ public function testBuildPath(): void
/**
* @group Helper
*/
+ public function testEnvFilter(): void
+ {
+ $environmentConfigLoaderHelper = new EnvironmentConfigLoaderTestHelper();
+ /** @phpstan-ignore method.internal */
+ $environmentConfigLoaderHelper->setEnvStore([
+ 'OPENMAGE_CONFIG__DEFAULT__GENERAL__STORE_INFORMATION__NAME' => 'some_value',
+ ]);
+ // empty because env flag is not set
+ $env = $environmentConfigLoaderHelper->getEnv();
+ static::assertIsArray($env);
+ static::assertEmpty($env);
+ /** @phpstan-ignore method.internal */
+ $environmentConfigLoaderHelper->setEnvStore([
+ 'OPENMAGE_CONFIG__DEFAULT__GENERAL__STORE_INFORMATION__NAME' => 'some_value',
+ 'OPENMAGE_CONFIG_OVERRIDE_ALLOWED' => 1, // enable feature
+ ]);
+ // flag is set => feature is enabled
+ $env = $environmentConfigLoaderHelper->getEnv();
+ static::assertIsArray($env);
+ static::assertNotEmpty($env);
+ }
+
+ /**
+ * @group Mage_Core
+ * @group Mage_Core_Helper
+ */
public function testBuildNodePath(): void
{
$environmentConfigLoaderHelper = new EnvironmentConfigLoaderTestHelper();
@@ -70,6 +99,7 @@ public function testXmlHasTestStrings(): void
}
/**
+ * @runInSeparateProcess
* @dataProvider envOverridesCorrectConfigKeysDataProvider
* @group Helper
*
@@ -84,11 +114,14 @@ public function testEnvOverridesForValidConfigKeys(array $config): void
$xml = new Varien_Simplexml_Config();
$xml->loadString($xmlStruct);
+
$loader = new Mage_Core_Helper_EnvironmentConfigLoader();
/** @phpstan-ignore method.internal */
$loader->setEnvStore([
+ 'OPENMAGE_CONFIG_OVERRIDE_ALLOWED' => 1,
$config['env_path'] => $config['value'],
]);
+ Mage::unregister('current_env_config');
$loader->overrideEnvironment($xml);
$configPath = $config['xml_path'];
@@ -96,7 +129,9 @@ public function testEnvOverridesForValidConfigKeys(array $config): void
$valueAfterOverride = $xml->getNode($configPath);
// assert
- static::assertNotSame((string) $defaultValue, (string) $valueAfterOverride, 'Default value was not overridden.');
+ $expected = (string) $defaultValue;
+ $actual = (string) $valueAfterOverride;
+ static::assertNotSame($expected, $actual, 'Default value was not overridden.');
}
public function envOverridesCorrectConfigKeysDataProvider(): Generator
@@ -170,6 +205,104 @@ public function envOverridesCorrectConfigKeysDataProvider(): Generator
}
/**
+ * @runInSeparateProcess
+ * @dataProvider envAsArrayDataProvider
+ * @group Mage_Core
+ *
+ * @param array $config
+ */
+ public function testAsArray(array $config): void
+ {
+ // phpcs:ignore Ecg.Classes.ObjectInstantiation.DirectInstantiation
+ $loader = new Mage_Core_Helper_EnvironmentConfigLoader();
+ /** @phpstan-ignore method.internal */
+ $loader->setEnvStore([
+ 'OPENMAGE_CONFIG_OVERRIDE_ALLOWED' => 1,
+ $config['env_path'] => 1,
+ ]);
+ $store = $config['store'];
+ $actual = $loader->getAsArray($store);
+ $expected = $config['expected'];
+ static::assertSame($expected, $actual);
+ }
+
+ public function envAsArrayDataProvider(): Generator
+ {
+ yield 'default' => [
+ [
+ 'env_path' => 'OPENMAGE_CONFIG__DEFAULT__GENERAL__STORE_INFORMATION__NAME',
+ 'store' => '', // or 'default', which will be used internally, but this is how \Mage_Adminhtml_Model_Config_Data::_validate defines it
+ 'expected' => [
+ self::XML_PATH_GENERAL => 1,
+ ],
+ ],
+ ];
+ yield 'store' => [
+ [
+ 'env_path' => 'OPENMAGE_CONFIG__STORES__GERMAN__GENERAL__STORE_INFORMATION__NAME',
+ 'store' => 'german',
+ 'expected' => [
+ self::XML_PATH_GENERAL => 1,
+ ],
+ ],
+ ];
+ yield 'invalidStore' => [
+ [
+ 'env_path' => '',
+ 'store' => 'foo',
+ 'expected' => [],
+ ],
+ ];
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @dataProvider envHasPathDataProvider
+ * @group Mage_Core
+ *
+ * @param array $config
+ */
+ public function testHasPath(array $config): void
+ {
+ // phpcs:ignore Ecg.Classes.ObjectInstantiation.DirectInstantiation
+ $loader = new Mage_Core_Helper_EnvironmentConfigLoader();
+ /** @phpstan-ignore method.internal */
+ $loader->setEnvStore([
+ 'OPENMAGE_CONFIG_OVERRIDE_ALLOWED' => 1,
+ $config['env_path'] => 1,
+ ]);
+ $actual = $loader->hasPath($config['xml_path']);
+ $expected = $config['expected'];
+ static::assertSame($expected, $actual);
+ }
+
+ public function envHasPathDataProvider(): Generator
+ {
+ yield 'hasPath default' => [
+ [
+ 'env_path' => 'OPENMAGE_CONFIG__DEFAULT__GENERAL__STORE_INFORMATION__NAME',
+ 'xml_path' => 'default/general/store_information/name',
+ 'expected' => true,
+ ],
+ ];
+ yield 'hasPath store' => [
+ [
+ 'env_path' => 'OPENMAGE_CONFIG__STORES__GERMAN__GENERAL__STORE_INFORMATION__NAME',
+ 'xml_path' => 'stores/german/general/store_information/name',
+ 'expected' => true,
+ ],
+ ];
+ yield 'hasNotPath' => [
+ [
+ 'env_path' => 'OPENMAGE_CONFIG__DEFAULT__GENERAL__STORE_INFORMATION__NAME',
+ 'xml_path' => 'foo/foo/foo',
+ 'expected' => false,
+ ],
+ ];
+ }
+
+ /**
+ * @runInSeparateProcess
* @dataProvider envDoesNotOverrideOnWrongConfigKeysDataProvider
* @group Helper
*
@@ -185,15 +318,19 @@ public function testEnvDoesNotOverrideForInvalidConfigKeys(array $config): void
$xml->loadString($xmlStruct);
$defaultValue = 'test_default';
- static::assertSame($defaultValue, (string) $xml->getNode(self::XML_PATH_DEFAULT));
+ $actual = (string) $xml->getNode(self::XML_PATH_DEFAULT);
+ static::assertSame($defaultValue, $actual);
$defaultWebsiteValue = 'test_website';
- static::assertSame($defaultWebsiteValue, (string) $xml->getNode(self::XML_PATH_WEBSITE));
+ $actual = (string) $xml->getNode(self::XML_PATH_WEBSITE);
+ static::assertSame($defaultWebsiteValue, $actual);
$defaultStoreValue = 'test_store';
- static::assertSame($defaultStoreValue, (string) $xml->getNode(self::XML_PATH_STORE));
+ $actual = (string) $xml->getNode(self::XML_PATH_STORE);
+ static::assertSame($defaultStoreValue, $actual);
$loader = new Mage_Core_Helper_EnvironmentConfigLoader();
/** @phpstan-ignore method.internal */
$loader->setEnvStore([
+ 'OPENMAGE_CONFIG_OVERRIDE_ALLOWED' => 1,
$config['path'] => $config['value'],
]);
$loader->overrideEnvironment($xml);