t('HSD config'), 'access arguments' => array('administer content types'), 'page callback' => 'drupal_get_form', 'page arguments' => array('hs_content_taxonomy_description_config_form', 3, 5), 'type' => MENU_NORMAL_ITEM, 'file' => 'hs_content_taxonomy_description.admin.inc', ); return $items; } /** * Implementation of hook_form_alter(). */ function hs_content_taxonomy_description_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_hsd') { $form['#submit'][] = 'hs_content_taxonomy_form_submit'; break; } } } } } /** * Implementation of hook_form_FORM_ID_alter() */ function hs_content_taxonomy_description_form_content_field_edit_form_alter(&$form, &$form_state) { if ($form['#field']['widget']['type'] == 'content_taxonomy_hsd') { $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_description_form_submit(&$form, &$form_state) { foreach ($form['#field_info'] as $field_name => $field_info) { if ($field_info['widget']['type'] == 'content_taxonomy_hsd') { // 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_description_widget_info() { return array( 'content_taxonomy_hsd' => array( // 'content_taxonomy_hsd' instead of 'content_taxonomy_hierarchical_select' due to CCK limitations. 'label' => 'Hierarchical Select Description', '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_description_widget_settings($op, $widget) { switch ($op) { case 'form': drupal_add_css(drupal_get_path('module', 'hs_content_taxonomy_description') .'/hs_content_taxonomy_description.css'); $context = _hs_content_taxonomy_description_parse_context_from_url(); list($content_type_name, $field_name) = $context; $url = 'admin/content/node-type/'. $content_type_name .'/fields/'. $field_name .'/hsd_config'; $items[] = t( "Due to limitations of CCK, there is a separate form to configure this Hierarchical Select widget's settings.",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['hsd_config'] = array( '#type' => 'fieldset', '#title' => t('Hierarchical Select configuration'), '#description' => '

'. 'Important!'. '

'. 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_description_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']; } } // Single default value as a field setting. 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_description', '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_description_theme() { return array( 'hs_content_taxonomy_description_formatter_hierarchical_text' => array( 'arguments' => array('element' => NULL), 'function' => 'theme_hs_content_taxonomy_description_formatter_hierarchical', ), 'hs_content_taxonomy_description_formatter_hierarchical_links' => array( 'arguments' => array('element' => NULL), 'function' => 'theme_hs_content_taxonomy_description_formatter_hierarchical', ), 'hs_content_taxonomy_description_row' => array( 'arguments' => array('row' => NULL, 'type' => NULL), ), ); } /** * Implementation of hook_field_formatter_info(). */ function hs_content_taxonomy_description_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_description_row($row, $type) { $separator = ''; $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 = ''; // Depending on which formatter is active, create links or use labels. switch ($type) { case 'hierarchical_links': $line .= l($term->name . HS_CONTENT_TAXONOMY_DESCRIPTION_SEP . $term->description, taxonomy_term_path($term), array('rel' => 'tag', 'title' => $term->description)); break; case 'hierarchical_text': $line .= $item['label'] . HS_CONTENT_TAXONOMY_DESCRIPTION_SEP . $term->description; break; } $line .= ''; $items[] = $line; } $output = implode($separator , $items); return $output; } /** * Theme function for HS Content Taxonomy formatters. * */ function theme_hs_content_taxonomy_description_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_description', '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 = ''; // 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_description_hierarchical_select_params() { $params = array( 'vid', // The vocabulary id. 'tid', // The root term's term id. 'depth', // The depth of the tree. ); return $params; } /** * Implementation of hook_hierarchical_select_root_level(). */ function hs_content_taxonomy_description_hierarchical_select_root_level($params) { $tid = $params['tid']; $terms = _hs_taxonomy_hierarchical_select_get_tree($params['vid'], $tid, -1, 1); return _hs_content_taxonomy_description_select_terms_to_options($terms); } /** * Implementation of hook_hierarchical_select_children(). */ function hs_content_taxonomy_description_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_content_taxonomy_description_select_terms_to_options($terms); } /** * Transform an array of terms into an associative array of options, for use * in a select form item. * * @param $terms * An array of term objects. * @return * An associative array of options, keys are tids, values are term names. */ function _hs_content_taxonomy_description_select_terms_to_options($terms) { $options = array(); foreach ($terms as $key => $term) { // Use the translated term when available! $term->name .= HS_CONTENT_TAXONOMY_DESCRIPTION_SEP . $term->description; if (module_exists('i18ntaxonomy') && isset($term->vid) && i18ntaxonomy_vocabulary($term->vid) == I18N_TAXONOMY_LOCALIZE) { $options[$term->tid] = tt("taxonomy:term:$term->tid:name", $term->name); } else { $options[$term->tid] = t($term->name); } } return $options; } /** * Implementation of hook_hierarchical_select_lineage(). */ function hs_content_taxonomy_description_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_description_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_description_term_within_allowed_depth($term->tid, $term->vid, $params['tid'], $params['depth'])); } /** * Implementation of hook_hierarchical_select_item_get_label(). */ function hs_content_taxonomy_description_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_description_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_description_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_description_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_description_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_hsd') { $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/hsd_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_description_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_hsd') { return array($content_type['type'], $field_name); } } } return FALSE; } function _hs_content_taxonomy_description_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]); }