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(\Drupal\block\Entity\BlockBlock $block, $operation, \Drupal\Core\Session\AccountInterfaceAccountInterface $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 (\Drupal::currentUser$block_entity->createDuplicate()->hasPermission>access('administer blocks''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;
}
}
}