Score:0

Custom block plugin: Give access to edit form

in flag

I am creating a custom block plugin with custom configuration options (blockForm()).

I would like to allow content editors to edit the configuration of this block, without giving them "administer blocks" permission. Also, I don't want them to create new block instances..

I would like to avoid installing an additional module like block_permissions, as this would introduce a wide range of settings or permissions for all blocks, not just the specific block I am creating.

I also don't want this to be a "content" block with "fields". I want to know how this can be done with a custom plugin-based block.

EDIT / UPDATE
(based on responses and discussion)

The existing response is spot-on, if your goal is as described in the question.

However, the entire premise has one problem: The entire block placement will be exported to config, and overwritten on deployment. This means any user-controlled settings will be destroyed on next release. So this is not sustainable. A content block entity can be used to allow a proper split.

Score:1
cn flag

If a user doesn't have the admin permission for an entity type this doesn't mean you can't allow specific operations on specific existing entities.

For example, content editors you gave permission to administer content are also allowed to update blocks of your custom block plugin:

use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;

/**
 * Implements hook_ENTITY_TYPE_access() for entity type "block".
 */
function mymodule_block_access(Block $block, $operation, AccountInterface $account) {
  if ($operation == 'update'
    && $block->getPluginId() == 'custom_block_plugin_id'
    // The last condition is only needed if the trick from below is used with
    // `$block_entity->createDuplicate()->access('update')`.
    && $block->id() !== NULL
  ) {
    return AccessResult::allowedIfHasPermission($account, 'administer nodes');
  }
  return AccessResult::neutral();
}

You also need to alter the form not to redirect on submit to a page the user has no access to. Or link to the edit form with a destination query string pointing to a different page.


(EDIT by @donquixote)

If you want to restrict access to other elements on the page, you could do it like below.
This is a bit fragile, because it assumes a very specific structure of the form.

/**
 * Implements hook_form_FORM_ID_alter() for 'block_form'.
 */
function mymodule_form_block_form_alter(array &$form, FormStateInterface $form_state, string $form_id) {
  $form_object = $form_state->getFormObject();
  if (!$form_object instanceof BlockForm) {
    return;
  }
  /** @var Block $block_entity */
  $block_entity = $form_object->getEntity();
  if ($block_entity->getPluginId() !== 'custom_block_plugin_id') {
    return;
  }
  if ($block_entity->createDuplicate()->access('update')) {
    // The user has general access to update this block.
    return;
  }
  // The user was only given access via mymodule_block_access().
  // They should only edit the specific plugin settings, not change where the
  // block is placed.
  foreach (['visibility', 'id', 'weight', 'region'] as $key) {
    if (isset($form[$key])) {
      $form[$key]['#access'] = FALSE;
    }
  }
  foreach (['label', 'label_display'] as $key) {
    if (isset($form['settings'][$key])) {
      $form['settings'][$key]['#access'] = FALSE;
    }
  }
  foreach (['delete'] as $key) {
    if (isset($form['actions'][$key])) {
      $form['actions'][$key]['#access'] = FALSE;
    }
  }
}
in flag
Thanks, this works! However, now I also need `hook_form_block_form_alter()` to set `'#access' => FALSE` on all the elements I don't want them to edit. Which is most of the rest of this form.
in flag
The destination is fine already, because users will access this form through the contextual links.
in flag
Oops, I was assuming you would have to confirm the edits..
in flag
Let me know if the edit is ok, or feel free to modify it. I can also make it a separate answer, but I don't want to "steal" the "accepted answer" checkmark.
4uk4 avatar
cn flag
You can, if you want, alter the form elements in the custom block plugin directly by overriding buildConfigurationForm().
in flag
This only covers the elements within the `$form['settings']` subform. Visibility settings, region placement, and the option to remove the block still need to be dealt with in `hook_form_alter()`.
in flag
Btw the `\Drupal::currentUser()->hasPermission('administer blocks')` is a bit awkward. Instead, I would like to check whether the user _would_ have block update access if it were not for the custom `hook_block_access()`.
4uk4 avatar
cn flag
OK, so the form alter hook seems to be the easiest way to control all form elements. If you need to make more adjustments at one point it could be necessary to extend the block form. I've seen the layout builder has a lot of different block forms for different tasks.
4uk4 avatar
cn flag
I think this is OK, the block config entity is using this simplified one for all permission defined in the entity type.
in flag
The 'administer blocks' might not be suitable when using an additional module for more fine-grained access control. I added a trick in the code with `$block_entity->createDuplicate()->access('update')`. This seems to do the trick, and will also circumvent the cache, I hope..
in flag
Is the access result cached per entity, or do we need to add another cache tag?
in flag
I notice another problem with this: All of this is exported to config, so any user changes would be overwritten when config is imported on the next deployment. Now I am almost convinced that this is the wrong way to do things, and I really should use a content block..
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.