term_level.module in Term Level Field 7
Field type for referencing terms with a level to an entity.
File
term_level.moduleView source
<?php
/**
* @file
* Field type for referencing terms with a level to an entity.
*/
// term_level_element form type
include_once 'term_level_element.inc';
// We currently only support vocabularies that have 3 (root, 1, 2) levels or
// less.
define('TERM_LEVEL_VOC_MAX_DEPTH', 2);
/**
* Implements hook_field_info().
*/
function term_level_field_info() {
return array(
'term_level' => array(
'label' => 'Term Level Field',
'description' => t('This field references a term with a selectable level.'),
'default_widget' => 'term_level_widget',
'default_formatter' => 'term_level_default',
'settings' => array(
'voc' => '',
'levels' => '',
),
// Properties for Entity Metadata.
'property_type' => 'field_item_term_level',
'property_callbacks' => array(
'term_level_entity_metadata_callback',
),
),
);
}
/**
* Entity metadata callback for term_level settings.
*/
function term_level_entity_metadata_callback(&$info, $entity_type, $field, $instance, $field_type) {
$property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
$property['getter callback'] = 'entity_metadata_field_verbatim_get';
$property['setter callback'] = 'entity_metadata_field_verbatim_set';
unset($property['query callback']);
$property['property info'] = array(
'term' => array(
'type' => 'taxonomy_term',
'label' => t('Taxonomy Term'),
'getter callback' => 'term_level_entity_metadata_field_get_term',
),
'level' => array(
'type' => 'integer',
'label' => t('Level'),
),
);
$property['options list'] = 'entity_metadata_field_options_list';
}
/**
* Getter callback for term in term level field.
*/
function term_level_entity_metadata_field_get_term($item, array $options, $name, $type, $context) {
return $item['tid'];
}
/**
* Implements hook_options_list().
*/
function term_level_options_list($field) {
$levels = _term_level_extract_levels($field['settings']['levels']);
if (module_exists('i18n_field')) {
$levels = i18n_string_translate(array(
'field',
$field['field_name'],
'#levels',
), $levels);
}
return $levels;
}
/**
* Implements hook_field_is_empty().
*/
function term_level_field_is_empty($item, $field) {
if (!is_array($item) || empty($item['tid']) || !isset($item['level']) || $item['level'] === 'none') {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_theme().
*/
function term_level_theme($existing, $type, $theme, $path) {
return array(
'term_level_element' => array(
'render element' => 'element',
),
'term_level_group' => array(
'variables' => array(
'levels' => array(),
'level' => 0,
'term_names' => array(),
),
),
);
}
/**
* Implements hook_field_formatter_info().
*/
function term_level_field_formatter_info() {
return array(
'term_level_default' => array(
'label' => t('Terms with level'),
'field types' => array(
'term_level',
),
),
'term_level_group' => array(
'label' => t('Terms grouped by their level'),
'field types' => array(
'term_level',
),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function term_level_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
drupal_add_css(drupal_get_path('module', 'term_level') . '/term_level.css');
$levels = term_level_options_list($field);
$term_levels = array();
$level_groups = array();
foreach ($items as $delta => $item) {
$term_levels[$item['tid']] = $item['level'];
$level_groups[$item['level']][] = $item['tid'];
}
$terms = taxonomy_term_load_multiple(array_keys($term_levels));
$element = array();
if ($display['type'] == 'term_level_default') {
foreach ($term_levels as $tid => $level_key) {
if (isset($terms[$tid])) {
$term = $terms[$tid];
$element[$tid] = array(
'#markup' => check_plain($term->name) . ': ' . check_plain($levels[$level_key]),
);
}
}
}
elseif ($display['type'] == 'term_level_group') {
krsort($level_groups);
foreach ($level_groups as $level_key => $tids) {
$term_names = array();
foreach ($tids as $tid) {
if (isset($terms[$tid])) {
$term = $terms[$tid];
$term_names[] = check_plain($term->name);
}
}
$element[$level_key] = array(
'#theme' => 'term_level_group',
'#levels' => $levels,
'#level' => $level_key,
'#term_names' => $term_names,
);
}
}
return $element;
}
/**
* Implements hook_field_settings_form().
*/
function term_level_field_settings_form($field, $instance, $has_data) {
$vocabularies = taxonomy_get_vocabularies();
$options = array();
foreach ($vocabularies as $vocabulary) {
$options[$vocabulary->machine_name] = $vocabulary->name;
}
$form['voc'] = array(
'#type' => 'select',
'#title' => t('Vocabulary'),
'#default_value' => $field['settings']['voc'],
'#options' => $options,
'#required' => TRUE,
'#description' => t('The vocabulary which supplies the options for this field. The vocabulary structure either needs to be a flat list or a two-level hierarchy, where the terms get grouped by their parents in the first level.'),
'#disabled' => $has_data,
);
$form['levels'] = array(
'#type' => 'textarea',
'#title' => t('Levels'),
'#default_value' => $field['settings']['levels'],
'#required' => TRUE,
'#description' => t('Specify the term levels for this field. Enter one level per line, in the format level-key|level-label (level-key must be numeric).'),
'#element_validate' => array(
'term_level_field_settings_levels_validate',
),
'#disabled' => $has_data,
);
return $form;
}
/**
* Validates that level settings are correct.
*/
function term_level_field_settings_levels_validate($element, &$form_state) {
$levels = _term_level_extract_levels($element['#value']);
if (count($levels) == 0) {
form_error($element, t('Please enter valid levels.'));
}
foreach ($levels as $key => $label) {
if (!is_numeric($key)) {
form_error($element, t('The level key must be numeric.'));
}
}
}
/**
* Implements hook_i18n_string_list_field_alter().
*/
function term_level_i18n_string_list_field_alter(&$properties, $type, $object) {
if ($type == "field" && $object['type'] == "term_level") {
$levels = _term_level_extract_levels($object['settings']['levels']);
foreach ($levels as $key => $level) {
$properties['field'][$object['field_name']]['#levels'][$key] = array(
'title' => t('Level %name', array(
'%name' => $level,
)),
'string' => $level,
);
}
}
}
/**
* Implements hook_field_widget_info().
*/
function term_level_field_widget_info() {
return array(
'term_level_widget' => array(
'label' => t('Term Level Widget'),
'field types' => array(
'term_level',
),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
),
'settings' => array(
'tag_cloud' => FALSE,
'term_entries_limit_per_group' => FIELD_CARDINALITY_UNLIMITED,
'validate_empty_terms' => FALSE,
),
),
);
}
/**
* Implements hook_widget_settings_form().
*/
function term_level_field_widget_settings_form($field, $instance) {
$settings = $instance['widget']['settings'];
$form['term_entries_limit_per_group'] = array(
'#type' => 'select',
'#title' => t('Number of terms per group'),
'#options' => array(
FIELD_CARDINALITY_UNLIMITED => t('Unlimited'),
) + drupal_map_assoc(range(0, 10)),
'#default_value' => $settings['term_entries_limit_per_group'],
);
$form['tag_cloud'] = array(
'#type' => 'checkbox',
'#title' => t('Enable Tag Cloud'),
'#default_value' => $settings['tag_cloud'],
'#description' => t('If checked, each term group will have a tag cloud with addional terms not listed in the table.'),
);
$form['validate_empty_terms'] = array(
'#type' => 'checkbox',
'#title' => t('Validate empty terms'),
'#default_value' => $settings['validate_empty_terms'],
'#description' => t('If checked, terms with no selected level will throw a validation errror. This forces the user to either select a level or remove the term from the table group.'),
);
return $form;
}
/**
* Implements hook_field_widget_form().
*/
function term_level_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$field_settings = $field['settings'];
$widget_settings = $instance['widget']['settings'];
$voc = taxonomy_vocabulary_machine_name_load($field_settings['voc']);
$levels = term_level_options_list($field);
$default_values = _term_level_extract_default_values($items);
// Submitted values through AJAX.
if (isset($form_state['values']['default_terms'])) {
foreach ($form_state['values']['default_terms'] as $tid => $level) {
$default_values[$tid] = $level;
}
}
// Term added through AJAX.
$added_term = 0;
if (isset($form_state['values']['added_term']) && $form_state['values']['added_term']) {
$added_term = $form_state['values']['added_term'];
}
$groups = _term_level_get_groups($voc->vid, $widget_settings['term_entries_limit_per_group'], $default_values, $added_term);
$element['#element_validate'] = array(
'term_level_widget_validate',
);
$element['#required'] = $instance['required'];
$label = filter_xss_admin($instance['label']);
$label = $instance['required'] ? $label . ' ' . theme('form_required_marker', array()) : $label;
$element['label'] = array(
'#markup' => '<label class="term-level-widget-label">' . $label . '</label>',
);
if (!empty($instance['description'])) {
$element['description'] = array(
'#markup' => '<div class="term-level-widget-description description">' . filter_xss_admin($instance['description'] . '</div>'),
);
}
foreach ($groups['parents'] as $parent => $parent_name) {
$element[] = array(
'#type' => 'term_level',
'#title' => $parent_name,
'#terms' => isset($groups['terms'][$parent]) ? $groups['terms'][$parent] : array(),
'#levels' => $levels,
'#parent' => $parent,
'#vid' => $voc->vid,
'#tag_cloud_terms' => $widget_settings['tag_cloud'] && isset($groups['tag_cloud'][$parent]) ? $groups['tag_cloud'][$parent] : array(),
);
}
return $element;
}
/**
* Extracts values form term level field and sets the value for saving.
*/
function term_level_widget_validate($element, &$form_state) {
$instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
$values = array();
foreach (element_children($element) as $key) {
if (isset($element[$key]['terms']) && is_array($element[$key]['terms'])) {
foreach (element_children($element[$key]['terms']) as $tid) {
$level = $element[$key]['terms'][$tid]['#value'];
if (!is_null($level) && $level != "none") {
$values[] = array(
'tid' => $tid,
'level' => $level,
);
}
elseif (!empty($instance['widget']['settings']['validate_empty_terms']) && is_null($level)) {
$term = $element[$key]['#terms'][$tid];
form_error($element[$key]['terms'][$tid], t('%term has no level specified. Please select one or remove it from the table.', array(
'%term' => $term->name,
)));
}
}
}
}
// Use own required validation here, as the default validation does not work
// here (tag_cloud_term_options always exists).
if ($element['#required'] && !count($values)) {
form_error($element, t('!name field is required.', array(
'!name' => $element['#title'],
)));
}
form_set_value($element, $values, $form_state);
}
/**
* Implements hook_field_widget_error().
*/
function term_level_field_widget_error($element, $error, $form, &$form_state) {
form_error($element, $error['message']);
}
/**
* Extracts levels (level-key => level-label) out of the field settings.
*
* Level-label are not yet sanitized.
*/
function _term_level_extract_levels($level_settings) {
$levels = array();
$list = explode("\n", $level_settings);
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
foreach ($list as $key => $value) {
if (strpos($value, '|') !== FALSE) {
list($level_key, $label) = explode('|', $value);
}
if (isset($level_key) && isset($level_key)) {
$levels[$level_key] = $label;
}
}
return $levels;
}
/**
* Transforms the default values into a more usable structure.
*/
function _term_level_extract_default_values($items = array()) {
$default_values = array();
foreach ($items as $item) {
$default_values[$item['tid']] = $item['level'];
}
return $default_values;
}
/**
* Returns list of terms, group by their parents or voc.
*
* Terms have an row_weight value set, used for ordering the table rows,
* correct ordering is important when rebuilding the form element:
* - default_values are on top (within default_values order by term order)
* - added_terms are always on the bottom (weight 9999)
* - the rest is in between (according to term order)
*
* @return An array containing following entries:
* - 'parents': Each entry represents its own term level table group.
* - 'terms': The terms that should be listed in the table, keyed by the
* table parent.
* - 'tag_cloud': An array keyed by the table parent that either contains the
* entry 'all' with all tag cloud terms, or 'groups' with an additional
* group level for taxonomies with 3 hierarchical levels.
*/
function _term_level_get_groups($vid, $term_entries_limit_per_group, $default_values = array(), $added_term = 0) {
$groups = array();
$groups['terms'] = array();
$groups['parents'] = array();
$groups['tag_cloud'] = array();
$tree = taxonomy_get_tree($vid, 0, TERM_LEVEL_VOC_MAX_DEPTH + 1);
// Check how many levels the vocabulary has. We currently only support
// vocabularies that have 3 (root, 1, 2) levels or less.
$max_depth = 0;
foreach ($tree as $term) {
if ($term->depth > $max_depth) {
$max_depth = $term->depth;
if ($max_depth == TERM_LEVEL_VOC_MAX_DEPTH) {
break;
}
}
}
// Flat vocabulary, use the voc name for the table group.
if ($max_depth == 0) {
$voc = taxonomy_vocabulary_load($vid);
$groups['parents'][0] = $voc->name;
}
$term_entries = 0;
foreach ($tree as $term) {
// Create the groups (only for hierarchical vocs).
if ($max_depth != 0 && $term->depth == 0) {
$groups['parents'][$term->tid] = $term->name;
$term_entries = 0;
}
else {
if ($max_depth != 1 && $term->depth == 1) {
$top_parent = reset($term->parents);
$groups['level_2'][$term->tid] = array(
'term' => $term,
'parent' => $top_parent,
);
$groups['tag_cloud'][$top_parent]['group_terms'][$term->tid] = $term;
}
elseif ($term->depth == $max_depth) {
// Check if the term should be listed in the table:
// - either all terms are shown in table
// - or the limit isn't reached yet
// - or the current term is in the default values and doesn't have the
// level 'none'
// - or the current term has been added from the tag cloud
if ($term_entries_limit_per_group == FIELD_CARDINALITY_UNLIMITED || $term_entries < $term_entries_limit_per_group || in_array($term->tid, array_keys($default_values)) || $term->tid === $added_term) {
if (in_array($term->tid, array_keys($default_values))) {
$term->level = $default_values[$term->tid];
$term->row_weight = -1;
if ($term->level == 'none') {
_term_level_groups_add_to_tag_clould($groups, $term);
}
}
if ($term->tid === $added_term) {
$term->level = NULL;
// List the new term at the bottom of the table.
$term->row_weight = 9999;
}
if (!isset($term->row_weight)) {
$term->row_weight = $term_entries;
}
_term_level_groups_add_to_table($groups, $term);
$term_entries++;
}
else {
_term_level_groups_add_to_tag_clould($groups, $term);
}
}
}
}
return $groups;
}
/**
* Helper function that adds a term to the table structure.
*/
function _term_level_groups_add_to_table(&$groups, $term) {
$term_parent = end($term->parents);
if (isset($groups['level_2'][$term_parent])) {
$term_parent = $groups['level_2'][$term_parent]['parent'];
}
$groups['terms'][$term_parent][$term->tid] = $term;
}
/**
* Helper function that adds a tag cloud term to the array structure.
*/
function _term_level_groups_add_to_tag_clould(&$groups, $term) {
$term_parent = end($term->parents);
if (isset($groups['level_2'][$term_parent])) {
$top_term_parent = $groups['level_2'][$term_parent]['parent'];
$groups['tag_cloud'][$top_term_parent]['groups'][$term_parent][$term->tid] = $term;
}
else {
$groups['tag_cloud'][$term_parent]['all'][$term->tid] = $term;
}
}
/**
* Themes the term_level_group formatter (per level).
*/
function theme_term_level_group(&$variables) {
if (isset($variables['levels'][$variables['level']]) && !empty($variables['term_names'])) {
$markup = '<div class="term-level-field-label term-level-field-label-level-' . $variables['level'] . '">' . check_plain($variables['levels'][$variables['level']]) . ': </div>';
$markup .= '<div class="term-level-field-items term-level-field-items-level-' . $variables['level'] . '">' . implode(', ', $variables['term_names']) . '</div>';
return $markup;
}
}
Functions
Constants
Name | Description |
---|---|
TERM_LEVEL_VOC_MAX_DEPTH |