5

I want to make some checks and, under certain circumstances, prevent a node from being deleted.

My module makes checks in HOOK_form_BASE_FORM_ID_alter() when the node is being created, but I cannot see where I can get control when the node is being deleted.

  • I don't have time for a full answer, or a working example in Drupal 8, but the node access system is essentially the same as Drupal 7; just some small changes. – mpdonadio Dec 30 '15 at 23:14
13

Using hook_node_access() is one option:

use Drupal\Core\Entity\EntityInterface; 
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;

...

function MYMODULE_node_access(EntityInterface $entity, $operation, AccountInterface $account) {
  if ($operation == 'delete') {
    if (some_access_function($entity, $account)) {
      return AccessResult::allowed();
    }
    else {
      return AccessResult::forbidden();
    }
  }

  return AccessResult::neutral();
}

This method has the advantage of preventing deletion through other methods (e.g. REST) at the same time, and will automatically remove the delete link and tab from the node form anyway.

  • You were a minute faster :) – Berdir Dec 30 '15 at 23:14
  • Yeah but I bet you didn't have to check out the source first to answer @Berdir ;) – Clive Dec 30 '15 at 23:15
  • 3
    Actually, that's why you were faster, I checked the argument order in the hook documentation :) – Berdir Dec 30 '15 at 23:16
  • Thanks @Clive for your helpful and concise answer which I was able to try straight away (changing EntityInterface to NodeInterface. Actually though, per my comments to @Berdir's answer, it didn't turn out to be exactly what I wanted. – iainH Jan 1 '16 at 19:25
  • Take care that this hook is not called for the admin user drupal.org/node/2717901 – Juanmi Sep 8 '17 at 8:40
7

What kind of check?

What's easy is to prevent delete access. Implement the hook hook_node_access(NodeInterface $node, $operation, AccountInterface $account) and return AccessResult::forbidden() if your condition matches for $operation delete.

That doesn't prevent deletion through the API but it will automatically hide any delete button/link/action in the UI and users won't be able to delete that node.

  • Yes, the devil's in the "kind of check" detail :-) I don't think that this is exactly what I want as just having hook_node_access() declaration in my module hides the Delete link. What I am wanting to do is to respond to the user clicking Delete by making my checks and then allowing or refusing the deletion. Specifically my checks are where used / referential integrity: i.e. I will refuse the deletion if any other node has a reference to the one the user is attempting to delete. – iainH Dec 31 '15 at 10:30
  • "hook_node_access() declaration in my module hides the Delete link". That shouldn't happen. especially not if it's really hook and not $yourmodule. You can do those kind of checks in there and then return AccessResult::forbidden() or AccessResult::neutral(). That is a valid thing to do, just keep in mind that it will be checked quite frequently, e.g. for every visible node on admin/content. – Berdir Dec 31 '15 at 10:36
  • Also, declaring hook_node_access() hides the Delete link in all Node / Content Types. – iainH Dec 31 '15 at 10:37
  • 1
    If you really want to limit to the delete form only, you can also mess with the confirm form there. node has that just like node type, just implement hook_form_alter() in your module, check the $form_id and then check on that and the node $form_state. But keep in mind that there at least two forms, the single confirm form and then mass-delete bulk action on admin/content. – Berdir Dec 31 '15 at 10:38
  • 1
    @iainH It'll be MYMODULE_form_node_confirm_form_alter, use $form['#validate'][] = 'foo';, and in that validate handler check for a problem and $form_state->setError($form, 'Message'); to stop the submission and inform the user – Clive Jan 1 '16 at 19:49
5

This does what I want.

The Agenda Node cannot be deleted if there are any Meeting Nodes that refer to it.

Ideally, I’d have preferred that the validation handler would be called when the user clicked the Delete on the primary tasks tabs whereas it is called on the confirmation form. Good enough, though.

use Drupal\Core\Form\FormStateInterface;

function agenda_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  switch ($form_id) {
    // ...
    case "node_agenda_delete_form":
      $form['#validate'][] = '_agenda_delete_validation';
      break;
    // ...
  }
}

function _agenda_delete_validation(&$form, FormStateInterface $form_state) {
  $agenda_node = $form_state->getFormObject()->getEntity();
  $agenda_name = $agenda_node->getTitle();
  $agenda_node_id = $agenda_node->id();
  $query = \Drupal::entityQuery('node')
    ->condition('type', 'meeting')
    ->condition('field_agenda', $agenda_node_id);
  $nids = $query->execute();
  $count_nids = count($nids);
  if ($count_nids > 0) {
    $msg_stem = ($count_nids == 1) ? 'There is one Meeting that references "' : 'There are '.$count_nids.' Meetings that reference "';
    $form_state->setError($form, $msg_stem.$agenda_name.'". It cannot be deleted yet.' );
  }
}

With many thanks to @Clive and @Berdir who gave great answers that I was able to follow up.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.