Sunday, February 12, 2017

Ctools: custom argument plugin

This time we will consider an argument plugin. Arguments are pretty similar to contexts. Actually arguments are context objects loaded from url. By default ctools provides a full set of needed arguments such as "Node: ID", "User: ID", "User: name" etc. But what if you've created a custom context? You might need to create a custom argument for your context (if you want to use your context as an argument of course). I advise you to read previous articles from "Ctools custom plugin" series such as "Ctools: custom access plugin" and "Ctools: custom context plugin". It's also required to read "Ctools: custom content type plugin" before reading this post because there I've described how to create a module integrated with ctools API which can contain ctools plugins.

For this tutorial we need a custom context (because there is no sense to copy paste default ctools argument plugins for creating default ctools context plugins - let's be creative) so I've created one: node_status. It's pretty simple and similar to default 'node' context by it provides extra node properties: 'status' and 'promote' (default node context doesn't provide them). It behaves similar to 'node' context. I mean it has a 'nid' option in settings form. Once you've entered a node id you will be able to use %argument_name:status and %argument_name:promote values. Context definition looks like:
<?php

/**
 * @file
 *
 * Plugin to provide a node_status context.
 */

/**
 * Plugins are described by creating a $plugin array which will be used
 * by the system that includes this file.
 */
$plugin = [
  // Plugin title.
  'title' => t('Node status'),
  // Plugin description.
  'description' => t('Node status.'),
  // A function that will return context.
  'context' => 'example_context_node_status_plugin',
  // Context keyword to use for
  // substitution in titles.
  'keyword' => 'node_status',
  // Context machine name.
  'context name' => 'node_status',
  // Settings form callback.
  'edit form' => 'example_context_node_status_plugin_settings_form',
  // Default values for settings form.
  'defaults' => [
    'nid' => 1,
  ],
  // Array of available context values.
  'convert list' => [
    'status' => t('Node status'),
    'promote' => t('Node promote'),
  ],
  // A function gets data from a given
  // context and returns values defined
  // in 'convert list' property.
  'convert' => 'example_context_node_status_plugin_convert',
];

/**
 * Context callback.
 */
function example_context_node_status_plugin($empty, $data = NULL, $conf = FALSE) {
  // Create context object.
  $context = new ctools_context('node_status');

  // This property should contain file name where
  // plugin definition is placed.
  $context->plugin = 'example_module.example_context_plugin.node_status';

  if (empty($empty)) {
    // Define context data.
    // Variable $data can be an array or an object.
    // It depends on how this context is created.
    // If it's created by putting context directly to
    // a panel page then $data - array containing
    // settings from form.
    if (is_array($data) && !empty($data['nid'])) {
      $node = node_load($data['nid']);
    }

    // If context is created by
    // an argument then $data - object.
    if (is_object($data)) {
      $node = $data;
    }

    if (!empty($node)) {
      $context->data = $node;
      $context->title = $node->title;

      // Specify argument value - node id.
      $context->argument = $node->nid;
    }
  }

  return $context;
}

/**
 * Returns property value by property type.
 */
function example_context_node_status_plugin_convert($context, $type) {
  $result = '';

  // Return node property (status or promote).
  if (!empty($context->data)) {
    $result = $context->data->{$type};
  }

  return $result;
}

/**
 * Settings form for cookies context.
 */
function example_context_node_status_plugin_settings_form($form, &$form_state) {
  // Node id option.
  $form['nid'] = [
    '#type' => 'textfield',
    '#title' => t('Node nid'),
    '#default_value' => $form_state['conf']['nid'],
    '#element_validate' => [
      'element_validate_integer_positive',
    ],
  ];

  return $form;
}

/**
 * Settings form submit.
 */
function example_context_node_status_plugin_settings_form_submit($form, &$form_state) {
  // Save submitted value.
  $form_state['conf']['nid'] = $form_state['values']['nid'];
}
I won't describe what goes here because we've already discussed custom context theme last time. Just put this code into example_module/plugins/contexts/example_module.example_context_plugin.node_status.inc file. Make sure that your module file structure now looks like this one:
example_module
  |__ plugins
  |  |__ content_types
  |  |  |__ example_module.example_content_type_plugin.inc
  |  |__ access
  |  |  |__ example_module.example_access_plugin.inc
  |  |__ contexts
  |     |__ example_module.example_context_plugin.inc
  |     |__ example_module.example_context_plugin.node_status.inc
  |__ example_module.info
  |__ example_module.module
Ok, finally we've got a context which we want to use as argument. For this tutorial I've created a custom page with a "node-view-page/%nid" url.

Page with required argument

Let's move on. Now we will create an argument plugin.

1. Create file example_module/plugins/arguments/example_module.example_argument_plugin.inc:
<?php

/**
 * @file
 *
 * Plugin to provide an argument handler for a node_status context.
 */

/**
 * Plugins are described by creating a $plugin array which will be used
 * by the system that includes this file.
 */
$plugin = [
  // Plugin title.
  'title' => t('Node status argument'),
  // Plugin description.
  'description' => t('Creates a node status context from a node ID argument.'),
  // Keyword.
  'keyword' => 'node_status_argument',
  // Context builder function.
  'context' => 'example_argument_plugin',
];

/**
 * Discover if this argument gives us the node we crave.
 */
function example_argument_plugin($arg = NULL, $conf = NULL, $empty = FALSE) {
  $context = FALSE;

  // If empyt it wants a generic, unfilled context.
  if ($empty) {
    $context = ctools_context_create_empty('example_module.example_context_plugin.node_status');
  }
  else {
    // We can accept either a node object or a pure nid.
    if (is_object($arg)) {
      $context = ctools_context_create('example_module.example_context_plugin.node_status', $arg);
    }
    elseif (is_numeric($arg)) {
      $node = node_load($arg);

      if (!empty($node)) {
        $context = ctools_context_create('example_module.example_context_plugin.node_status', $node);
      }
    }
  }

  return $context;
}
2. Clear cache and navigate to panel page settings: arguments. Assign "Node status argument" to %nid.

Assigned argument

3. Navigate to panel page settings: contexts. You will see that argument provides properties from defined context.

Context loaded from an argument

4. Now you can use context properties for example for page title:

Context properties are used in a page title

Now if you open node-view-page/1, for example, you will see:

Custom argument plugin in action

If you unpublish a node then title will be "Status: 0 Promoted: 1". That's all.

No comments:

Post a Comment