How to Write a Custom Ctools Access Plugin Based on the Value of a Boolean Field

I had the delightful task at work of determining the visibility of a view (in a node variant) based on the value of a boolean field. But it got better! Said boolean field was not on the node that was being viewed, it was on a node that referenced the node being viewed. But wait! It gets better! The referenced node was an organic group, and those references work differently then a usual entity reference! Confused yet? The actual purpose of the exercise was to allow a content manager to control the widget without having to touch the actual panel variant. This prevents confusion on their part, and also the possibility of the page layout being accidentally destroyed. If they want the widget on their page, they check a box.If they don't want it, leave the box unchecked. Simple enough.

First things first: you have to let ctools know that there is a plugin to use. In your custom module (or in my case, in the relevant feature) .module, you need something like this:

1
2
3
4
5
6
7
8
9
10
/**  
* Implements hook_ctools_plugin_directory().  
*  
* It simply tells panels where to look for the .inc file that  
* defines various args, contexts and content_types.  
*/ 
function my_feature_ctools_plugin_directory($module, $plugin) {  
  if ($module == 'ctools' && !empty($plugin)) {    
    return "plugins/$plugin";  } 
  } 

Then in your feature (or custom module), create the directory, so you have a my_feature/plugins/access folder structure.

In the access folder, create a new .inc file. For the purposes of this exercise, the field we are using to determine visibility will be called field_widget, so I would call my file field_widget.inc.

You have to start by defining the plugin, so:

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * Plugins are described by creating a $plugin array which will
 * be used by the system that includes the file.
 */
$plugin = array(
  'title' => t('Node: Widget'),
  'description' => t('Only displays this pane if the Widget field on the related Home Page for this Organic Group is set to On.'),
  'callback' => 'my_feature_field_widget_ctools_access_check',
  'default' => array('field_widget' => 1),
  'summary' => 'my_feature_field_widget_ctools_access_summary',
  'required context' => new ctools_context_required(t('Node'), 'node'),
);

Now write the callback:

1
2
3
4
5
6
7
8
9
10
11
/**
 * Custom callback defined by 'callback' in the $plugin array.
 *
 * Check for access.
 */
function my_feature_field_widget_ctools_access_check($conf, $context) {
 
  // If for some unknown reason that $context isn't set, return false.
  if (empty($context) || empty($context->data)) {
    return FALSE;
  }

Because the field is *not* on the node that is currently being viewed, we have to identify the correct home page node.
1
2
3
4
5
6
  // Identify the home page node for the current organic group.
  $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', 'node')
      ->entityCondition('bundle', 'division_home')
      ->fieldCondition('og_division_home_division_ref', 'target_id', $context->data->nid);
    $result = $query->execute();

Now we have to load the node, to access the field_widget value.

1
2
    $home_page_nid = current($result['node'])->nid;
    $home_page_node = node_load($home_page_nid);

Here is where we check the value of field_widget. Being a boolean, if the value is 0, that means the boolean is not checked, and we want to hide the pane.

1
2
3
4
5
6
7
// If the home page widget field is not checked, hide the pane.
  if (!$home_page_node->field_widget[LANGUAGE_NONE][0]['value']) {
  }
 
  //Otherwise, show the pane.
  return TRUE;
}

Here is the whole kit and caboodle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
 * Plugins are described by creating a $plugin array which will
 * be used by the system that includes the file.
 */
$plugin = array(
  'title' => t('Node: Widget'),
 
  'description' => t('Only displays this pane if the Widget field on
the related Home Page for this Organic Group is set to On.'),
  'callback' => 'my_feature_field_widget_ctools_access_check',
  'default' => array('field_widget' => 1),
  'summary' => 'my_feature_field_widget_ctools_access_summary',
  'required context' => new ctools_context_required(t('Node'), 'node'),
);
 
/**
 * Custom callback defined by 'callback' in the $plugin array.
 *
 * Check for access.
 */
function my_feature_field_widget_ctools_access_check($conf, $context) {
 
  // If for some unknown reason that $context isn't set, return false.
  if (empty($context) || empty($context->data)) {
    return FALSE;
  }
 
 
 
  // Identify the home page node for the current organic group.
  $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', 'node')
      ->entityCondition('bundle', 'division_home')
      ->fieldCondition('og_division_home_division_ref', 'target_id', $context->data->nid);
    $result = $query->execute();
 
    $home_page_nid = current($result['node'])->nid;
    $home_page_node = node_load($home_page_nid);
 
 
 
// If the home page widget field is not checked, hide the pane.
  if (!$home_page_node->field_widget[LANGUAGE_NONE][0]['value']) {
  }
 
  // Otherwise, show the pane.
  return TRUE;
}

And there you have it. :)

4 Comments

Jimmy the son of wil's picture
Jimmy the son of wil -

This was a very informative article.

I wonder if this code:

if (!$home_page_node->field_widget['und'][0]['value']) {}     

return TRUE;

Could be rewritten more clearly as:

if ($home_page_node->field_widget['und'][0]['value']) {  return TRUE; }

I probably missed something or there was code left out for the example.

Matthew Connerton's picture

Aww man, I saw this a day to late. Would have saved a lot of time. Nice article.

I think you are missing a "return FALSE" after line 43. Should be:

if (!$home_page_node->field_widget['und'][0]['value']) {
  return FALSE; // hide the pane
}

Morten Thorpe's picture
Morten Thorpe -

Hello, Just a quick comment on a bad practice in your code here...

$home_page_node->field_widget['und'][0]['value']

Don't ever manually reference field-values with a hardcoded "und" as a language constant selector! The most Drupal-like D7-way is using "field_get_items" which abstracts the language parameter away, so you don't need to add the language constraint/context and potentially break your code with multilingual installations.

Cecily Borzillo's picture
Cecily Borzillo -

Morten -

You are absolutely right, good catch!  I have changed it to LANGUAGE_NONE, as a boolean field value is not translatable.  For a translatable field, field_get_items is best practice, as you say.

 

Add Your Comment

Case studies

Obermeyer — First Drupal 8 ecommerce site.
Roomify — Powerful online booking. Open source comes to travel.
Italy Magazine — Italian lifestyle, travel, and culture.
Sandusky Radio — 11 radio station websites in 6 months, within budget.

Join Now!