Skip to content

Fix environment loader #4617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a9fe11c
fix: set cache when env loader overrides data
Dec 29, 2024
99b3d84
feat?: call env helper on config get
Feb 8, 2025
e08fccf
feat?: backend: show data from env
Feb 8, 2025
5d22f56
feat?: backend: disable field when ENV data is available
Feb 8, 2025
ecd1157
Update app/code/core/Mage/Adminhtml/Block/System/Config/Form.php
sreichel Feb 8, 2025
2bd48b9
Update app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php
sreichel Feb 8, 2025
d9b0faa
feat?: backend: do not log on invalid stores - creates infinite loop
Feb 9, 2025
69ac537
feat: move env variables "str_starts_with"
Feb 17, 2025
2f3402f
feat: add env flag to disable/enable feature
Feb 17, 2025
b65cf70
fix: fix tests
Feb 17, 2025
d8927bc
feat: set disabled, set scope label
Feb 17, 2025
9fbc1a6
feat: remove checkbox to override
Feb 17, 2025
8362d2c
chore: remove inline helper variable name
Feb 17, 2025
de4e7fc
Update tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
pquerner Feb 17, 2025
c26f384
Update tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
pquerner Feb 17, 2025
c6c9422
Update app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php
pquerner Feb 17, 2025
cfeb9dd
Update app/code/core/Mage/Adminhtml/Block/System/Config/Form.php
pquerner Feb 17, 2025
1642a7d
Update tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
pquerner Feb 17, 2025
df382c8
Update app/code/core/Mage/Adminhtml/Block/System/Config/Form.php
pquerner Feb 18, 2025
a04943b
feat: add "hasPath" test
Feb 18, 2025
4704318
feat: add "asArray" test
Feb 18, 2025
59ce16b
tests: add test cases
Feb 18, 2025
5a6f669
Update app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php
pquerner Mar 9, 2025
325ae2a
fix: onStore(re)init the env vars would be lost
Mar 9, 2025
32c813f
envLoader: fix loading from scopes
Jun 16, 2025
912f557
chore: run phpcs
Jun 16, 2025
af92809
chore: phptan: add ignore line comment about internal method
Jun 16, 2025
a2e51db
envLoader: fix tests, fix scope default
Jun 16, 2025
f9b3283
chore: run rector:fix
Jun 16, 2025
bff5358
Update tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
sreichel Jun 19, 2025
b942d99
Update tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
sreichel Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions app/code/core/Mage/Adminhtml/Block/System/Config/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]'),
];
}

Expand Down Expand Up @@ -368,7 +370,7 @@ public function initFields($fieldset, $group, $section, $fieldPrefix = '', $labe
}
}

$field = $fieldset->addField($id, $fieldType, [
$elementFieldData = [
'name' => $name,
'label' => $label,
'comment' => $comment,
Expand All @@ -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)) {
Expand Down Expand Up @@ -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
*
Expand Down
8 changes: 8 additions & 0 deletions app/code/core/Mage/Adminhtml/Model/Config/Data.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
142 changes: 134 additions & 8 deletions app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

/**
Expand All @@ -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(
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions app/code/core/Mage/Core/Model/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ public function reinitStores()
*/
protected function _initStores()
{
Mage::unregister('current_env_config');
$this->_stores = [];
$this->_groups = [];
$this->_website = null;
Expand Down
3 changes: 3 additions & 0 deletions app/code/core/Mage/Core/Model/Store.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading