Drupal 7 Tutorial: Creating a Custom Entityqueue Handler

Entityqueue uses Ctools plugins for what we call an EntityQueueHandler. In this post we are going to see how to create a custom EntityQueueHandler. We will be looking at the Entityqueue Scheduler module as an example.

What is an EntityQueueHandler?

When you create a new Entityqueue, there is an option to select which handler to use.

First lets look at what an EntityQueueHandler can do. To do that, take a peek at the EntityQueueHandlerInterface. While you can implement that interface directly, most likely you will extend the EntityQueueHandlerBase abstract class or one of its subclasses and only override the methods you care about.

Here is a quick overview of the most likely methods you'll want to override.

Methods for overriding Entityqueue forms

/**
 * Generates a settings form for this handler.
 */
public function settingsForm();
 
/**
 * Generates an add/edit subqueue form for this handler.
 */
public function subqueueForm(EntitySubqueue $subqueue, &$form_state);

Method for overriding a Subqueue label

/**
 * Returns the label of a given subqueue.
 */
public function getSubqueueLabel(EntitySubqueue $subqueue);

Method for determining if a Subqueue can be deleted

/**
 * Returns TRUE if subqueue can be deleted, otherwise returns FALSE.
 */
public function canDeleteSubqueue(EntitySubqueue $subqueue);

Methods for CRUD on an Entityqueue

/**
 * Act on creating a queue.
 */
public function create();
 
/**
 * Act on loading a queue.
 */
public function load();
 
/**
 * Act on loading a queue that is defined only in code.
 */
public function loadFromCode();
 
/**
 * Act before a queue is saved.
 */
public function preSave();
 
/**
 * Act after a new queue is saved.
 */
public function insert();
 
/**
 * Act after a queue is updated.
 */
public function update();
 
/**
 * Act before deleting a queue.
 */
public function preDelete();
 
/**
 * Act after deleting a queue.
 */
public function postDelete();

Getting Started

Like all Ctools plugins when you want to implement your own custom plugin you first have to let ctools know where to look for your plugins.

Implement hook_ctools_plugin_directory() to let ctools know where to look.

An example from Entityqueue Scheduler.

/**
 * Implements hook_ctools_plugin_directory().
 */
function entityqueue_scheduler_ctools_plugin_directory($module, $plugin) {
  return 'plugins/' . $module . '/' . $plugin;
}

The common convention is to put plugins in a plugin folder and subfolders for the different modules and plugin types that your module implements. In this case $module = 'entityqueue' and $plugin = 'handler', so ctools will look for plugins/entityqueue/handler within our module folder for any .inc files.

Inside our include file, we define our $plugin array.

// plugins/entityqueue/handler/scheduled.inc
$plugin = array(
  'title' => t('Scheduled queue'),
  'class' => 'EntityQueueSchedulerEntityQueueHandler',
  'weight' => -99,
);

Entityqueue handler plugins are pretty simple. Give it a title, tell it the name of the class that implements EntityQueueHandler and optionally give it a weight. There is also a 'queue type' which can be set to 'single' if the EntityQueueHandler only supports 1 Subqueue, otherwise it defaults to 'multiple' which means the EntityQueueHandler supports multiple Subqueues.

Overview of Entityqueue Scheduler

Entityqueue Scheduler allows users to schedule the items and order of the queue in the future. It does this using subqueues. Each subqueue has a date on which it will be "published".

Defining EntityQueueSchedulerEntityQueueHandler

We create our class in the same directory as our plugin definition, the file must be named ClassName.class.php. For EntityQueueSchedulerEntityQueueHandler that is EntityQueueSchedulerEntityQueueHandler.class.php

First we extend EntityQueueHandlerBase which implements the EntityQueueHandlerInterface.

class EntityQueueSchedulerEntityQueueHandler extends EntityQueueHandlerBase {

Define our Subqueue Form

Since we want our Subqueues to have a date on which to be published, we need to override the Subqueue form. The subqueueForm method takes the EntitySubqueue and $form_state as parameters and returns the $form array. Keep in mind that the EntitySubqueue may not be saved yet, so may not have a subqueue_id.

/**
 * Overrides EntityQueueHandlerBase::subqueueForm().
 */
public function subqueueForm(EntitySubqueue $subqueue, &$form_state) {
  // Get the current timezone
  $timezone = date_default_timezone_object();
  // The subqueue has a schedule date, use that as the default
  if (isset($subqueue->scheduler_date)) {
    $date_object = new DateObject($subqueue->scheduler_date, 'UTC');
    $date_object->setTimezone($timezone);
    $default_date = date_format_date($date_object, 'custom', 'Y-m-d H:i');
  }
  // Otherwise set the default to an hour from now
  else {
    $now = date_now($timezone);
    // Add an hour.
    $now->add(new DateInterval('PT1H'));
    $default_date = date_format_date($now, 'custom', 'Y-m-d H:i');
  }
  $form = array();
  // Don't add the date field to the currently published subqueue.
  if (!$this->isLiveQueue($subqueue)) {
    $form['date'] = array(
      '#title' => t('Publish Date'),
      '#type' => 'date_select',
      '#default_value' => $default_date,
      '#date_format' => 'm d, Y H:iA',
      '#date_label_position' => 'above',
      '#date_increment' => 15,
      '#date_year_range' => '-0:+3',
      '#required' => TRUE,
      '#weight' => -50,
      '#disabled' => isset($subqueue->subqueue_id),
      '#description' => t('This is the date when this subqueue should be published. Once you set the date for the subqueue it cannot be changed. You will need to create a new subqueue for a new date.'),
    );
    $form['#validate'][] = 'entityqueue_scheduler_subqueue_form_validate';
  }
  return $form;
}

Define our Subqueue Label

Since our subqueues are only distiguishable by the date, we want to use that in the label and display it in a user-friendly way. To do that, we override the getSubqueueLabel method which takes a EntitySubqueue and returns a translated string.

/**
 * Overrides EntityQueueHandlerBase::getSubqueueLabel().
 */
public function getSubqueueLabel(EntitySubqueue $subqueue) {
  if (!empty($subqueue->scheduler_date)) {
    // Use site timezone for label.
    $timezone = date_default_timezone_object(FALSE);
    $date_object = new DateObject($subqueue->scheduler_date, 'UTC');
    $date_object->setTimezone($timezone);
    $label = t('@queue: @date queue', array(
      '@queue' => $this->queue->label,
      '@date' => date_format_date($date_object, 'short'),
    ));
  }
  else {
    $label = t('@queue: Live queue', array(
      '@queue' => $this->queue->label,
    ));
  }
  return $label;
}

Ensure there is always a Published Subqueue

Entityqueue Scheduler uses a special Subqueue that it calls the "Live queue". This is the currently published version of the queue. We want to make sure this Subqueue exists as soon as a new queue using our EntityQueueSchedulerEntityQueueHandler is created.

/**
 * Overrides EntityQueueHandlerBase::loadFromCode().
 */
public function loadFromCode() {
  $this->ensureSubqueue();
}
 
/**
 * Overrides EntityQueueHandlerBase::insert().
 */
public function insert() {
  $this->ensureSubqueue();
}
 
/**
 * Makes sure that every queue has a subqueue.
 */
protected function ensureSubqueue() {
  global $user;
  static $queues = array();
 
  if (!isset($queues[$this->queue->name])) {
    $queues[$this->queue->name] = TRUE;
 
    $transaction = db_transaction();
    $query = new EntityFieldQuery();
    $query
      ->entityCondition('entity_type', 'entityqueue_subqueue')
      ->entityCondition('bundle', $this->queue->name);
    $result = $query->execute();
 
    // If we don't have a subqueue already, create the live one.
    if (empty($result['entityqueue_subqueue'])) {
      $subqueue = entityqueue_subqueue_create();
      $subqueue->queue = $this->queue->name;
      $subqueue->name = $this->queue->name . '__live';
      $subqueue->label = $this->getSubqueueLabel($subqueue);
      $subqueue->module = 'entityqueue_scheduler';
      $subqueue->uid = $user->uid;
 
      entity_get_controller('entityqueue_subqueue')->save($subqueue, $transaction);
    }
  }
}

We define a helper method to create the Live subqueue if it doesn't exist and call it whenever a new queue is inserted and when a queue is loaded from a code export. We override the insert and loadFromCode methods.

Prevent Users from Deleting the Published Subqueue

We also want to make sure users cannot accidentally delete our Live subqueue. To do that override the canDeleteSubqueue method.

/**
 * Overrides EntityQueueHandlerBase::canDeleteSubqueue().
 */
public function canDeleteSubqueue(EntitySubqueue $subqueue) {
  if ($this->isLiveQueue($subqueue)) {
    return FALSE;
  }
  return TRUE;
}

What Next?

That's it for our implementation of an EntityQueueHandler. Feel free to checkout the rest of the code for Entityqueue Scheduler. Also, you can help get it to a full release by reviewing this patch to the Date module.

If you need additional help understanding ctools plugins (who doesn't?), Ctools has a Plugin Examples module. I also found slides for a Ctools plugins presentation that gives a good overview.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.

About the Author

Jonathan Jordan, Software Architect