You are here

hs_content_taxonomy.module in Hierarchical Select 6.3

Same filename and directory in other branches
  1. 5.3 modules/hs_content_taxonomy.module

Implementation of the Hierarchical Select API for the Content Taxonomy module.

File

modules/hs_content_taxonomy.module
View source
<?php

/**
 * @file
 * Implementation of the Hierarchical Select API for the Content Taxonomy
 * module.
 */

/**
 * TRICKY: Content Taxonomy's depth setting:
 * - 0 means the entire tree
 * - 1 means only the root level
 * - 2 means the first two levels
 * - etc.
 */

//----------------------------------------------------------------------------

// Core hooks.

/**
 * Implementation of hook_menu().
 */
function hs_content_taxonomy_menu() {
  $items['admin/content/node-type/%/fields/%/hs_config'] = array(
    'title' => t('HS config'),
    'access arguments' => array(
      'administer content types',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hs_content_taxonomy_config_form',
      3,
      5,
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'hs_content_taxonomy.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_form_alter().
 */
function hs_content_taxonomy_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['type']['#value']) && $form['type']['#value'] . '_node_form' == $form_id) {
    if (isset($form['#field_info']) && count($form['#field_info']) > 0) {
      foreach ($form['#field_info'] as $field_name => &$field_info) {
        if ($field_info['widget']['type'] == 'content_taxonomy_hs') {
          $form['#submit'][] = 'hs_content_taxonomy_form_submit';
          break;
        }
      }
    }
  }
}

/**
 * Implementation of hook_form_FORM_ID_alter()
 */
function hs_content_taxonomy_form_content_field_edit_form_alter(&$form, &$form_state) {
  if ($form['#field']['widget']['type'] == 'content_taxonomy_hs') {
    $form['field']['multiple']['#attributes'] = array(
      'disabled' => 'disabled',
    );
    $form['field']['multiple']['#description'] = t('This setting is now managed by the Hierarchical Select widget configuration!');
  }
}

//----------------------------------------------------------------------------

// Forms API callbacks.

/**
 * Submit handler for HS CT field form
 */
function hs_content_taxonomy_form_submit(&$form, &$form_state) {
  foreach ($form['#field_info'] as $field_name => $field_info) {
    if ($field_info['widget']['type'] == 'content_taxonomy_hs') {

      // Change format of values to the one Content Taxonomy expects
      if (is_array($form_state['values'][$field_name]['tids'])) {
        $values = array();
        foreach ($form_state['values'][$field_name]['tids'] as $tid) {
          $values[] = array(
            'value' => $tid,
          );
          array_unshift($form_state['values'][$field_name], array(
            'value' => $tid,
          ));
        }
        $form_state['values'][$field_name]['tids'] = $values;
      }
      else {
        $values[] = array(
          'value' => $form_state['values'][$field_name]['tids'],
        );
        array_unshift($form_state['values'][$field_name], array(
          'value' => $form_state['values'][$field_name]['tids'],
        ));
        $form_state['values'][$field_name]['tids'] = $values;
      }
    }
  }
}

//----------------------------------------------------------------------------

// CCK hooks.

/**
 * Implementation of hook_widget_info().
 */
function hs_content_taxonomy_widget_info() {
  return array(
    'content_taxonomy_hs' => array(
      // 'content_taxonomy_hs' instead of 'content_taxonomy_hierarchical_select' due to CCK limitations.
      'label' => 'Hierarchical Select',
      'field types' => array(
        'content_taxonomy',
      ),
      // Set multiple settings to be handled by widget rather than by CCK itself
      'multiple values' => CONTENT_HANDLE_MODULE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
  );
}

/**
 * Implementation of hook_widget_settings().
 */
function hs_content_taxonomy_widget_settings($op, $widget) {
  switch ($op) {
    case 'form':
      drupal_add_css(drupal_get_path('module', 'hs_content_taxonomy') . '/hs_content_taxonomy.css');
      $context = _hs_content_taxonomy_parse_context_from_url();
      list($content_type_name, $field_name) = $context;
      $url = 'admin/content/node-type/' . $content_type_name . '/fields/' . $field_name . '/hs_config';
      $items[] = t("Due to limitations of CCK, there is a separate form to <a href=\"!url\">\n        configure this Hierarchical Select widget's settings.</a>", array(
        '!url' => url($url),
      ));
      $placeholders = array(
        '%multiple_values' => t('Multiple values'),
        '%enable_the_dropbox' => t('Enable the dropbox'),
        '%save_term_lineage' => t('Save term lineage'),
      );
      $items[] = t('The %multiple_values field setting is now managed by the Hierarchical
        Select module: it will be enabled when either the %enable_the_dropbox
        or %save_term_lineage settings (or both) are enabled.', $placeholders);
      $form['hs_config'] = array(
        '#type' => 'fieldset',
        '#title' => t('Hierarchical Select configuration'),
        '#description' => '<p class="cck-hierarchical-select-warning">' . '<span class="highlight">Important!</span>' . '</p>' . theme('item_list', $items),
        '#collapsible' => FALSE,
      );
      return $form;
    case 'callbacks':
      return array(
        'default value' => CONTENT_CALLBACK_NONE,
      );
  }
}

/**
 * Implementation of hook_widget().
 */
function hs_content_taxonomy_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  $field_name = $field['field_name'];
  $vid = $field['vid'];
  $tid = content_taxonomy_field_get_parent($field);
  $depth = empty($field['depth']) ? 0 : $field['depth'];
  require_once drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
  $node =& $form['#node'];

  // Extremely ugly checks because CCK/Content Taxonomy is a big failure.
  $selected_items = array();
  if (isset($items[$field_name])) {

    // New node: "default value" is the default value from field settings.
    if (isset($items[$field_name]['tids'])) {

      // Multiple default values as a field setting.
      if (is_array($items[$field_name]['tids'])) {
        foreach ($items[$field_name]['tids'] as $item) {
          $selected_items[] = $item['value'];
        }
      }
      else {
        $selected_items[] = $items[$field_name]['tids'];
      }
    }
  }
  else {

    // Existing node: "default value" are the previously selected terms.
    foreach ($items as $item) {
      $selected_items[] = $item['value'];
    }
  }
  $node_field =& $node->{$field_name};
  $form[$field_name]['#tree'] = TRUE;
  $form[$field_name]['#weight'] = $field['widget']['weight'];
  $form[$field_name]['tids'] = array(
    '#title' => t($field['widget']['label']),
    '#type' => 'hierarchical_select',
    '#weight' => $field['widget']['weight'],
    '#config' => array(
      'module' => 'hs_content_taxonomy',
      'params' => array(
        'vid' => $vid,
        'tid' => $tid,
        'depth' => $depth,
      ),
    ),
    '#required' => $field['required'],
    '#description' => t($field['widget']['description']),
    '#default_value' => !empty($selected_items) ? array_values($selected_items) : array(),
  );
  unset($form[$field_name]['#options']);

  // Unset to prevent passing around of possibly huge HTML.
  unset($form[$field_name]['#theme']);

  // Unset to prevent theme_taxonomy_term_select() from running.
  hierarchical_select_common_config_apply($form[$field_name]['tids'], "content-taxonomy-{$field_name}");
  return $form;
}

//-------------------------------------------------------------------------------------------------------

// HS Content Taxonomy CCK formatters

/**
 * Implementation of hook_theme().
 */
function hs_content_taxonomy_theme() {
  return array(
    'hs_content_taxonomy_formatter_hierarchical_text' => array(
      'arguments' => array(
        'element' => NULL,
      ),
      'function' => 'theme_hs_content_taxonomy_formatter_hierarchical',
    ),
    'hs_content_taxonomy_formatter_hierarchical_links' => array(
      'arguments' => array(
        'element' => NULL,
      ),
      'function' => 'theme_hs_content_taxonomy_formatter_hierarchical',
    ),
    'hs_content_taxonomy_row' => array(
      'arguments' => array(
        'row' => NULL,
        'type' => NULL,
      ),
    ),
  );
}

/**
 * Implementation of hook_field_formatter_info().
 */
function hs_content_taxonomy_field_formatter_info() {
  return array(
    'hierarchical_text' => array(
      'label' => t('As hierarchical text'),
      'field types' => array(
        'content_taxonomy',
      ),
      'multiple values' => CONTENT_HANDLE_MODULE,
    ),
    'hierarchical_links' => array(
      'label' => t('As hierarchical links'),
      'field types' => array(
        'content_taxonomy',
      ),
      'multiple values' => CONTENT_HANDLE_MODULE,
    ),
  );
}

/**
 *  Theme function to output single row (lineage) of CT field
 *
 *  Giving levels different classes so some funny theming is possible:
 *  for example, different font size depending on level (like tagadelic)
 */
function theme_hs_content_taxonomy_row($row, $type) {
  $separator = '<span class="hierarchical-select-item-separator">›</span>';
  $output = '';
  if (empty($row)) {
    return $output;
  }
  $items = array();
  foreach ($row as $level => $item) {
    $term = taxonomy_get_term($item['value']);
    _content_taxonomy_localize_term($term);
    $line = '<span class="lineage-item lineage-item-level-' . $level . '">';

    // Depending on which formatter is active, create links or use labels.
    switch ($type) {
      case 'hierarchical_links':
        $line .= l($term->name, taxonomy_term_path($term), array(
          'rel' => 'tag',
          'title' => $term->description,
        ));
        break;
      case 'hierarchical_text':
        $line .= $item['label'];
        break;
    }
    $line .= '</span>';
    $items[] = $line;
  }
  $output = implode($separator, $items);
  return $output;
}

/**
 * Theme function for HS Content Taxonomy formatters.
 *
 */
function theme_hs_content_taxonomy_formatter_hierarchical($element) {
  $output = '';

  // Extract required field information.
  // $element contains only field name; so we use cck function to get more info.
  $field = content_fields($element['#field_name'], $element['#type_name']);
  $field_name = $field['field_name'];
  $vid = $field['vid'];
  $tid = empty($field['tid']) ? 0 : $field['tid'];
  $depth = empty($field['depth']) ? 0 : $field['depth'];

  // Get the config for this field.
  require_once drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
  $config_id = "content-taxonomy-{$field_name}";
  $config = hierarchical_select_common_config_get($config_id);
  $config += array(
    'module' => 'hs_content_taxonomy',
    'params' => array(
      'vid' => $vid,
      'tid' => $tid,
      'depth' => $depth,
    ),
  );
  $selection = array();

  // Cycle through elements.
  foreach (element_children($element) as $key) {
    if (isset($element[$key]['#item']['value'])) {
      $selection[] = $element[$key]['#item']['value'];
    }
  }

  // It is said that formatter theme function is called even if field is empty.
  if (empty($selection)) {
    return $output;
  }

  // Generate a dropbox out of the selection. This will automatically
  // calculate all lineages for us.
  $dropbox = _hierarchical_select_dropbox_generate($config, $selection);

  // Actual formatting.
  // In 6.x formatter is fully themable
  // We theme each lineage using additional theme function
  $num_items = count($dropbox->lineages);
  $flip = array(
    'even' => 'odd',
    'odd' => 'even',
  );
  $class = 'even';
  $output = '<ul class="hierarchical-select-lineages">';
  foreach ($dropbox->lineages as $i => $lineage) {
    $class = $flip[$class];
    $classes = ' ' . $class;
    if ($i == 0) {
      $classes .= ' first';
    }
    if ($i == $num_items - 1) {
      $classes .= ' last';
    }
    $output .= '<li class="lineage-' . $i . $classes . '">';
    $output .= theme('hs_content_taxonomy_row', $lineage, $element['#formatter']);
    $output .= '</li>';
  }
  $output .= '</ul>';

  // Add the CSS.
  drupal_add_css(drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css');
  return $output;
}

//----------------------------------------------------------------------------

// Hierarchical Select hooks.

/**
 * Implementation of hook_hierarchical_select_params().
 */
function hs_content_taxonomy_hierarchical_select_params() {
  $params = array(
    'vid',
    // The vocabulary id.
    'tid',
    // The root term's term id.
    'depth',
  );
  return $params;
}

/**
 * Implementation of hook_hierarchical_select_root_level().
 */
function hs_content_taxonomy_hierarchical_select_root_level($params) {
  $tid = $params['tid'];
  $terms = _hs_taxonomy_hierarchical_select_get_tree($params['vid'], $tid, -1, 1);
  return _hs_taxonomy_hierarchical_select_terms_to_options($terms);
}

/**
 * Implementation of hook_hierarchical_select_children().
 */
function hs_content_taxonomy_hierarchical_select_children($parent, $params) {
  static $tree;
  $vid = $params['vid'];
  $tid = $params['tid'];
  $depth = $params['depth'];

  // Keep a static cache of the entire tree, this allows us to quickly look up
  // if a term is not too deep – because if it's too deep, we don't want to
  // return any children.
  if (!isset($tree[$vid][$tid])) {
    $raw_tree = _hs_taxonomy_hierarchical_select_get_tree($vid, $tid);
    foreach ($raw_tree as $term) {
      $tree[$vid][$tid][$term->tid] = $term->depth;
    }
  }
  $terms = $depth > 0 && $tree[$vid][$tid][$parent] + 1 >= $depth ? array() : _hs_taxonomy_hierarchical_select_get_tree($vid, $parent, -1, 1);
  return _hs_taxonomy_hierarchical_select_terms_to_options($terms);
}

/**
 * Implementation of hook_hierarchical_select_lineage().
 */
function hs_content_taxonomy_hierarchical_select_lineage($item, $params) {
  $lineage = hs_taxonomy_hierarchical_select_lineage($item, $params);

  // If there is NO root term, then the tid parameter is set to 0. In that
  // case, there is no need to remove any of the terms before the root term,
  // because there won't be any.
  if ($params['tid'] != 0) {

    // TRICKY: When the root term has been *changed* over time, it *might* not
    // be in the lineage, because the lineage. This means the lineage is not
    // inside the tree below the defined root term. So we have to reset the
    // lineage.
    if (!in_array($params['tid'], $lineage)) {
      $lineage = array();
    }
    else {

      // Remove all terms before the root term and then the root term itself, too.
      while (count($lineage) && $lineage[0] != $params['tid']) {
        array_shift($lineage);
      }
      array_shift($lineage);
    }
  }
  return $lineage;
}

/**
 * Implementation of hook_hierarchical_select_valid_item().
 */
function hs_content_taxonomy_hierarchical_select_valid_item($item, $params) {
  if (!is_numeric($item) || $item < 1) {
    return FALSE;
  }
  $term = taxonomy_get_term($item);
  _content_taxonomy_localize_term($term);

  // Bug: tid isn't set to zero for some reason when root term is not set, so we make workaround for this

  //$params['tid'] = $params['tid'] ? $params['tid']: 0;
  return $term->vid == $params['vid'] && _hs_content_taxonomy_term_within_allowed_depth($term->tid, $term->vid, $params['tid'], $params['depth']);
}

/**
 * Implementation of hook_hierarchical_select_item_get_label().
 */
function hs_content_taxonomy_hierarchical_select_item_get_label($item, $params) {
  return hs_taxonomy_hierarchical_select_item_get_label($item, $params);
}

/**
 * Implementation of hook_hierarchical_select_create_item().
 */
function hs_content_taxonomy_hierarchical_select_create_item($label, $parent, $params) {

  // TRICKY: no depth check is necessary because HS's internal validation
  // prevents an invalid parent.
  return hs_taxonomy_hierarchical_select_create_item($label, $parent, $params);
}

/**
 * Implementation of hook_hierarchical_select_entity_count().
 */
function hs_content_taxonomy_hierarchical_select_entity_count($item, $params) {
  return hs_taxonomy_hierarchical_select_entity_count($item, $params);
}

/**
 * Implementation of hook_hierarchical_select_implementation_info().
 */
function hs_content_taxonomy_hierarchical_select_implementation_info() {
  return array(
    'hierarchy type' => t('Content Taxonomy'),
    'entity type' => t('Node'),
  );
}

/**
 * Implementation of hook_hierarchical_select_config_info().
 */
function hs_content_taxonomy_hierarchical_select_config_info() {
  static $config_info;
  if (!isset($config_info)) {
    $config_info = array();
    $content_types = content_types();
    $fields = content_fields();
    foreach ($fields as $field_name => $field) {
      if ($field['type'] == 'content_taxonomy') {
        foreach ($content_types as $content_type_name => $content_type) {
          if (isset($content_type['fields'][$field_name]) && $content_type['fields'][$field_name]['widget']['type'] == 'content_taxonomy_hs') {
            $vocabulary = taxonomy_vocabulary_load($field['vid']);
            $config_id = "content-taxonomy-{$field_name}";
            $config_info["{$config_id}|{$content_type_name}"] = array(
              'config_id' => $config_id,
              'hierarchy type' => t('Content Taxonomy'),
              'hierarchy' => t($vocabulary->name) . " ({$field_name})",
              'entity type' => t('Node'),
              'entity' => t($content_type['name']),
              'context type' => t('Node form'),
              'context' => '',
              'edit link' => "admin/content/node-type/{$content_type_name}/fields/{$field_name}/hs_config",
            );
          }
        }
      }
    }
  }
  return $config_info;
}

//----------------------------------------------------------------------------

// Private functions.

/**
 * Parse the context (the content type and the field name) from the URL.
 *
 * @return
 *   - FALSE if no context could be found
 *   - array($content_type_name, $field_name) otherwise
 */
function _hs_content_taxonomy_parse_context_from_url() {
  if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'node-type') {
    $content_type = content_types(arg(3));
    $field_name = arg(5);
    if (arg(4) == 'fields' && !empty($field_name) && isset($content_type['fields'][$field_name])) {
      if ($content_type['fields'][$field_name]['type'] == 'content_taxonomy' && $content_type['fields'][$field_name]['widget']['type'] == 'content_taxonomy_hs') {
        return array(
          $content_type['type'],
          $field_name,
        );
      }
    }
  }
  return FALSE;
}
function _hs_content_taxonomy_term_within_allowed_depth($tid, $vid, $root_tid, $allowed_depth) {

  // If the allowed depth is zero, then every term is allowed!
  if ($allowed_depth == 0) {
    return TRUE;
  }

  // Otherwise, only allow terms that are within the allowed depth.
  static $valid_tids;
  if (!isset($valid_tids[$vid][$root_tid][$allowed_depth])) {
    $valid_tids[$vid][$root_tid][$allowed_depth] = array();
    $tree = _hs_taxonomy_hierarchical_select_get_tree($vid, $root_tid);
    foreach ($tree as $term) {
      if ($term->depth < $allowed_depth) {
        $valid_tids[$vid][$root_tid][$allowed_depth][] = $term->tid;
      }
    }
  }
  return in_array($tid, $valid_tids[$vid][$root_tid][$allowed_depth]);
}

Functions

Namesort descending Description
hs_content_taxonomy_field_formatter_info Implementation of hook_field_formatter_info().
hs_content_taxonomy_form_alter Implementation of hook_form_alter().
hs_content_taxonomy_form_content_field_edit_form_alter Implementation of hook_form_FORM_ID_alter()
hs_content_taxonomy_form_submit Submit handler for HS CT field form
hs_content_taxonomy_hierarchical_select_children Implementation of hook_hierarchical_select_children().
hs_content_taxonomy_hierarchical_select_config_info Implementation of hook_hierarchical_select_config_info().
hs_content_taxonomy_hierarchical_select_create_item Implementation of hook_hierarchical_select_create_item().
hs_content_taxonomy_hierarchical_select_entity_count Implementation of hook_hierarchical_select_entity_count().
hs_content_taxonomy_hierarchical_select_implementation_info Implementation of hook_hierarchical_select_implementation_info().
hs_content_taxonomy_hierarchical_select_item_get_label Implementation of hook_hierarchical_select_item_get_label().
hs_content_taxonomy_hierarchical_select_lineage Implementation of hook_hierarchical_select_lineage().
hs_content_taxonomy_hierarchical_select_params Implementation of hook_hierarchical_select_params().
hs_content_taxonomy_hierarchical_select_root_level Implementation of hook_hierarchical_select_root_level().
hs_content_taxonomy_hierarchical_select_valid_item Implementation of hook_hierarchical_select_valid_item().
hs_content_taxonomy_menu Implementation of hook_menu().
hs_content_taxonomy_theme Implementation of hook_theme().
hs_content_taxonomy_widget Implementation of hook_widget().
hs_content_taxonomy_widget_info Implementation of hook_widget_info().
hs_content_taxonomy_widget_settings Implementation of hook_widget_settings().
theme_hs_content_taxonomy_formatter_hierarchical Theme function for HS Content Taxonomy formatters.
theme_hs_content_taxonomy_row Theme function to output single row (lineage) of CT field
_hs_content_taxonomy_parse_context_from_url Parse the context (the content type and the field name) from the URL.
_hs_content_taxonomy_term_within_allowed_depth