nodeterms.module

<?php
// $Id$

/**
 * @file
 * The 'Node terms' module allows selected node vocabulary terms to be group by vocabulary and re-formatted with a custom display.
 *
 * This modules is very similar to the lightweight @link http://drupal.org/project/term_display Term Display @endlink module.
 * 
 * This module provides full control over the display of a node's vocabulary terms. 
 * This module does not require any custom theming, most layout changes can be accomplished 
 * by adjusting the admin and vocabulary display setting and/or by adding a little CSS. 
 *   
 * Features
 * - Format flat vocabulary terms or free-tags as a list, links, or comma delimited values.
 * - Hide all terms or just an individual vocabulary's terms in node's teaser and/or page view. *
 * - Customize a node's vocabulary terms display for teaser and page view. *
 * - Format node vocabulary as a block, fieldset, or inline text. *
 * - Format heirachical vocabulary terms as a tree (aka nested lists) and/or a breadcrumbs. *
 *
 * * These features are unique to this module. The remaining features/functionality are also available within the list of 'Similar modules' below.
 *
 * Notes
 * - If you are using the heirachical vocabulary tree and/or breadcrumb displays you should enable caching to insure optimum performance.
 *
 * Todo
 * - Test
 * - Coder
 * - Review API documentation
 * - Screencast script
 * - Post
 * 
 * Similar modules
 * - @link http://drupal.org/project/taxonomy_hide Taxonomy Hide @endlink
 * - @link http://drupal.org/project/term_display Term Display @endlink
 */

/**
 * Constants defining display options for node vocabularies.
 */

define('NODETERMS_VOCABULARY_DISPLAY_NONE', 'none');
define('NODETERMS_VOCABULARY_DISPLAY_DEFAULT', 'default');
define('NODETERMS_VOCABULARY_DISPLAY_HIDDEN', 'hidden');
define('NODETERMS_VOCABULARY_DISPLAY_BLOCK', 'block');
define('NODETERMS_VOCABULARY_DISPLAY_FIELDSET', 'fieldset');
define('NODETERMS_VOCABULARY_DISPLAY_FIELDSET_COLLAPSED', 'fieldset-collapsed');
define('NODETERMS_VOCABULARY_DISPLAY_FIELDSET_COLLAPSIBLE', 'fieldset-collapsible');
define('NODETERMS_VOCABULARY_DISPLAY_INLINE', 'inline');
define('NODETERMS_VOCABULARY_DISPLAY_CUSTOM', 'custom');

/**
 * Constants defining display options for node vocabulary terms.
 */

define('NODETERMS_TERMS_DISPLAY_DEFAULT', 'default');
define('NODETERMS_TERMS_DISPLAY_LINKS', 'links');
define('NODETERMS_TERMS_DISPLAY_LIST', 'list');
define('NODETERMS_TERMS_DISPLAY_TREE', 'tree');
define('NODETERMS_TERMS_DISPLAY_BREADCRUMBS', 'breadcrumbs');
define('NODETERMS_TERMS_DISPLAY_DELIMITED', 'delimited');
define('NODETERMS_TERMS_DISPLAY_CUSTOM', 'custom');

/**
 * Implementation of hook_menu().
 */
function nodeterms_menu() {
  $items['admin/content/taxonomy/nodeterms'] = array(
    'title' => 'Node terms',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('nodeterms_admin_form'),
    'access arguments' => array('administer taxonomy'),
    'file' => 'nodeterms.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Implementation of hook_help().
 */
function nodeterms_help($path, $args) {
  switch ($path) {
    case 'admin/content/taxonomy/nodeterms':
      return t("The 'Node terms' module allows selected node vocabulary terms to be group by vocabulary and re-formatted with a custom display.");
  }
}

/**
 * Implementation of hook_form_alter().
 */
function nodeterms_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) {
  // Add js
  drupal_add_js( drupal_get_path('module', 'nodeterms') .'/nodeterms.toggle.js');
  
  $data = nodeterms_data($form['vid']['#value']);
  
  // Vocabulary
  $vocab_options =  array(
    NODETERMS_VOCABULARY_DISPLAY_DEFAULT => t('<use default>'),
    NODETERMS_VOCABULARY_DISPLAY_HIDDEN => t('hidden'),
    NODETERMS_VOCABULARY_DISPLAY_BLOCK => t('block'),
    NODETERMS_VOCABULARY_DISPLAY_INLINE => t('inline'),
    NODETERMS_VOCABULARY_DISPLAY_FIELDSET => t('fieldset'),
    NODETERMS_VOCABULARY_DISPLAY_FIELDSET_COLLAPSIBLE => t('fieldset - collapsible'),
    NODETERMS_VOCABULARY_DISPLAY_FIELDSET_COLLAPSED => t('fieldset - collapsible+collapsed'),
    NODETERMS_VOCABULARY_DISPLAY_CUSTOM => t('custom'),
  );

  // Terms
  $terms_options = array(
    NODETERMS_TERMS_DISPLAY_DEFAULT => t('<use default>'),
    NODETERMS_TERMS_DISPLAY_LINKS => t('links'),
    NODETERMS_TERMS_DISPLAY_LIST => t('list'),
    NODETERMS_TERMS_DISPLAY_DELIMITED => t('delimited values'),
    NODETERMS_TERMS_DISPLAY_TREE => t('tree'),
    NODETERMS_TERMS_DISPLAY_BREADCRUMBS => t('breadcrumbs'),
    NODETERMS_TERMS_DISPLAY_CUSTOM => t('custom'),
  );

  // Lighten the identification fieldset so it's above ours.
  $form['identification']['#weight'] = -1;
  
  $form['nodeterms']= array(
    '#type' => 'fieldset',
    '#title' => t('Node terms display settings'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#weight' => -0.5,
  );

  // Add default teaser settings to default option text
  $default_teaser_vocabulary_display = variable_get('nodeterms_default_teaser_vocabulary_display', NODETERMS_VOCABULARY_DISPLAY_INLINE);
  $vocab_options[NODETERMS_VOCABULARY_DISPLAY_DEFAULT] = t('<use default setting>') .' - '. $vocab_options[$default_teaser_vocabulary_display]; 
  $default_teaser_terms_display = variable_get('nodeterms_default_teaser_terms_display', NODETERMS_TERMS_DISPLAY_DELIMITED);
  $terms_options[NODETERMS_TERMS_DISPLAY_DEFAULT] = t('<use default setting>') .' - '. $terms_options[$default_teaser_terms_display]; 
  
  $form['nodeterms']['teaser_vocabulary_display'] = array(
    '#type' => 'select',
    '#title' => t('Teaser vocabulary display'),
    '#default_value' => $data['teaser_vocabulary_display'],
    '#options' => $vocab_options,
    '#required' => TRUE,
  );

  $style = _nodeterms_get_form_terms_display_style($data['teaser_vocabulary_display']);
  $form['nodeterms']['teaser_terms_display'] = array(
    '#type' => 'select',
    '#title' => t('Teaser terms display'),
    '#default_value' => $data['teaser_terms_display'],
    '#options' => $terms_options,
    '#required' => TRUE,
    // Used by jQuery, in nodeterms.admin.js, to hide/show terms based on the associated display.
    '#prefix' => '<div id="toggle-teaser-terms-display" class="vocabulary-terms"'. $style .'>',
    '#suffix' => '</div>',
  );

  // Add default page settings to default option text
  $default_page_vocabulary_display = variable_get('nodeterms_default_page_vocabulary_display', NODETERMS_VOCABULARY_DISPLAY_BLOCK);
  $vocab_options[NODETERMS_VOCABULARY_DISPLAY_DEFAULT] = t('<use default setting>') .' - '. $vocab_options[$default_page_vocabulary_display]; 

  $default_page_terms_display = variable_get('nodeterms_default_page_terms_display', NODETERMS_TERMS_DISPLAY_LINKS);
  $terms_options[NODETERMS_TERMS_DISPLAY_DEFAULT]  =  t('<use default setting>') .' - '. $terms_options[$default_page_terms_display]; 
  
  $form['nodeterms']['page_vocabulary_display'] = array(
    '#type' => 'select',
    '#title' => t('Page vocabulary display'),
    '#default_value' => $data['page_vocabulary_display'],
    '#options' => $vocab_options,
    '#required' => TRUE,
  );

  $style = _nodeterms_get_form_terms_display_style($data['page_vocabulary_display']);
  $form['nodeterms']['page_terms_display'] = array(
    '#type' => 'select',
    '#title' => t('Page terms display'),
    '#default_value' => $data['page_terms_display'],
    '#options' => $terms_options,
    '#required' => TRUE,
    // Used by jQuery, in nodeterms.admin.js, to hide/show terms based on the associated display.
    '#prefix' => '<div id="toggle-page-terms-display" class="vocabulary-terms"'. $style .'>',
    '#suffix' => '</div>',
  );
}

/**
 * Return style attribute for a vocabulary's terms toggle wrapper if the associated display is hidden.
 */
function _nodeterms_get_form_terms_display_style($display_type) {
  // Note: Only hidden vocabularies will not display the terms select box.
  if ($display_type == NODETERMS_VOCABULARY_DISPLAY_HIDDEN) {
    return ' style="display:none"';
  }
  else {
    return '';
  }
}

/**
 * Fetch nodeterms data for a vocabulary or defaults if there are no registered data.
 */
function nodeterms_data($vid) {
  static $nodeterms_data;

  if (empty($nodeterms_data)) {
    $result = db_query('SELECT vid, teaser_vocabulary_display, teaser_terms_display, page_vocabulary_display, page_terms_display FROM {nodeterms}');
    while ($vocabulary = db_fetch_array($result)) {
      $nodeterms_data[$vocabulary['vid']] = $vocabulary;
    }
  }
  if (isset($nodeterms_data[$vid])) {
    return $nodeterms_data[$vid];
  }
  else {
    return array(
      'teaser_vocabulary_display' => NODETERMS_VOCABULARY_DISPLAY_DEFAULT,
      'teaser_terms_display' => NODETERMS_TERMS_DISPLAY_DEFAULT,
      'page_vocabulary_display' => NODETERMS_VOCABULARY_DISPLAY_DEFAULT, 
      'page_terms_display' => NODETERMS_TERMS_DISPLAY_DEFAULT,
    );
  }
}

/**
 * Implementation of hook_taxonomy().
 */
function nodeterms_taxonomy($op, $type, $object = NULL) {
  if ($type =! 'vocabulary') {
    return;  
  }

  switch ($op) {
    case 'delete':
      db_query('DELETE FROM {nodeterms} WHERE vid = %d', $object['vid']);
      break;
    case 'update':
      $sql = "UPDATE {nodeterms} SET teaser_vocabulary_display = '%s', teaser_terms_display = '%s', page_vocabulary_display = '%s', page_terms_display = '%s'  WHERE vid = %d";
      db_query($sql, $object['teaser_vocabulary_display'], $object['teaser_terms_display'], $object['page_vocabulary_display'], $object['page_terms_display'], $object['vid']);
      break;
    case 'insert':
      $sql = "INSERT INTO {nodeterms} (teaser_vocabulary_display, teaser_terms_display, page_vocabulary_display, page_terms_display, vid) VALUES ('%s', '%s', '%s', '%s', %d)";
      db_query($sql, $object['teaser_vocabulary_display'], $object['teaser_terms_display'], $object['page_vocabulary_display'], $object['page_terms_display'], $object['vid']);
      break;
  }
  
  if ($op == 'update' || $op == 'insert') {
    nodeterms_cache_clear();
  }
}

/**
 * Implementation of hook_preprocess_node().
 */
function nodeterms_preprocess_node(&$variables) {
  // Get node terms grouped by vocabulary output.
  $output = nodeterms_vocabularies_output(node_load($variables['nid']), $variables['teaser']);

  // Set $variables['terms'] to $output if it is not NULL.
  // If the $output is NULL then the node's terms should NOT be overwritten.
  if ($output !== NULL) {
    $variables['terms'] = $output;
  }
}

/**
 * Output a node's vocabularies group by vocabulary with a customized display and terms formatting.
 *
 * @param $node
 *   A node object
 * @param $teaser
 *   Boolean to indicate whether to return the teaser or page output of the node's terms.
 * @return
 *   NULL if the default formatting should be used, an empty string if hidden, and finally the re-formatted node terms grouped by vocabulary.
 */
function nodeterms_vocabularies_output($node, $teaser = FALSE) {
  // Add css
  drupal_add_css(drupal_get_path('module', 'nodeterms') .'/nodeterms.css');

  $display = ($teaser) ? 'teaser' : 'page';

  // Get cache
  $cached = nodeterms_cache_get($node, $display);
  if ($cached !== NULL) {
    return $cached;
  }

  // Default settings
  if ($teaser) {
    $display_default_type = variable_get('nodeterms_default_teaser_vocabulary_display', NODETERMS_VOCABULARY_DISPLAY_INLINE);
    $terms_default_type = variable_get('nodeterms_default_teaser_terms_display', NODETERMS_TERMS_DISPLAY_DELIMITED);
  }
  else {
    $display_default_type = variable_get('nodeterms_default_page_vocabulary_display', NODETERMS_VOCABULARY_DISPLAY_BLOCK);
    $terms_default_type = variable_get('nodeterms_default_page_terms_display', NODETERMS_TERMS_DISPLAY_LINK);
  }
  
  // Return NULL if node has no taxonomy terms or use default term display.
  if (!$node->taxonomy || $display_default_type == NODETERMS_VOCABULARY_DISPLAY_DEFAULT) {
    return NULL;
  }

  // Return an empty string if terms are hidden.
  if ($display_default_type == NODETERMS_VOCABULARY_DISPLAY_HIDDEN) {
    return '';
  }
  // Loop and format the node's vocabularies.
  $output = '';
  $vocabularies = taxonomy_get_vocabularies($node->type);
  
  foreach ($vocabularies as $vocabulary) {
    $data = nodeterms_data($vocabulary->vid);
    
    // Get vocab type
    $display_type = $data[$display .'_vocabulary_display'];
    if ($display_type == NODETERMS_VOCABULARY_DISPLAY_DEFAULT) {
      $display_type = $display_default_type;
    }

    // Get terms type
    $terms_type = $data[$display .'_terms_display'];
    if ($terms_type == NODETERMS_TERMS_DISPLAY_DEFAULT) {
      $terms_type = $terms_default_type;
    }

    // Get node's vocabulary output.
    $output .= nodeterms_vocabulary_output($node, $vocabulary, $display_type, $terms_type);
  }

  // Wrap output with div
  if ($output != '') {
    $output = '<div class="nodeterms-terms">'. $output .'</div>';
  }

  // Set output in cache
  nodeterms_cache_set($node, $display, $output);

  return $output;
}

/**
 * Get cache node terms output.
 *
 * @param $node
 *   A node object.
 * @param $display
 *   A string containing 'teaser' or 'page'.
 *
 * @return
 *   Return cached output or NULL.
 *
 */
function nodeterms_cache_get($node, $display = 'teaser') {
  $cache_lifetime = variable_get('nodeterms_cache_lifetime', 0);
  if (!$cache_lifetime) {
    return NULL;
  }

  $settings_last_updated = variable_get('nodeterms_last_updated', '0');
  $cid = 'nodeterms-'. $display .'-'. $node->nid;

  $cached = cache_get($cid, 'cache');
  if ($cached && // Is there data in the cache
      $cached->expire >= time() && // Expire is greater than current time
      $cached->created >= $node->revision_timestamp && // created is greater than node updated
      $cached->created >= $settings_last_updated // Created is greater admin settings last update
      ) {
    // DEBUG:
    // print('cache get');
    return $cached->data;
  }
}

/**
 * Set cache node terms output.
 *
 * @param $node
 *   A node object.
 * @param $display
 *   A string containing 'teaser' or 'page'.
 * @param $data
 *   Output data to be cached.
 */
function nodeterms_cache_set($node, $display = 'teaser', $data = NULL) {
  $cache_lifetime = variable_get('nodeterms_cache_lifetime', 0);
  if (!$cache_lifetime) {
    return;
  }

  // DEBUG:
  // print('cache set');
  $cid = 'nodeterms-'. $display .'-'. $node->nid;
  $cache_expire = $cache_lifetime * 60 + time();
  cache_set($cid, $data, 'cache', $cache_expire);
}

/**
 * Clear node terms cache.
 */
function nodeterms_cache_clear() {
  $cache_lifetime = variable_get('nodeterms_cache_lifetime', 0);
  if (!$cache_lifetime) {
    return NULL;
  }
  
  // Flush node terms node cache
  cache_clear_all('nodeterms', 'cache', TRUE);
  // Update settings last updated timestamp
  variable_set('nodeterms_last_updated', time());
}

/**
 * Output a node's vocabulary with a customized display and terms formatting.
 *
 * @param $node
 *   A node object
 * @param $vocabulary
 *   A taxonomy vocabular object
 * @param $display_type
 *   Sets the format of the vocabulary as a block, fieldset, or inline text.
 * @param $terms_type
 *   Sets the format of the node's vocabulary terms as list, links, comma delimited values, tree, or breadcrumbs.
 * @return
 *   An empty string if hidden, and finally the re-formatted node terms grouped by vocabulary.
 */
function nodeterms_vocabulary_output($node, $vocabulary, $display_type = NODETERMS_VOCABULARY_DISPLAY_BLOCK, $terms_type = NODETERMS_TERMS_DISPLAY_LINKS) {
  // Return empty string if hidden
  if ($display_type == NODETERMS_VOCABULARY_DISPLAY_HIDDEN) {
    return '';
  }
  
  // Build node's vocabulary terms content.
  switch ($terms_type) {
    case NODETERMS_TERMS_DISPLAY_BREADCRUMBS;
      $vocabulary_breadcrumbs = _nodeterms_get_vocabulary_breadcrumbs($node, $vocabulary);
      $content = ($vocabulary_breadcrumbs) ? theme('nodeterms_terms_display_breadcrumbs', $vocabulary_breadcrumbs) : NULL;
      break;
    case NODETERMS_TERMS_DISPLAY_TREE;
      $vocabulary_tree = _nodeterms_get_vocabulary_tree($node, $vocabulary);
      $content =  ($vocabulary_tree) ? theme('nodeterms_terms_display_tree', $vocabulary_tree) : NULL;
      break;
    case NODETERMS_TERMS_DISPLAY_LINKS;
      $vocabulary_links = _nodeterms_get_vocabulary_links($node, $vocabulary);
      $content = ($vocabulary_links) ? theme('nodeterms_terms_display_links', $vocabulary_links) : NULL;
      break;
    case NODETERMS_TERMS_DISPLAY_LIST;
      $vocabulary_links = _nodeterms_get_vocabulary_links($node, $vocabulary);
      $content = ($vocabulary_links) ? theme('nodeterms_terms_display_list', $vocabulary_links) : NULL;
      break;
    case NODETERMS_TERMS_DISPLAY_DELIMITED;
      $vocabulary_links = _nodeterms_get_vocabulary_links($node, $vocabulary);
      $content = ($vocabulary_links) ? theme('nodeterms_terms_display_delimited', $vocabulary_links) : NULL;
      break;
    case NODETERMS_TERMS_DISPLAY_CUSTOM;
      $content = theme('nodeterms_terms_display_custom', $node, $vocabulary);
      break;
    default:
      $content = '';
      break;
  }

  // Continue if the content if there is no content.
  if (!$content) {
    return '';
  }

  // Build display output
  switch ($display_type) {
    case NODETERMS_VOCABULARY_DISPLAY_BLOCK;
      return theme('nodeterms_vocabulary_display_block', $node, $vocabulary, $content);
    case NODETERMS_VOCABULARY_DISPLAY_FIELDSET;
    case NODETERMS_VOCABULARY_DISPLAY_FIELDSET_COLLAPSIBLE;
    case NODETERMS_VOCABULARY_DISPLAY_FIELDSET_COLLAPSED;
      return theme('nodeterms_vocabulary_display_fieldset', $node, $vocabulary, $content, $display_type);
      break;
    case NODETERMS_VOCABULARY_DISPLAY_INLINE;
      return theme('nodeterms_vocabulary_display_inline', $node, $vocabulary, $content);
    case NODETERMS_VOCABULARY_DISPLAY_CUSTOM;
      return theme('nodeterms_vocabulary_display_custom', $node, $vocabulary, $content);
    default:
      return '';
  }
}

/*
 * Get node's vocabulary tree
 */
function _nodeterms_get_vocabulary_tree($node, $vocabulary) {
  $terms = taxonomy_node_get_terms_by_vocabulary($node, $vocabulary->vid);

  // Return null if there no node terms for the vocabulary
  if (!$terms) {
    return NULL;
  }

  $tree = array();
  foreach ($terms as $term) {

    // Set default branch to the current vocabulary tree.
    $branch = &$tree;

    // Get parents and loop parents
    $parents = array_reverse( taxonomy_get_parents_all($term->tid) );
    foreach ($parents as $parent) {
      // Use name so that it can be sorted
      $tkey = $parent->name;

      // If branch is not yet defiend add as an array with path set to NULL.
      if (!isset($branch[$tkey]['term'])) {
        $parent->path = NULL;
        $branch[$tkey]['term'] = $parent;
      }

      // Add path if the term is a leaf
      if ($parent->tid == $term->tid) {
        $branch[$tkey]['term']->path = taxonomy_term_path($term);
      }

      // Set branch pointer to move down the tree.
      $branch = &$branch[$tkey]['below'];
    }
  }

  return $tree;
}

/*
 * Get node's vocabulary breadcrumbs
 */
function _nodeterms_get_vocabulary_breadcrumbs($node, $vocabulary) {
  $delimiter = variable_get('nodeterms_breadcrumbs_delimiter', ' &gt; ');
  $terms = taxonomy_node_get_terms_by_vocabulary($node, $vocabulary->vid);

  if (!$terms) {
    return NULL;
  }

  // Collection breadcrumbs in an associated/keyed array.
  $breadcrumbs = array();
  foreach ($terms as $term) {
    $parents = array_reverse( taxonomy_get_parents_all($term->tid) );

    $breadcrumb = '';
    $key = ''; // Used to sort breadcrumbs alphabetically
    foreach ($parents as $parent) {
      if ($parent->tid == $term->tid) {
        $breadcrumb .= l($parent->name, taxonomy_term_path($term) );
        $key .= $parent->name;
      }
      else {
        $breadcrumb .= '<span>'. $parent->name .'</span> '. $delimiter;
        $key .= $parent->name .'-';

      }
    }
    $breadcrumbs[$key] = $breadcrumb;
  }

  // Sort keyed array
  ksort(&$breadcrumbs);

  return $breadcrumbs;
}

/*
 * Get node's vocabulary link
 */
function _nodeterms_get_vocabulary_links($node, $vocabulary) {
  // Collect vocabulary links. Based on taxonomy_link().
  foreach ($node->taxonomy as $term) {
    if ($term->vid == $vocabulary->vid) {
      $links['taxonomy_term_'. $term->tid] = array(
        'title' => $term->name,
        'href' => taxonomy_term_path($term),
        'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
      );
    }
  }
  return $links;
}

/**
 * Implementation of hook_theme().
 */
function nodeterms_theme() {
  return array(

    // Themes for vocabulary by display
    'nodeterms_vocabulary_display_block' => array(
      'arguments' => array('node' => NULL, 'vocabulary' => NULL, 'content' => NULL),
    ),
    'nodeterms_vocabulary_display_fieldset' => array(
      'arguments' => array('node' => NULL, 'vocabulary' => NULL, 'content' => NULL, 'type' => NULL, 'form_id' => NULL),
    ),
    'nodeterms_vocabulary_display_inline' => array(
      'arguments' => array('node' => NULL, 'vocabulary' => NULL, 'content' => NULL),
    ),
    'nodeterms_vocabulary_display_custom' => array(
      'arguments' => array('node' => NULL, 'vocabulary' => NULL, 'content' => NULL),
    ),
    
    // Themes for terms by type
    'nodeterms_terms_display_tree' => array(
      'arguments' => array('branch' => NULL),
    ),
    'nodeterms_terms_display_breadcrumbs' => array(
      'arguments' => array('breadcrumbs' => NULL),
    ),
    'nodeterms_terms_display_links' => array(
      'arguments' => array('links' => NULL),
    ),
    'nodeterms_terms_display_list' => array(
      'arguments' => array('links' => NULL),
    ),
    'nodeterms_terms_display_delimited' => array(
      'arguments' => array('links' => NULL),
    ),
    'nodeterms_terms_display_custom' => array(
      'arguments' => array('node' => NULL, 'vocabulary' => NULL),
    ),
  );
}


/**
 * Theme node terms vocabulary as block. Uses theme('block', $block).
 */
function theme_nodeterms_vocabulary_display_block($node, $vocabulary, $content) {
  // Build a basic block object.
  $block = new stdClass();
  $block->module = 'nodeterms';
  $block->delta = 'nid_'. $node->nid .'_vid_'. $vocabulary->vid;
  $block->subject = $vocabulary->name;
  $block->content = $content;

  // Add node and vocabulary to block's variables so that the block.tpl.php could provide additional formatting.
  $block->node = $node;
  $block->node = $vocabulary;

  return theme('block', $block);
}

/**
 * Theme node terms vocabulary fieldset that can be collapsible and/or collapsed.
 */
function theme_nodeterms_vocabulary_display_fieldset($node, $vocabulary, $content, $type) {
  // The #attributes[id]  and #container_id is used by fieldset helper module to save collapse state in the database.
  $element = array(
    '#type' => 'fieldset',
    '#title' => $vocabulary->name,
    '#value' => $content,
    '#attributes' => array(
      'id' => 'fieldset-nid-'. $node->nid .'-vid-'. $vocabulary->vid,
      'class' => 'nodeterms-vocabulary-fieldset'
    ),
    '#container_id' => 'node-'. $node->nid,
  );
  
  // Get fieldset's css class used for collapsible fieldsets.
  switch ($type) {
    case NODETERMS_VOCABULARY_DISPLAY_FIELDSET_COLLAPSIBLE;
      $element['#collapsible'] = TRUE;
      break;
    case NODETERMS_VOCABULARY_DISPLAY_FIELDSET_COLLAPSED;
      $element['#collapsible'] = TRUE;
      $element['#collapsed'] = TRUE;
      break;
    default:
      break;
  }

  return theme('fieldset', $element);
}

/**
 * Theme node terms vocabulary inline.
 */
function theme_nodeterms_vocabulary_display_inline($node, $vocabulary, $content) {
  $delimiter = variable_get('nodeterms_inline_delimiter', ': ');

  $output = '<div class="nodeterms-vocabulary-inline">';
  $output .= '<strong>'. $vocabulary->name . $delimiter .'</strong>';
  $output .= $content;
  $output .= '</div>';
  return $output;
}

/**
 * Theme node terms vocabulary inline.
 */
function theme_nodeterms_vocabulary_display_custom($node, $vocabulary, $content) {
  $output = '<div class="nodeterms-vocabulary-custom">';
  $output .= '<h3>'. $vocabulary->name .'</h3>';
  $output .= $content;
  $output .= '</div>';
  return $output;
}

/**
 * Theme node terms as tree (nested un-ordered lists).
 */
function theme_nodeterms_terms_display_tree($branch) {
  // Sort current branch by key
  ksort(&$branch);

  $output = '<ul>';
  foreach ($branch as $key => $value) {
    $term = $value['term'];

    $output .= '<li>';
    $output .= ($term->path) ? l($term->name, $term->path ) : '<span>'. $term->name .'</span>';
    // Recurse below branches
    if ($value['below']) {
      $output .= theme_nodeterms_terms_display_tree($value['below']);
    }
    $output .= '</li>';
  }
  $output .= '</ul>';
  return $output;
}

/**
 * Theme node terms vocabulary as breadcrumbs.
 */
function theme_nodeterms_terms_display_breadcrumbs($breadcrumbs) {
  // Format breadcrumbs in unordered list.
  $output = '<ul>';
  foreach ($breadcrumbs as $breadcrumb) {
    $output .= '<li>'. $breadcrumb .'</li>';
  }
  $output .= '</ul>';
  return $output;
}

/**
 * Theme node terms vocabulary as links.
 */
function theme_nodeterms_terms_display_links($links) {
  return theme('links', $links);
}

/**
 * Theme node terms vocabulary as list.
 */
function theme_nodeterms_terms_display_list($links) {
  return theme('links', $links, NULL);
}

/**
 * Theme node terms vocabulary as delimited values.
 */
function theme_nodeterms_terms_display_delimited($links) {
  foreach ($links as $key => $link) {
    $links[$key] = l($link['title'], $link['href'], $link);
  }

  $delimiter = variable_get('nodeterms_delimited_delimiter', ', ');
  return implode($delimiter, $links);
}

/**
 * Custom theme node terms vocabulary 
 */
function theme_nodeterms_terms_display_custom($node, $vocabulary) {
  $links = _nodeterms_get_vocabulary_links($node, $vocabulary);
  
  foreach ($links as $key => $link) {
    $links[$key] = l($link['title'], $link['href'], $link);
  }

  $delimiter = variable_get('nodeterms_delimited_delimiter', ', ');
  return implode($delimiter, $links);
}