hs_content_taxonomy.module in Hierarchical Select 6.3
Same filename and directory in other branches
Implementation of the Hierarchical Select API for the Content Taxonomy module.
File
modules/hs_content_taxonomy.moduleView 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
Name | 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 |