Skip to content

Commit 80bd8da

Browse files
Merge pull request #7 from ericthehacker/feature/scope-hint-values-display
Show override value hint
2 parents 83f8471 + d068220 commit 80bd8da

File tree

6 files changed

+163
-30
lines changed

6 files changed

+163
-30
lines changed

Helper/Data.php

Lines changed: 113 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
class Data extends \Magento\Framework\App\Helper\AbstractHelper
88
{
9+
const STORE_VIEW_SCOPE_CODE = 'stores';
10+
const WEBSITE_SCOPE_CODE = 'websites';
11+
912
/** @var \Magento\Framework\App\Helper\Context */
1013
protected $context;
1114

@@ -18,16 +21,29 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
1821
* @var \Magento\Backend\Model\Url
1922
*/
2023
protected $urlBuilder;
24+
/**
25+
* @var \Magento\Config\Model\Config\Structure\SearchInterface
26+
*/
27+
protected $configStructure;
28+
/**
29+
* @var \Magento\Framework\Escaper
30+
*/
31+
protected $escaper;
2132

2233
/**
34+
* Data constructor.
2335
* @param \Magento\Framework\App\Helper\Context $context
2436
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
2537
* @param \Magento\Backend\Model\Url $urlBuilder
38+
* @param \Magento\Config\Model\Config\Structure\SearchInterface $configStructure
39+
* @param \Magento\Framework\Escaper $escaper
2640
*/
2741
public function __construct(
2842
\Magento\Framework\App\Helper\Context $context,
2943
\Magento\Store\Model\StoreManagerInterface $storeManager,
30-
\Magento\Backend\Model\Url $urlBuilder
44+
\Magento\Backend\Model\Url $urlBuilder,
45+
\Magento\Config\Model\Config\Structure\SearchInterface $configStructure,
46+
\Magento\Framework\Escaper $escaper
3147
) {
3248
parent::__construct($context);
3349

@@ -37,6 +53,8 @@ public function __construct(
3753
// an instance of \Magento\Framework\Url instead of \Magento\Backend\Model\Url, we must explicitly request it
3854
// via DI.
3955
$this->urlBuilder = $urlBuilder;
56+
$this->configStructure = $configStructure;
57+
$this->escaper = $escaper;
4058
}
4159

4260
/**
@@ -46,17 +64,17 @@ public function __construct(
4664
* @return array
4765
*/
4866
public function getScopeTree() {
49-
$tree = array('websites' => array());
67+
$tree = array(self::WEBSITE_SCOPE_CODE => array());
5068

5169
$websites = $this->storeManager->getWebsites();
5270

5371
/* @var $website Website */
5472
foreach($websites as $website) {
55-
$tree['websites'][$website->getId()] = array('stores' => array());
73+
$tree[self::WEBSITE_SCOPE_CODE][$website->getId()] = array(self::STORE_VIEW_SCOPE_CODE => array());
5674

5775
/* @var $store Store */
5876
foreach($website->getStores() as $store) {
59-
$tree['websites'][$website->getId()]['stores'][] = $store->getId();
77+
$tree[self::WEBSITE_SCOPE_CODE][$website->getId()][self::STORE_VIEW_SCOPE_CODE][] = $store->getId();
6078
}
6179
}
6280

@@ -66,15 +84,51 @@ public function getScopeTree() {
6684
/**
6785
* Wrapper method to get config value at path, scope, and scope code provided
6886
*
69-
* @param $path
70-
* @param $contextScope
71-
* @param $contextScopeId
72-
* @return mixed
87+
* @param string $path
88+
* @param string $contextScope
89+
* @param string|int $contextScopeId
90+
* @return string
7391
*/
7492
protected function _getConfigValue($path, $contextScope, $contextScopeId) {
7593
return $this->context->getScopeConfig()->getValue($path, $contextScope, $contextScopeId);
7694
}
7795

96+
/**
97+
* Gets human-friendly display value(s) for given config path
98+
*
99+
* @param string $path
100+
* @param string $contextScope
101+
* @param string|int $contextScopeId
102+
* @return array
103+
*/
104+
public function getConfigDisplayValue($path, $contextScope, $contextScopeId) {
105+
$value = $this->_getConfigValue($path, $contextScope, $contextScopeId);
106+
107+
$labels = [$value]; //default labels to raw value
108+
109+
/** @var \Magento\Config\Model\Config\Structure\Element\Field $field */
110+
$field = $this->configStructure->getElement($path);
111+
112+
if($field->getOptions()) {
113+
$labels = []; //reset labels so we can add human-friendly labels
114+
115+
$optionsByValue = [];
116+
foreach($field->getOptions() as $option) {
117+
$optionsByValue[$option['value']] = $option;
118+
}
119+
120+
$values = explode(',', $value);
121+
122+
foreach($values as $valueInstance) {
123+
$labels[] = isset($optionsByValue[$valueInstance])
124+
? $optionsByValue[$valueInstance]['label'] : $valueInstance;
125+
126+
}
127+
}
128+
129+
return $labels;
130+
}
131+
78132
/**
79133
* Gets array of scopes and scope IDs where path value is different
80134
* than supplied context scope and context scope ID.
@@ -90,41 +144,43 @@ public function getOverriddenLevels($path, $contextScope, $contextScopeId) {
90144

91145
$currentValue = $this->_getConfigValue($path, $contextScope, $contextScopeId);
92146

93-
if(is_null($currentValue)) {
94-
return array(); //something is off, let's bail gracefully.
95-
}
96-
97147
$overridden = array();
98148

99149
switch($contextScope) {
100-
case 'websites':
101-
$stores = array_values($tree['websites'][$contextScopeId]['stores']);
150+
case self::WEBSITE_SCOPE_CODE:
151+
$stores = array_values($tree[self::WEBSITE_SCOPE_CODE][$contextScopeId][self::STORE_VIEW_SCOPE_CODE]);
102152
foreach($stores as $storeId) {
103-
$value = $this->_getConfigValue($path, 'stores', $storeId);
153+
$value = $this->_getConfigValue($path, self::STORE_VIEW_SCOPE_CODE, $storeId);
104154
if($value != $currentValue) {
105155
$overridden[] = array(
106156
'scope' => 'store',
107-
'scope_id' => $storeId
157+
'scope_id' => $storeId,
158+
'value' => $value,
159+
'display_value' => $this->getConfigDisplayValue($path, self::STORE_VIEW_SCOPE_CODE, $storeId)
108160
);
109161
}
110162
}
111163
break;
112164
case 'default':
113-
foreach($tree['websites'] as $websiteId => $website) {
114-
$websiteValue = $this->_getConfigValue($path, 'websites', $websiteId);
165+
foreach($tree[self::WEBSITE_SCOPE_CODE] as $websiteId => $website) {
166+
$websiteValue = $this->_getConfigValue($path, self::WEBSITE_SCOPE_CODE, $websiteId);
115167
if($websiteValue != $currentValue) {
116168
$overridden[] = array(
117169
'scope' => 'website',
118-
'scope_id' => $websiteId
170+
'scope_id' => $websiteId,
171+
'value' => $websiteValue,
172+
'display_value' => $this->getConfigDisplayValue($path, self::WEBSITE_SCOPE_CODE, $websiteId)
119173
);
120174
}
121175

122-
foreach($website['stores'] as $storeId) {
123-
$value = $this->_getConfigValue($path, 'stores', $storeId);
176+
foreach($website[self::STORE_VIEW_SCOPE_CODE] as $storeId) {
177+
$value = $this->_getConfigValue($path, self::STORE_VIEW_SCOPE_CODE, $storeId);
124178
if($value != $currentValue && $value != $websiteValue) {
125179
$overridden[] = array(
126180
'scope' => 'store',
127-
'scope_id' => $storeId
181+
'scope_id' => $storeId,
182+
'value' => $value,
183+
'display_value' => $this->getConfigDisplayValue($path, self::STORE_VIEW_SCOPE_CODE, $storeId)
128184
);
129185
}
130186
}
@@ -135,6 +191,31 @@ public function getOverriddenLevels($path, $contextScope, $contextScopeId) {
135191
return $overridden;
136192
}
137193

194+
/**
195+
* Get HTML formatted value label(s)
196+
*
197+
* @param array $labels
198+
* @return string
199+
*/
200+
protected function getFormattedValueLabels(array $labels) {
201+
if(count($labels) == 1) {
202+
//if only one value, simply return it
203+
return '<span class="override-value-hint-label">' .
204+
nl2br($this->escaper->escapeHtml($labels[0])) .
205+
'</span>';
206+
}
207+
208+
$formattedLabels = '';
209+
210+
foreach($labels as $label) {
211+
$formattedLabels .= '<li class="override-value-hint-label">' .
212+
nl2br($this->escaper->escapeHtml($label)) .
213+
'</li>';
214+
}
215+
216+
return '<ul class="override-value-hint-labels">' . $formattedLabels . '</ul>';
217+
}
218+
138219
/**
139220
* Get HTML output for override hint UI
140221
*
@@ -145,11 +226,13 @@ public function getOverriddenLevels($path, $contextScope, $contextScopeId) {
145226
public function formatOverriddenScopes($section, array $overridden) {
146227
$formatted = '<div class="overridden-hint-wrapper">' .
147228
'<p class="lead-text">' . __('This config field is overridden at the following scope(s):') . '</p>' .
148-
'<ul class="overridden-hint-list">';
229+
'<dl class="overridden-hint-list">';
149230

150231
foreach($overridden as $overriddenScope) {
151232
$scope = $overriddenScope['scope'];
152233
$scopeId = $overriddenScope['scope_id'];
234+
$value = $overriddenScope['value'];
235+
$valueLabel = $overriddenScope['display_value'];
153236
$scopeLabel = $scopeId;
154237

155238
$url = '#';
@@ -170,6 +253,7 @@ public function formatOverriddenScopes($section, array $overridden) {
170253

171254
break;
172255
case 'store':
256+
/** @var \Magento\Store\Model\Store $store */
173257
$store = $this->storeManager->getStore($scopeId);
174258
$website = $store->getWebsite();
175259
$url = $this->urlBuilder->getUrl(
@@ -187,10 +271,14 @@ public function formatOverriddenScopes($section, array $overridden) {
187271
break;
188272
}
189273

190-
$formatted .= '<li class="' . $scope . '">'. $scopeLabel .'</li>';
274+
$formatted .=
275+
'<dt class="override-scope ' . $scope . '" title="'. __('Click to see overridden value') .'">'
276+
. $scopeLabel .
277+
'</dt>' .
278+
'<dd class="override-value">' . $this->getFormattedValueLabels($valueLabel) . '</dd>';
191279
}
192280

193-
$formatted .= '</ul></div>';
281+
$formatted .= '</dl></div>';
194282

195283
return $formatted;
196284
}

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ After installing the module, when viewing a system configuration field, an alert
4949

5050
The icon is only shown when the value is overridden at a more specific scope than the current one – that is, if viewing at the default scope, overrides at the website or store view level are shown, but if viewing at the website level, only overrides below the currently selected website are shown.
5151

52-
Along with the alert message, a detailed list of the exact scope(s) that override the value, with links directly to the store config for the current section at those scopes.
52+
Along with the alert message, a detailed list of the exact scope(s) that override the value, with links directly to the store config for the current section at those scopes. Clicking an override hint row arrow will expand the row to also show the field's value at that scope.
5353

54-
![Screenshot of system config scope hints module](https://ericisaweso.me/images/magento2-configscopehints-v3.png)
54+
![Screenshot of system config scope hints module](https://ericisaweso.me/images/magento2-configscopehints-v3.1.png)
5555

5656
## Compatibility and Technical Notes
5757

etc/module.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
2-
<module name="EW_ConfigScopeHints" setup_version="3.0.0">
2+
<module name="EW_ConfigScopeHints" setup_version="3.1.0">
33
<sequence>
44
<module name="Magento_Config"/>
55
</sequence>

view/adminhtml/layout/adminhtml_system_config_edit.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
33
<head>
44
<css src="EW_ConfigScopeHints::css/configscopehints.css"/>
5+
<script src="EW_ConfigScopeHints::js/configscopehints.js"/>
56
</head>
67
</page>

view/adminhtml/web/css/configscopehints.less

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,42 @@
1111
}
1212
}
1313

14-
.overridden-hint-list {
15-
list-style-position: inside;
14+
.override-scope {
15+
cursor: pointer;
16+
line-height: 40px; //improve touch experience
17+
position: relative;
18+
padding-right: 4rem; //prevent arrow from overlapping text
19+
20+
&:before {
21+
font-family: "Admin Icons";
22+
font-style: normal;
23+
content: '\e616';
24+
font-size: 1.8rem;
25+
position: absolute;
26+
right: 1.3rem;
27+
}
28+
29+
&.open {
30+
border-bottom: none; //remove native accordion border so following .override-value can have it
31+
32+
&:before {
33+
content: '\e615'; //set icon to close arrow
34+
}
35+
}
36+
}
37+
38+
.override-value {
39+
&.visible {
40+
display: block;
41+
}
42+
43+
span.override-value-hint-label {
44+
display: block;
45+
margin-bottom: 1rem;
46+
}
47+
48+
//move native accordion left whitespace from margin to padding
49+
margin-left: 0;
50+
padding-left: 40px;
1651
}
1752
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require([
2+
'jquery'
3+
], function ($) {
4+
$(document).ready(function() {
5+
$('.overridden-hint-list').on('click', '.override-scope', function() {
6+
$(this).toggleClass('open').next('.override-value').toggleClass('visible');
7+
});
8+
});
9+
});

0 commit comments

Comments
 (0)