7

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.

1
  • 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, 2015 at 23:14

4 Answers 4

16

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.

2
  • 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, 2016 at 19:25
  • 1
    Take care that this hook is not called for the admin user drupal.org/node/2717901 Sep 8, 2017 at 8:40
8

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.

8
  • 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, 2015 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, 2015 at 10:36
  • Also, declaring hook_node_access() hides the Delete link in all Node / Content Types.
    – iainH
    Dec 31, 2015 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, 2015 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, 2016 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.

1
  • 1
    Based on the question you made, the access_hook approach is much more suitable for most cases.
    – ssibal
    Jul 24, 2019 at 12:15
1

For Drupal 8

There is a module prevent node delete

https://www.drupal.org/project/prevent_node_delete

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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