diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 584feb7c53..ae3869a02c 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -85,19 +85,7 @@ public function get($key = '') { if (!isset($this->overriddenData)) { $this->setOverriddenData(); } - if (empty($key)) { - return $this->overriddenData; - } - else { - $parts = explode('.', $key); - if (count($parts) == 1) { - return isset($this->overriddenData[$key]) ? $this->overriddenData[$key] : NULL; - } - else { - $value = NestedArray::getValue($this->overriddenData, $parts, $key_exists); - return $key_exists ? $value : NULL; - } - } + return self::extractKeyedData($this->overriddenData, $key); } /** @@ -141,6 +129,27 @@ public function setModuleOverride(array $data) { return $this; } + /** + * Returns overrides that apply to this configuration. + * + * Simply comparing the the data from getOriginal() with and without overrides + * does not help to detect overrides if the override happens to have the same + * value as the original data. + * + * @param string $key + * A string that maps to a key within the configuration data. + * + * @return mixed + * The value of the override or NULL if not overwritten. + */ + public function getOverrides($key = '') { + $overrides = $this->mergeOverridesWithData([]); + if (empty($overrides)) { + return NULL; + } + return self::extractKeyedData($overrides, $key); + } + /** * Sets the current data for this configuration object. * @@ -153,13 +162,7 @@ public function setModuleOverride(array $data) { * The configuration object. */ protected function setOverriddenData() { - $this->overriddenData = $this->data; - if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) { - $this->overriddenData = NestedArray::mergeDeepArray([$this->overriddenData, $this->moduleOverrides], TRUE); - } - if (isset($this->settingsOverrides) && is_array($this->settingsOverrides)) { - $this->overriddenData = NestedArray::mergeDeepArray([$this->overriddenData, $this->settingsOverrides], TRUE); - } + $this->overriddenData = $this->mergeOverridesWithData($this->data); return $this; } @@ -280,27 +283,29 @@ public function getOriginal($key = '', $apply_overrides = TRUE) { $original_data = $this->originalData; if ($apply_overrides) { // Apply overrides. - if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) { - $original_data = NestedArray::mergeDeepArray([$original_data, $this->moduleOverrides], TRUE); - } - if (isset($this->settingsOverrides) && is_array($this->settingsOverrides)) { - $original_data = NestedArray::mergeDeepArray([$original_data, $this->settingsOverrides], TRUE); - } + $original_data = $this->mergeOverridesWithData($original_data); } - if (empty($key)) { - return $original_data; + return self::extractKeyedData($original_data, $key); + } + + /** + * Merge module and settings overrides with the provided data. + * + * @param array $data + * The data to merge the overrides with. + * + * @return array + * The overwritten data, unchanged if no overrides apply. + */ + protected function mergeOverridesWithData(array $data) { + if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) { + $data = NestedArray::mergeDeepArray([$data, $this->moduleOverrides], TRUE); } - else { - $parts = explode('.', $key); - if (count($parts) == 1) { - return isset($original_data[$key]) ? $original_data[$key] : NULL; - } - else { - $value = NestedArray::getValue($original_data, $parts, $key_exists); - return $key_exists ? $value : NULL; - } + if (isset($this->settingsOverrides) && is_array($this->settingsOverrides)) { + $data = NestedArray::mergeDeepArray([$data, $this->settingsOverrides], TRUE); } + return $data; } } diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php index 4f445ea21c..64f348a9a7 100644 --- a/core/lib/Drupal/Core/Config/ConfigBase.php +++ b/core/lib/Drupal/Core/Config/ConfigBase.php @@ -129,19 +129,7 @@ public static function validateName($name) { * The data that was requested. */ public function get($key = '') { - if (empty($key)) { - return $this->data; - } - else { - $parts = explode('.', $key); - if (count($parts) == 1) { - return isset($this->data[$key]) ? $this->data[$key] : NULL; - } - else { - $value = NestedArray::getValue($this->data, $parts, $key_exists); - return $key_exists ? $value : NULL; - } - } + return self::extractKeyedData($this->data, $key); } /** @@ -295,4 +283,33 @@ protected function castSafeStrings($data) { return $data; } + /** + * Extract the value of a sub key in the data. + * + * @param array $data + * The configuration data. + * @param string $key + * A string that maps to a key within the configuration data. + * + * @return mixed + * The data at the key or NULL if it is not set. + * + * @see \Drupal\Core\Config\ConfigBase::get + */ + protected static function extractKeyedData(array $data, $key) { + if (empty($key)) { + return $data; + } + else { + $parts = explode('.', $key); + if (count($parts) == 1) { + return isset($data[$key]) ? $data[$key] : NULL; + } + else { + $value = NestedArray::getValue($data, $parts, $key_exists); + return $key_exists ? $value : NULL; + } + } + } + } diff --git a/core/modules/settings_tray/js/settings_tray.es6.js b/core/modules/settings_tray/js/settings_tray.es6.js index a680115490..c182a873ed 100644 --- a/core/modules/settings_tray/js/settings_tray.es6.js +++ b/core/modules/settings_tray/js/settings_tray.es6.js @@ -5,7 +5,7 @@ * @private */ -(function ($, Drupal) { +(function ($, Drupal, drupalSettings) { const blockConfigureSelector = '[data-settings-tray-edit]'; const toggleEditSelector = '[data-drupal-settingstray="toggle"]'; const itemsToToggleSelector = '[data-off-canvas-main-canvas], #toolbar-bar, [data-drupal-settingstray="editable"] a, [data-drupal-settingstray="editable"] button'; @@ -164,6 +164,17 @@ if (!('dialogOptions' in instance.options.data)) { instance.options.data.dialogOptions = {}; } + + if (drupalSettings.hasOwnProperty('settings_tray') && drupalSettings.settings_tray.hasOwnProperty('overridden_blocks')) { + Object.entries(drupalSettings.settings_tray.overridden_blocks).forEach( + ([blockId, url]) => { + if (instance.options.url.includes(`/${blockId}/`)) { + instance.options.url = url; + } + }, + ); + } + instance.options.data.dialogOptions.settingsTrayActiveEditableId = $(instance.element).parents('.settings-tray-editable').attr('id'); instance.progress = { type: 'fullscreen' }; }); @@ -253,4 +264,4 @@ } }, }); -}(jQuery, Drupal)); +}(jQuery, Drupal, drupalSettings)); diff --git a/core/modules/settings_tray/js/settings_tray.js b/core/modules/settings_tray/js/settings_tray.js index 78c5b60382..98e81f3d93 100644 --- a/core/modules/settings_tray/js/settings_tray.js +++ b/core/modules/settings_tray/js/settings_tray.js @@ -4,8 +4,9 @@ * https://www.drupal.org/node/2815083 * @preserve **/ +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); -(function ($, Drupal) { +(function ($, Drupal, drupalSettings) { var blockConfigureSelector = '[data-settings-tray-edit]'; var toggleEditSelector = '[data-drupal-settingstray="toggle"]'; var itemsToToggleSelector = '[data-off-canvas-main-canvas], #toolbar-bar, [data-drupal-settingstray="editable"] a, [data-drupal-settingstray="editable"] button'; @@ -100,6 +101,19 @@ if (!('dialogOptions' in instance.options.data)) { instance.options.data.dialogOptions = {}; } + + if (drupalSettings.hasOwnProperty('settings_tray') && drupalSettings.settings_tray.hasOwnProperty('overridden_blocks')) { + Object.entries(drupalSettings.settings_tray.overridden_blocks).forEach(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + blockId = _ref2[0], + url = _ref2[1]; + + if (instance.options.url.includes('/' + blockId + '/')) { + instance.options.url = url; + } + }); + } + instance.options.data.dialogOptions.settingsTrayActiveEditableId = $(instance.element).parents('.settings-tray-editable').attr('id'); instance.progress = { type: 'fullscreen' }; }); @@ -153,4 +167,4 @@ } } }); -})(jQuery, Drupal); \ No newline at end of file +})(jQuery, Drupal, drupalSettings); \ No newline at end of file diff --git a/core/modules/settings_tray/settings_tray.libraries.yml b/core/modules/settings_tray/settings_tray.libraries.yml index de5c82c78d..11b55a478f 100644 --- a/core/modules/settings_tray/settings_tray.libraries.yml +++ b/core/modules/settings_tray/settings_tray.libraries.yml @@ -17,3 +17,4 @@ drupal.settings_tray: - core/drupal - core/jquery.once - core/drupal.ajax + - core/drupalSettings diff --git a/core/modules/settings_tray/settings_tray.module b/core/modules/settings_tray/settings_tray.module index af04450459..821f62c19c 100644 --- a/core/modules/settings_tray/settings_tray.module +++ b/core/modules/settings_tray/settings_tray.module @@ -10,6 +10,9 @@ use Drupal\settings_tray\Block\BlockEntityOffCanvasForm; use Drupal\settings_tray\Form\SystemBrandingOffCanvasForm; use Drupal\settings_tray\Form\SystemMenuOffCanvasForm; +use Drupal\block\Entity\Block; +use Drupal\Core\Url; +use Drupal\Core\EventSubscriber\MainContentViewSubscriber; /** * Implements hook_help(). @@ -48,6 +51,21 @@ function settings_tray_contextual_links_view_alter(&$element, $items) { } } +/** + * Checks if a block has overrides. + * + * @param string $block_id + * The ID of the block to check for overrides. + * + * @return bool + * TRUE if the block has overrides otherwise FALSE. + */ +function _settings_tray_has_block_overrides($block_id) { + $block = Block::load($block_id); + $overrides = \Drupal::config($block->getEntityType()->getConfigPrefix() . '.' . $block->id())->getOverrides(); + return !empty($overrides); +} + /** * Implements hook_block_view_alter(). */ @@ -58,6 +76,27 @@ function settings_tray_block_view_alter(array &$build) { $build['#contextual_links']['settings_tray'] = [ 'route_parameters' => [], ]; + $block_id = $build['#block']->id(); + // If a block currently has configuration overrides it cannot be edited in the + // Settings Tray form. + if (_settings_tray_has_block_overrides($block_id)) { + $url = Url::fromRoute('settings_tray.overridden_block_warning') + ->setRouteParameter('block', $block_id) + ->setOptions( + [ + 'query' => [ + MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog.off_canvas', + ] + \Drupal::destination()->getAsArray(), + ] + ); + // Store the URL to the overridden notice for this block in drupalSettings. + // This will be used in the Javascript function prepareAjaxLinks() to + // replace the URL to the Settings Tray edit form. We cannot use + // settings_tray_contextual_links_view_alter() to alter the URL because the + // contextual links will not be rebuilt for every context that could have + // configuration overrides. + $build['#attached']['drupalSettings']['settings_tray']['overridden_blocks'][$block_id] = $url->toString(); + } } /** diff --git a/core/modules/settings_tray/settings_tray.routing.yml b/core/modules/settings_tray/settings_tray.routing.yml index 01109e4c79..c2f4f7c140 100644 --- a/core/modules/settings_tray/settings_tray.routing.yml +++ b/core/modules/settings_tray/settings_tray.routing.yml @@ -6,3 +6,10 @@ entity.block.off_canvas_form: requirements: _permission: 'administer blocks' _access_block_plugin_has_settings_tray_form: 'TRUE' +settings_tray.overridden_block_warning: + path: '/admin/settings-tray-overridden/{block}' + defaults: + _controller: '\Drupal\settings_tray\Controller\OverriddenBlockConfig::overrideNotice' + _title_callback: '\Drupal\settings_tray\Block\BlockEntityOffCanvasForm::title' + requirements: + _permission: 'administer blocks' diff --git a/core/modules/settings_tray/src/Controller/OverriddenBlockConfig.php b/core/modules/settings_tray/src/Controller/OverriddenBlockConfig.php new file mode 100644 index 0000000000..f113f82070 --- /dev/null +++ b/core/modules/settings_tray/src/Controller/OverriddenBlockConfig.php @@ -0,0 +1,48 @@ + [ + '#type' => 'markup', + '#markup' => '

' . $this->t('This block cannot be edited in the Settings Tray form because it has configuration overrides in effect.') . '

', + ], + 'link' => [ + '#type' => 'link', + '#title' => $this->t('Edit Block'), + '#url' => Url::fromRoute('entity.block.edit_form') + ->setRouteParameter('block', $block->id()) + ->setOption('query', ['destination' => \Drupal::request()->get('destination')]), + ], + ]; + + } + +} diff --git a/core/tests/Drupal/Tests/Core/Config/ConfigTest.php b/core/tests/Drupal/Tests/Core/Config/ConfigTest.php index 41c3e93c99..8633e59fed 100644 --- a/core/tests/Drupal/Tests/Core/Config/ConfigTest.php +++ b/core/tests/Drupal/Tests/Core/Config/ConfigTest.php @@ -173,6 +173,7 @@ public function testSaveExisting($data) { * @covers ::setModuleOverride * @covers ::setSettingsOverride * @covers ::getOriginal + * @covers ::getOverrides * @dataProvider overrideDataProvider */ public function testOverrideData($data, $module_data, $setting_data) { @@ -184,26 +185,37 @@ public function testOverrideData($data, $module_data, $setting_data) { // Save so that the original data is stored. $this->config->save(); + $this->assertNull($this->config->getOverrides()); // Set module override data and check value before and after save. $this->config->setModuleOverride($module_data); $this->assertConfigDataEquals($module_data); + $this->assertEquals($module_data, $this->config->getOverrides()); $this->config->save(); $this->assertConfigDataEquals($module_data); + $this->assertEquals($module_data, $this->config->getOverrides()); + + // Reset the module overrides. + $this->config->setModuleOverride([]); + $this->assertNull($this->config->getOverrides()); // Set setting override data and check value before and after save. $this->config->setSettingsOverride($setting_data); $this->assertConfigDataEquals($setting_data); + $this->assertEquals($setting_data, $this->config->getOverrides()); $this->config->save(); $this->assertConfigDataEquals($setting_data); + $this->assertEquals($setting_data, $this->config->getOverrides()); // Set module overrides again to ensure override order is correct. $this->config->setModuleOverride($module_data); // Setting data should be overriding module data. $this->assertConfigDataEquals($setting_data); + $this->assertEquals($setting_data, $this->config->getOverrides()); $this->config->save(); $this->assertConfigDataEquals($setting_data); + $this->assertEquals($setting_data, $this->config->getOverrides()); // Check original data has not changed. $this->assertOriginalConfigDataEquals($data, FALSE); @@ -215,7 +227,17 @@ public function testOverrideData($data, $module_data, $setting_data) { foreach ($setting_data as $key => $value) { $config_value = $this->config->getOriginal($key); $this->assertEquals($value, $config_value); + $this->assertEquals($value, $this->config->getOverrides($key)); } + + // Check that the overrides can be completely reset. + $this->config->setModuleOverride([]); + $this->config->setSettingsOverride([]); + $this->assertConfigDataEquals($data); + $this->assertNull($this->config->getOverrides()); + $this->config->save(); + $this->assertConfigDataEquals($data); + $this->assertNull($this->config->getOverrides()); } /**