Drupal: Automatically adding nodes to relevant taxonomy subqueues

The Nodequeue module is great because it gives writers the possibility to add, remove, and reorder various lists of nodes easily. With Smartqueues and the taxonomy module, you can create lists by term. One downside over other methods is that nodes are not added automatically to queues, and it can become tedious to click a button every time you want to add a node to a queue.

Recently I needed to create nodequeues for each top level terms of a single vocabulary and add new nodes of a single type to the relevant term queue automatically at creation time. For instance, you may have a taxonomy structure like this:

Vocabulary 1

  • Vegetable
    • Potato
    • Celery
    • Kale
  • Fruit
    • Tomato
    • Orange
    • Apple

If someone creates a node and picks "Apple" as one of its terms, then the node should automatically be added to a subqueue associated with the term "Fruit" and get at the top of the list. This method has the advantage to make queues easy to maintain and keep up to date (since it is automated), but it also retains all the advantages of Nodequeue since a maintainer can easily remove the node from the queue or reorder it, or add pieces from other node types to the queue as well.

There is not much documentation to do this with Smartqueue, but we can create a custom module and use hook_nodeapi to catch our node before it is inserted into the database. Here we assume the queue id is 1:

/**
 * Implementation of hook nodeapi
 *
 * Automatically add recipe nodes to the 
 * landing page slideshow subqueues.
 *
 */
function recipe_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {

  switch($op) {

  case 'insert':

    if ($node->type == 'recipe') {

      $qid = 1; // the queue id.

      // this is a recipe node and we should add it to a relevant nodequeue.
      $queue = nodequeue_load($qid); // first nodequeue is landing slideshow

      // line below runs a custom function to find parent term ids.
      $parent_tids = _recipe_get_parent_tids($node->taxonomy);

      $subqueues = nodequeue_load_subqueues_by_reference(array($queue->qid => $parent_tids));

      // after getting the right subqueues (if terms belong to more than one
      // parent it may be more than one), insert the node into the queues.
      foreach ($subqueues as $subqueue) {
        // add new recipe node automatically to relevant top level term subqueue.
        nodequeue_subqueue_add($queue, $subqueue, $node->nid);
      }

    }

  }

}

For our taxonomy queues, we only want top level terms, not the actual terms that were selected from the node form. In our previous example, we want to get the term id of "Fruit" rather than "Apple" since we want to add the recipe to the Fruits queue. The following function does that job; it also checks that the terms are actually from the correct vocabulary and that they are not children of other terms. There's probably a better way a more elegant way to do this, but I haven't found:

function _recipe_get_parent_tids ($taxonomy) {

  $voc = 1; // we assume the vocabulary id is 1.
  $allowed_parents = array();

  // get the whole taxonomy tree
  $tree = taxonomy_get_tree($voc, 0, 0);

  foreach ($tree as $term) {
    if ($term->parents[0] === 0) {
      // only take terms from the top of the tree.
      $allowed_parents[] = $term->tid;
    }    
  }

  $tids = array();

  foreach ($taxonomy as $tid => $term) {

    if (in_array($term->tid, $allowed_parents)) {
       // the selected term itself is a parent.
       $tids[] = $term->tid;
    }

    $parents = taxonomy_get_parents($term->tid);

    if (count($parents)) {
      foreach($parents as $parent) {
        $tids[] = $parent->tid;
      }
    }

  }

  // return an array of unique parent tids.
  return $tids;

}