Add custom Contextual Links to Drupal node teaser pages

I recently wanted to have a quicker way to unpublish my nodes in one of my Drupal project with Contextual Links.

This mini code snippet will show you how to create a custom module to add links to the contextual shortcut (as seen in screenshot).

The code will also have a custom access check to only show the link for certain content type and custom permission.

<?php
/**
* Implements hook_permission().
*/
function YOUR_MODULE_permission() {
  return array(
    'YOUR MODULE article unpublish articles' => array(
      'title' => t('YOUR MODULE Article Unpublish'),
    ),
  );
}

/**
* Implements hook_menu().
*/
function YOUR_MODULE_menu() {
  $items = array();

  $items['node/%node/article/unpublish'] = array(
    'title' => 'Unpublish',
    'access callback' => '_YOUR_MODULE_article_unpublish_access_check',
    'access arguments' => array(1),
    'page callback' => '_YOUR_MODULE_article_unpublish',
    'page arguments' => array(1),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
  );

  return $items;
}

/**
* Custom access check for node/%node/article/unpublish menu link.
*
* @param object $node Node object from menu argument.
* @return boolean TRUE if has access, FALSE otherwise.
*/
function _YOUR_MODULE_article_unpublish_access_check($node) {
  if ($node->type == 'article' && user_access('YOUR MODULE unpublish articles')) {
    return TRUE;
  }

  return FALSE;
}

/**
* Callback action to unpublish article.
*
* @param object $node Node object.
*/
function _YOUR_MODULE_article_unpublish($node) {
  node_object_prepare($node);
  $node->status = 0;
  node_save($node);

  watchdog('YOUR_MODULE', __FUNCTION__ . ' -- Unpublished article "%title" (NID: %nid).', array('%title' => $node->title, '%nid' => $node->nid), WATCHDOG_NOTICE);

  $destination = drupal_get_destination();
  $destination = isset($destination['destination']) ? $destination['destination'] : '<front>';
  drupal_goto($destination);
}

12 comments for 'Add custom Contextual Links to Drupal node teaser pages'

larowlan's picture

This code snippet is csrf vulnerable. You should protect the unpublish callback with a token. Otherwise an attacker could easily unpublish an article eg by using the callback as an img src etc.

admin's picture

Could you demonstrate how a CSRF can be done with the contextual link above?

---

Here's another snippet for using Drupal's confirm_form().

function YOUR_MODULE_menu() {
  $items = array();

  $items['node/%node/article/unpublish'] = array(
    'title' => 'Unpublish',
    'access callback' => '_YOUR_MODULE_article_unpublish_access_check',
    'access arguments' => array(1),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_YOUR_MODULE_article_unpublish', 1),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
  );

  return $items;
}

function _YOUR_MODULE_article_unpublish($form, &$form_state, $node) {
  $form['#node'] = $node;
  $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
  return confirm_form(
    $form,
    t('Are you sure you want to unpublish %title?', array('%title' => $node->title)),
    'node/' . $node->nid,
    '',
    t('Unpublish'),
    t('Cancel')
  );
}

function _YOUR_MODULE_article_unpublish_submit($form, &$form_state) {
  if ($form_state['values']['confirm']) {
    $node = node_load($form_state['values']['nid']);
    $node->status = 0;
    node_save($node);

    watchdog('YOUR_MODULE', __FUNCTION__ . ' -- Unpublished article "%title" (NID: %nid).', array('%title' => $node->title, '%nid' => $node->nid), WATCHDOG_NOTICE);
  }

  $destination = drupal_get_destination();
  $destination = isset($destination['destination']) ? $destination['destination'] : '<front>';
  $form_state['redirect'] = $destination;
}

admin's picture

Note that this is not a module to be released, it was published under Code Snippets section of mine so that we can refer back next time on how to easily add Contextual Links.

Moreover, there's another method I've published above in comment that uses confirm_form() that will address the security issue.

larowlan's picture

Yeah, the confirm form is the preferred method, either that or append a token to your url and then check it is valid in the callback or an access callback before doing anything permanent. You'd need a hook_contextual_links_alter() to add the token to the url.

Mark's picture

I am not sure why am getting the item array as null in YOUR_MODULE_menu function though I have provided the correct access callback. Can you suggest what can be the problem

admin's picture

Hmm can you pinpoint which part am I missing specifically?

I have used this code reference before and it works on my projects.

trucos geometry dash's picture

Hmm...am I missing something or you haven't added "build" for contextual links in your code above?

Add new comment