If you currently don't use Config Readonly to protect config changes on Production, then either get on it or get outta here and stop wasting my bandwidth, jerk.
Blocking clients from blowing up their site is always something I can get behind, but there's always exceptions in which you want them to have control over certain settings or configurations.
Usually a combination of hook_config_readonly_whitelist_patterns() and the Config Ignore module work nicely for this as you can whitelist config that can be changed on production and also ignore it from being imported on deployments.
Enter the block problem.
Lets say you've got some nice configurable blocks on the site using "block layout", but you've locked config so the client doesn't go and remove the navigation or their footer (I've seen it). Unfortunately this means the client can only edit the block by finding it in the library, and can't edit anything on the Block Layout config.
You can't whitelist/ignore just a specific block type since all blocks ID's are block.block.<machine_name> so if there are multiple instance of the block type, or they want to add/remove a block of the type, your stuck.
The Solution?
First, we change the machine name of the block when adding it to the block layout to use the bundle of the custom block.
php
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function my_module_form_block_form_alter(&$form, FormStateInterface $form_state) {
// Check for a block content block.
if (isset($form['settings']['provider']['#value']) && $form['settings']['provider']['#value'] === 'block_content') {
/** @var \Drupal\block\BlockInterface $block */
$block = $form_state->getFormObject()->getEntity();
// Can only affect new blocks.
if ($block->isNew()) {
$block_config = $block->getPlugin()->getConfiguration();
$uuid = str_replace('block_content:', '', $block_config['id']);
$block_content_storage = \Drupal::entityTypeManager()->getStorage('block_content');
// Changes machine name for alert banner blocks to allow ignoring config.
if ($block_content = $block_content_storage->loadByProperties(['uuid' => $uuid])) {
$form['id']['#default_value'] = reset($block_content)->bundle() . '.' . $form['id']['#default_value'];
}
}
}
}
Now our block will be saved as block.block.<bundle>.<machine_name> in config. This allows a pattern to use for Config Read-only and Config Ignore
Bonus
Unfortunately, the above does not cover unlocking the moving of blocks without the layout as that config is tied to blocks as well. So in this case, we allowed block.block.* as the whitelisted config, and instead used our pattern above to allow only our blocks.
First, add the following to your snipped above:
php
// Add validation for the config read only blocks.
$form['#validate'][] = '_my_module_config_readonly_blocks';
Then we validate blocks that do not match our pattern:
php
/**
* Validate for when saving config for alerts.
*
* @param array $form
* The current form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
function _my_module_config_readonly_blocks(array $form, FormStateInterface $form_state) {
// We only do this if config read-only is on.
if (!Settings::get('config_readonly')) {
return;
}
// Don't allow changes unless an alert block.
$id = $form_state->getValue('id', FALSE);
if (strpos($id, 'alert_banner.') !== 0) {
$form_state->setErrorByName(NULL, t('This configuration form cannot be saved because the configuration active store is read-only.'));
}
}