You are here

hs_content_taxonomy.module in Hierarchical Select 5.3

Same filename and directory in other branches
  1. 6.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.
 */
define('HS_CONTENT_TAXONOMY_SEPARATOR', '<span class="hierarchical-select-item-separator">›</span>');

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

// Core hooks.

/**
 * Implementation of hook_menu().
 */
function hs_content_taxonomy_menu($may_cache) {
  $items = array();
  if (!$may_cache) {
    $context = _hs_content_taxonomy_parse_context_from_url();
    if (is_array($context)) {
      list($content_type_name, $field_name) = $context;
      $content_type = content_types($content_type_name);
      $items[] = array(
        'path' => 'admin/content/types/' . $content_type['url_str'] . '/fields/' . $field_name . '/hs_config',
        'title' => t('Hierarchical Select configuration for !field', array(
          '!field' => $content_type['fields'][$field_name]['widget']['label'],
        )),
        'callback' => 'drupal_get_form',
        'callback arguments' => array(
          'hs_content_taxonomy_config_form',
          $content_type['type'],
          $field_name,
        ),
        'access' => user_access('administer content types'),
        'type' => MENU_CALLBACK,
      );
    }
  }
  return $items;
}

/**
 * Implementation of hook_form_alter().
 */
function hs_content_taxonomy_form_alter($form_id, &$form) {
  if ($form_id == '_content_admin_field') {
    if ($form['widget']['widget_type']['#default_value'] == 'content_taxonomy_hs') {

      // Hide the "multiple values" setting, so the user can't change it.
      $form['field']['multiple']['#type'] = 'hidden';

      // Add a fake checkbox form item to indicate the current state of this
      // setting. Because this checkbox is disabled, it won't be submitted,
      // and that's why we have to add a fake form item.
      $split = array_search('multiple', array_keys($form['field'])) + 1;
      $first_part = array_slice($form['field'], 0, $split);
      $second_part = array_slice($form['field'], $split);
      $form['field'] = $first_part;
      $form['field']['fake_multiple'] = $form['field']['multiple'];
      $form['field']['fake_multiple']['#type'] = 'checkbox';
      $form['field']['fake_multiple']['#attributes'] = array(
        'disabled' => 'disabled',
      );
      $form['field']['fake_multiple']['#description'] = t('This setting is now managed by the Hierarchical Select widget
        configuration!');
      $form['field'] += $second_part;
    }
  }
}

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

// Forms API callbacks.

/**
 * Form definition; configuration form for Hierarchical Select as the widget
 * for a content_taxonomy field.
 *
 * @param $content_type_name
 *   Name of a content type. Provides necessary context.
 * @param $field_name
 *   Name of a field. Provides necessary context.
 */
function hs_content_taxonomy_config_form($content_type_name, $field_name) {
  require_once drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
  drupal_add_css(drupal_get_path('module', 'hs_content_taxonomy') . '/hs_content_taxonomy.css');
  $content_type = content_types($content_type_name);
  $field = $content_type['fields'][$field_name];

  // Extract the necessary context from the $field array.
  $vid = $field['vid'];
  $tid = $field['tid'];
  $depth = empty($field['depth']) ? 0 : $field['depth'];

  // Add the Hierarchical Select config form.
  $module = 'hs_content_taxonomy';
  $params = array(
    'vid' => $vid,
    'tid' => $tid,
    'depth' => $depth,
  );
  $config_id = "content-taxonomy-{$field_name}";
  $vocabulary = taxonomy_get_vocabulary($vid);
  $defaults = array(
    // Enable the save_lineage setting by default if the multiple parents
    // vocabulary option is enabled.
    'save_lineage' => (int) ($vocabulary->hierarchy == 2),
    'editability' => array(
      'max_levels' => min($depth, _hs_taxonomy_hierarchical_select_get_depth($vid)),
    ),
  );

  // If this config is being created (not edited), then enable the dropbox if
  // this is a "multiple values" field. This allows for an intuitive
  // transition to a Hierarchical Select widget.
  if (variable_get('hs_config_' . $config_id, FALSE) === FALSE) {
    $defaults['dropbox']['status'] = $field['multiple'];
  }
  $strings = array(
    'hierarchy' => t('vocabulary'),
    'hierarchies' => t('vocabularies'),
    'item' => t('term'),
    'items' => t('terms'),
    'item_type' => t('term type'),
    'entity' => t('node'),
    'entities' => t('nodes'),
  );
  $max_hierarchy_depth = min($depth == 0 ? 9 : $depth, _hs_taxonomy_hierarchical_select_get_depth($vid));
  $preview_is_required = $field['required'];
  $form['hierarchical_select_config'] = hierarchical_select_common_config_form($module, $params, $config_id, $defaults, $strings, $max_hierarchy_depth, $preview_is_required);
  $form['link'] = array(
    '#value' => l('Back to the field configuration', 'admin/content/types/' . $content_type['url_str'] . '/fields/' . $field_name),
    '#prefix' => '<div class="cck-hierarchical-select-back-link">',
    '#suffix' => '</div>',
    '#weight' => -5,
  );
  $form['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );

  // Add the the submit handler for the Hierarchical Select config form.
  $parents = array(
    'hierarchical_select_config',
  );
  $form['#submit']['hierarchical_select_common_config_form_submit'] = array(
    $parents,
  );
  $form['#submit']['hs_content_taxonomy_common_config_form_submit'] = array(
    $content_type_name,
    $field_name,
  );
  return $form;
}

/**
 * Additional submit callback to update the multiple values field setting.
 */
function hs_content_taxonomy_common_config_form_submit($form_id, $form_values, $content_type_name, $field_name) {
  $config = $form_values['hierarchical_select_config'];
  $multiple_values = $config['save_lineage'] | $config['dropbox']['status'];
  $content_type = content_types($content_type_name);
  $field = $content_type['fields'][$field_name];
  $form_values = array(
    'widget_type' => 'content_taxonomy_hs',
    'label' => $field['widget']['label'],
    'weight' => $field['widget']['weight'],
    'description' => $field['widget']['description'],
    'required' => $field['required'],
    'multiple' => $multiple_values,
    'save' => $field['save'],
    'vid' => $field['vid'],
    'tid' => $field['tid'],
    'depth' => $field['depth'],
    'op' => t('Save field settings'),
    'submit' => t('Save field settings'),
    'type_name' => $field['type_name'],
    'field_name' => $field_name,
    'field_type' => 'content_taxonomy',
    'module' => 'content_taxonomy, hs_content_taxonomy',
    'form_id' => '_content_admin_field',
  );
  drupal_execute('_content_admin_field', $form_values, $field['type_name'], $field_name);
}

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

// 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',
      ),
    ),
  );
}

/**
 * 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;
      $content_type = content_types($content_type_name);
      $url = 'admin/content/types/' . $content_type['url_str'] . '/fields/' . $field_name . '/hs_config';
      $items[] = t("Due to limitations of CCK, there is a separate form to\n        <a href=\"!url\"> configure this Hierarchical Select widget's\n        settings.</a>", array(
        '!url' => url($url),
      ));
      $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.', array(
        '%multiple_values' => t('Multiple values'),
        '%enable_the_dropbox' => t('Enable the dropbox'),
        '%save_term_lineage' => t('Save term lineage'),
      ));
      $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($op, &$node, $field, &$node_field) {
  if ($field['widget']['type'] == 'content_taxonomy_hs') {
    $field_name = $field['field_name'];
    $vid = $field['vid'];
    $tid = $field['tid'];
    $depth = empty($field['depth']) ? 0 : $field['depth'];
    switch ($op) {
      case 'form':
        require_once drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
        $form[$field_name]['#tree'] = TRUE;
        $form[$field_name]['tids'] = array(
          '#title' => $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']),
          // The default value comes from $node_field (thus from the CCK
          // storage), unless it's empty, then we check if $node->taxonomy
          // (thus 'normal' Taxonomy storage) contains a value, and use that
          // instead, unless that's empty too.
          // The latter will only work reliably if only one content_taxonomy
          // field is being used, because when you have multiple
          // content_taxonomy fields that use the same vocabulary, there's no
          // way to distinguish.
          '#default_value' => is_array($node_field[$tid]) ? array_keys($node_field[$tid]) : (is_array($node->taxonomy) ? array_keys($node->taxonomy) : array()),
        );
        hierarchical_select_common_config_apply($form[$field_name]['tids'], "content-taxonomy-{$field_name}");
        return $form;
      case 'process form values':

        // TRICKY: this piece of utterly ugly, crappy and dysfunctional code
        // is here thanks to the ugly internal works of the content_taxonomy
        // module that don't make any sense at all. It's necessary to support
        // the 'both' (and 'cck') "save option" of content_taxonomy.
        if (isset($field['save']) && $field['save'] != 'tag') {
          if ($field['multiple'] && is_array($node_field['tids'])) {
            foreach ($node_field['tids'] as $key => $tid) {
              if ($tid != 0) {
                $keys[$tid] = $tid;
              }
            }
          }
          else {
            $keys[$node_field['tids']] = $node_field['tids'];
          }
          $node_field = content_transpose_array_rows_cols(array(
            'value' => $keys,
          ));
        }
        else {
          if (!$field['multiple']) {
            $value = $node_field['tids'];
            $node_field['tids'] = array();
            $node_field['tids'][0] = $value;
          }
          else {
            $value = $node_field['tids'];
            if (!$value) {
              $node_field['tids'] = array();
              $node_field['tids'][0] = $value;
            }
          }
        }
        break;
    }
  }
}

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

/**
 * Implemenation of hook_field_formatter().
 */
function hs_content_taxonomy_field_formatter($field, $item, $formatter, $node) {
  $output = '';
  if (!is_array($item)) {
    return $output;
  }

  // Extract required field information.
  $field_name = $field['field_name'];
  $vid = $field['vid'];
  $tid = $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,
    ),
  );

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

  // Actual formatting.
  $separator = HS_CONTENT_TAXONOMY_SEPARATOR;
  foreach ($dropbox->lineages as $id => $lineage) {
    if ($id > 0) {
      $output .= '<br />';
    }
    $items = array();
    foreach ($lineage as $level => $item) {

      // Depending on which formatter is active, create links or use labels.
      if ($formatter == 'hierarchical_links') {
        $term = taxonomy_get_term($item['value']);
        $items[] = l($term->name, taxonomy_term_path($term), array(
          'rel' => 'tag',
          'title' => $term->description,
        ));
      }
      else {
        $items[] = $item['label'];
      }
    }
    $output .= implode($separator, $items);
  }

  // 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) {
  $terms = _hs_taxonomy_hierarchical_select_get_tree($params['vid'], $params['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 to 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);
  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_get_vocabulary($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/types/{$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) == 'types') {
    $content_type = content_types(arg(3));
    $type = node_get_types('types', $content_type['type']);
    $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_common_config_form_submit Additional submit callback to update the multiple values field setting.
hs_content_taxonomy_config_form Form definition; configuration form for Hierarchical Select as the widget for a content_taxonomy field.
hs_content_taxonomy_field_formatter Implemenation of hook_field_formatter().
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_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_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().
_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

Constants

Namesort descending Description
HS_CONTENT_TAXONOMY_SEPARATOR TRICKY: Content Taxonomy's depth setting: