drupal/modules/taxonomy/taxonomy.module

1246 lines
49 KiB
Plaintext

<?php
// $Id$
/**
* @file
* Enables the organization of content into categories.
*/
/**
* Implementation of hook_perm().
*/
function taxonomy_perm() {
return array('administer taxonomy');
}
/**
* Implementation of hook_link().
*
* This hook is extended with $type = 'taxonomy terms' to allow themes to
* print lists of terms associated with a node. Themes can print taxonomy
* links with:
*
* if (module_exist('taxonomy')) {
* $this->links(taxonomy_link('taxonomy terms', $node));
* }
*/
function taxonomy_link($type, $node = NULL) {
if ($type == 'taxonomy terms' && $node != NULL) {
$links = array();
if (array_key_exists('taxonomy', $node)) {
foreach ($node->taxonomy as $tid) {
$term = taxonomy_get_term($tid);
$links[] = l($term->name, 'taxonomy/term/'. $term->tid);
}
}
else {
$links = array();
foreach (taxonomy_node_get_terms($node->nid) as $term) {
$links[] = l($term->name, 'taxonomy/term/'. $term->tid);
}
}
return $links;
}
}
/**
* Implementation of hook_menu().
*/
function taxonomy_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'admin/taxonomy', 'title' => t('categories'),
'callback' => 'taxonomy_admin',
'access' => user_access('administer taxonomy'));
$items[] = array('path' => 'admin/taxonomy/list', 'title' => t('list'),
'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
$items[] = array('path' => 'admin/taxonomy/add/vocabulary', 'title' => t('add vocabulary'),
'callback' => 'taxonomy_admin',
'access' => user_access('administer taxonomy'),
'type' => MENU_LOCAL_TASK);
$items[] = array('path' => 'admin/taxonomy/edit/vocabulary', 'title' => t('edit vocabulary'),
'callback' => 'taxonomy_admin',
'access' => user_access('administer taxonomy'),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/taxonomy/add/term', 'title' => t('add term'),
'callback' => 'taxonomy_admin',
'access' => user_access('administer taxonomy'),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/taxonomy/edit/term', 'title' => t('edit term'),
'callback' => 'taxonomy_admin',
'access' => user_access('administer taxonomy'),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'taxonomy/term', 'title' => t('taxonomy term'),
'callback' => 'taxonomy_term_page',
'access' => user_access('access content'),
'type' => MENU_CALLBACK);
}
else {
if (is_numeric(arg(2))) {
$items[] = array('path' => 'admin/taxonomy/' . arg(2), 'title' => t('add term'),
'callback' => 'taxonomy_admin',
'access' => user_access('administer taxonomy'),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/taxonomy/' . arg(2) . '/list', 'title' => t('list'),
'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
$items[] = array('path' => 'admin/taxonomy/' . arg(2) . '/add/term', 'title' => t('add term'),
'callback' => 'taxonomy_admin',
'access' => user_access('administer taxonomy'),
'type' => MENU_LOCAL_TASK);
}
}
return $items;
}
function taxonomy_form_vocabulary($edit = array()) {
foreach (node_list() as $type) {
$node_type = node_invoke($type, 'node_name');
$nodes[$type] = $node_type ? $node_type : $type;
}
$form .= form_textfield(t('Vocabulary name'), 'name', $edit['name'], 50, 64, t('The name for this vocabulary. Example: "Topic".'), NULL, TRUE);
$form .= form_textarea(t('Description'), 'description', $edit['description'], 60, 5, t('Description of the vocabulary; can be used by modules.'));
$form .= form_textfield(t('Help text'), 'help', $edit['help'], 50, 255, t('Instructions to present to the user when choosing a term.'));
$form .= form_checkboxes(t('Types'), 'nodes', $edit['nodes'], $nodes, t('A list of node types you want to associate with this vocabulary.'), NULL, TRUE);
$form .= form_radios(t('Hierarchy'), 'hierarchy', $edit['hierarchy'], array(t('Disabled'), t('Single'), t('Multiple')), t('Allows <a href="%help-url">a tree-like hierarchy</a> between terms of this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'hierarchy'))));
$form .= form_checkbox(t('Related terms'), 'relations', 1, $edit['relations'], t('Allows <a href="%help-url">related terms</a> in this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'related-terms'))));
$form .= form_checkbox(t('Free tagging'), 'tags', 1, $edit['tags'], t('Content is categorized by typing terms instead of choosing from a list.'));
$form .= form_checkbox(t('Multiple select'), 'multiple', 1, $edit['multiple'], t('Allows nodes to have more than one term from this vocabulary (always true for free tagging).'));
$form .= form_checkbox(t('Required'), 'required', 1, $edit['required'], t('If enabled, every node <strong>must</strong> have at least one term in this vocabulary.'));
$form .= form_weight(t('Weight'), 'weight', $edit['weight'], 10, t('In listings, the heavier vocabularies will sink and the lighter vocabularies will be positioned nearer the top.'));
$form .= form_submit(t('Submit'));
if ($edit['vid']) {
$form .= form_submit(t('Delete'));
$form .= form_hidden('vid', $edit['vid']);
}
return form($form);
}
function taxonomy_save_vocabulary($edit) {
$edit['nodes'] = ($edit['nodes']) ? $edit['nodes'] : array();
$edit['weight'] = ($edit['weight']) ? $edit['weight'] : 0;
$data = array('name' => $edit['name'], 'description' => $edit['description'], 'help' => $edit['help'], 'multiple' => $edit['multiple'], 'required' => $edit['required'], 'hierarchy' => $edit['hierarchy'], 'relations' => $edit['relations'], 'tags' => $edit['tags'], 'weight' => $edit['weight'], 'module' => isset($edit['module']) ? $edit['module'] : 'taxonomy');
if ($edit['vid'] && $edit['name']) {
db_query('UPDATE {vocabulary} SET '. _taxonomy_prepare_update($data) .' WHERE vid = %d', $edit['vid']);
db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d", $edit['vid']);
foreach ($edit['nodes'] as $type) {
db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
}
module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
$message = t('Updated vocabulary %name.', array('%name' => theme('placeholder', $edit['name'])));
}
else if ($edit['vid']) {
$message = taxonomy_del_vocabulary($edit['vid']);
}
else {
$data['vid'] = $edit['vid'] = db_next_id('{vocabulary}_vid');
db_query('INSERT INTO {vocabulary} '. _taxonomy_prepare_insert($data, 1) .' VALUES '. _taxonomy_prepare_insert($data, 2));
foreach ($edit['nodes'] as $type) {
db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
}
module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
$message = t('Created new vocabulary %name.', array('%name' => theme('placeholder', $edit['name'])));
}
cache_clear_all();
drupal_set_message($message);
return $edit;
}
function taxonomy_del_vocabulary($vid) {
$vocabulary = taxonomy_get_vocabulary($vid);
db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
$result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
while ($term = db_fetch_object($result)) {
taxonomy_del_term($term->tid);
}
module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
cache_clear_all();
return t('deleted vocabulary "%name".', array('%name' => $vocabulary->name));
}
function _taxonomy_confirm_del_vocabulary($vid) {
$vocabulary = taxonomy_get_vocabulary($vid);
$extra = form_hidden('type', 'vocabulary');
$extra .= form_hidden('vid', $vid);
$output = theme('confirm',
t('Are you sure you want to delete the vocabulary %title?', array('%title' => $vocabulary->name)),
'admin/taxonomy',
t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
t('Delete'),
t('Cancel'),
$extra);
return $output;
}
function taxonomy_form_term($edit = array()) {
$vocabulary_id = isset($edit['vid']) ? $edit['vid'] : arg(4);
$vocabulary = taxonomy_get_vocabulary($vocabulary_id);
$form = form_textfield(t('Term name'), 'name', $edit['name'], 50, 64, t('The name for this term. Example: "Linux".'), NULL, TRUE);
$form .= form_textarea(t('Description'), 'description', $edit['description'], 60, 5, t('A description of the term.'));
if ($vocabulary->hierarchy) {
$parent = array_keys(taxonomy_get_parents($edit['tid']));
$children = taxonomy_get_tree($vocabulary_id, $edit['tid']);
// A term can't be the child of itself, nor of its children.
foreach ($children as $child) {
$exclude[] = $child->tid;
}
$exclude[] = $edit['tid'];
if ($vocabulary->hierarchy == 1) {
$form .= _taxonomy_term_select(t('Parent'), 'parent', $parent, $vocabulary_id, l(t('Parent term'), 'admin/help/taxonomy', NULL, NULL, 'parent') .'.', 0, '<'. t('root') .'>', $exclude);
}
elseif ($vocabulary->hierarchy == 2) {
$form .= _taxonomy_term_select(t('Parents'), 'parent', $parent, $vocabulary_id, l(t('Parent terms'), 'admin/help/taxonomy', NULL, NULL, 'parent') .'.', 1, '<'. t('root') .'>', $exclude);
}
}
if ($vocabulary->relations) {
$form .= _taxonomy_term_select(t('Related terms'), 'relations', array_keys(taxonomy_get_related($edit['tid'])), $vocabulary_id, NULL, 1, '<'. t('none') .'>', array($edit['tid']));
}
$form .= form_textarea(t('Synonyms'), 'synonyms', implode("\n", taxonomy_get_synonyms($edit['tid'])), 30, 5, t('<a href="%help-url">Synonyms</a> of this term, one synonym per line.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'synonyms'))));
$form .= form_weight(t('Weight'), 'weight', $edit['weight'], 10, t('In listings, the heavier terms will sink and the lighter terms will be positioned nearer the top.'));
$form .= form_hidden('vid', $vocabulary->vid);
$form .= form_submit(t('Submit'));
if ($edit['tid']) {
$form .= form_submit(t('Delete'));
$form .= form_hidden('tid', $edit['tid']);
}
else {
$form .= form_hidden('destination', $_GET['q']);
}
return form($form);
}
function taxonomy_save_term($edit) {
if ($edit['tid'] && $edit['name']) {
$data = array('name' => $edit['name'], 'description' => $edit['description'], 'weight' => $edit['weight']);
db_query('UPDATE {term_data} SET '. _taxonomy_prepare_update($data) .' WHERE tid = %d', $edit['tid']);
module_invoke_all('taxonomy', 'update', 'term', $edit);
$message = t('The term %term has been updated.', array('%term' => theme('placeholder', $edit['name'])));
}
else if ($edit['tid']) {
return taxonomy_del_term($edit['tid']);
}
else {
$edit['tid'] = db_next_id('{term_data}_tid');
$data = array('tid' => $edit['tid'], 'name' => $edit['name'], 'description' => $edit['description'], 'vid' => $edit['vid'], 'weight' => $edit['weight']);
db_query('INSERT INTO {term_data} '. _taxonomy_prepare_insert($data, 1) .' VALUES '. _taxonomy_prepare_insert($data, 2));
module_invoke_all('taxonomy', 'insert', 'term', $edit);
$message = t('Created new term %term.', array('%term' => theme('placeholder', $edit['name'])));
}
db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $edit['tid'], $edit['tid']);
if ($edit['relations']) {
foreach ($edit['relations'] as $related_id) {
if ($related_id != 0) {
db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $edit['tid'], $related_id);
}
}
}
db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $edit['tid']);
if (!isset($edit['parent'])) {
$edit['parent'] = array(0);
}
if (is_array($edit['parent'])) {
foreach ($edit['parent'] as $parent) {
if (is_array($parent)) {
foreach ($parent as $tid) {
db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $edit['tid'], $tid);
}
}
else {
db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $edit['tid'], $parent);
}
}
}
db_query('DELETE FROM {term_synonym} WHERE tid = %d', $edit['tid']);
if ($edit['synonyms']) {
foreach (explode ("\n", str_replace("\r", '', $edit['synonyms'])) as $synonym) {
if ($synonym) {
db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $edit['tid'], chop($synonym));
}
}
}
cache_clear_all();
drupal_set_message($message);
return $edit;
}
function taxonomy_del_term($tid) {
$tids = array($tid);
while ($tids) {
$children_tids = $orphans = array();
foreach ($tids as $tid) {
// See if any of the term's children are about to be become orphans:
if ($children = taxonomy_get_children($tid)) {
foreach ($children as $child) {
// If the term has multiple parents, we don't delete it.
$parents = taxonomy_get_parents($child->tid);
if (count($parents) == 1) {
$orphans[] = $child->tid;
}
}
}
$term = taxonomy_get_term($tid);
db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
module_invoke_all('taxonomy', 'delete', 'term', $term);
drupal_set_message(t('Deleted term %name.', array('%name' => theme('placeholder', $term->name))));
}
$tids = $orphans;
}
cache_clear_all();
}
function _taxonomy_confirm_del_term($tid) {
$term = taxonomy_get_term($tid);
$extra = form_hidden('type', 'term');
$extra .= form_hidden('tid', $tid);
$output = theme('confirm',
t('Are you sure you want to delete the term %title?', array('%title' => theme('placeholder', $term->name))),
'admin/taxonomy',
t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
t('Delete'),
t('Cancel'),
$extra);
return $output;
}
/**
* Generate a tabular listing of administrative functions for vocabularies.
*/
function taxonomy_overview() {
$vid = arg(2);
// Show all vocabularies and their terms. if vocabulary is "Free tagging",
// don't show terms here, but instruct user to "view terms" instead.
if (!$vid) {
$header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3'));
$vocabularies = taxonomy_get_vocabularies();
foreach ($vocabularies as $vocabulary) {
$types = array();
foreach ($vocabulary->nodes as $type) {
$node_type = node_invoke($type, 'node_name');
$types[] = $node_type ? $node_type : $type;
}
$rows[] = array('<strong>'.check_plain($vocabulary->name).'</strong>', implode(', ', $types), l(t('edit vocabulary'), "admin/taxonomy/edit/vocabulary/$vocabulary->vid"), l(t('add term'), "admin/taxonomy/add/term/$vocabulary->vid"), l(t('view terms'), "admin/taxonomy/$vocabulary->vid"));
// Show terms if non-free.
if (!$vocabulary->tags) {
$tree = taxonomy_get_tree($vocabulary->vid);
if ($tree) {
foreach ($tree as $term) {
$rows[] = array(array('data' => _taxonomy_depth($term->depth) . ' ' . check_plain($term->name), 'class' => 'term'), NULL, NULL, NULL, l(t('edit term'), "admin/taxonomy/edit/term/$term->tid"));
}
}
else {
$rows[] = array(array('data' => t('No terms available.'), 'colspan' => '5', 'class' => 'message'));
}
}
elseif ($vocabulary->tags) {
$rows[] = array(array('data' => t('This is a free tagging vocabulary:') . ' ' . l(t('view terms'), "admin/taxonomy/$vocabulary->vid") . '.', 'colspan' => '5', 'class' => 'message'));
}
}
if (!$rows) {
$rows[] = array(array('data' => t('No categories available.'), 'colspan' => '5', 'class' => 'message'));
}
}
// Free tagging vocabularies get their own terms pager
// since there is a greater chance of 1000+ terms.
else {
$header = array(t('Name'), array('data' => t('Operations'), 'width' => '60'));
$vocabulary = taxonomy_get_vocabulary($vid);
if ($vocabulary->module == 'taxonomy') {
drupal_set_title(check_plain($vocabulary->name));
$start_from = $_GET['from'] ? $_GET['from'] : 0;
$total_entries = 0; // total count for pager
$page_increment = 25; // number of tids per page
$displayed_count = 0; // number of tids shown
$tree = taxonomy_get_tree($vocabulary->vid);
foreach ($tree as $term) {
$total_entries++; // we're counting all-totals, not displayed
if (($start_from && $start_from > $total_entries) || ($displayed_count == $page_increment)) { continue; }
$rows[] = array(_taxonomy_depth($term->depth) . ' ' . check_plain($term->name), l(t('edit term'), "admin/taxonomy/edit/term/$term->tid"));
$displayed_count++; // we're counting tids displayed
}
if (!$rows) {
$rows[] = array(array('data' => t('No terms available.'), 'colspan' => '2'));
}
$GLOBALS['pager_from_array'][] = $start_from;
$GLOBALS['pager_total'][] = $total_entries;
$rows[] = array(array('data' => theme('pager', NULL, $page_increment), 'colspan' => '2'));
}
}
return theme('table', $header, $rows, array('id' => 'taxonomy'));
}
/**
* Generate a form element for selecting terms from a vocabulary.
*/
function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') {
$vocabulary = taxonomy_get_vocabulary($vid);
$help = ($help) ? $help : $vocabulary->help;
if ($vocabulary->required) {
$blank = 0;
}
else {
$blank = '<'. t('none') .'>';
}
return _taxonomy_term_select(check_plain($vocabulary->name), $name, $value, $vid, $help, intval($vocabulary->multiple), $blank);
}
/**
* Generate a set of options for selecting a term from all vocabularies. Can be
* passed to form_select.
*/
function taxonomy_form_all($free_tags = 0) {
$vocabularies = taxonomy_get_vocabularies();
$options = array();
foreach ($vocabularies as $vid => $vocabulary) {
if ($vocabulary->tags && !$free_tags) { continue; }
$tree = taxonomy_get_tree($vid);
$options[$vocabulary->name] = array();
if ($tree) {
foreach ($tree as $term) {
$options[$vocabulary->name][$term->tid] = _taxonomy_depth($term->depth, '-') . $term->name;
}
}
}
return $options;
}
/**
* Return an array of all vocabulary objects.
*
* @param $type
* If set, return only those vocabularies associated with this node type.
*/
function taxonomy_get_vocabularies($type = NULL) {
if ($type) {
$result = db_query("SELECT v.*, n.type FROM {vocabulary} v LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s' ORDER BY v.weight, v.name", $type);
}
else {
$result = db_query('SELECT v.*, n.type FROM {vocabulary} v LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid ORDER BY v.weight, v.name');
}
$vocabularies = array();
$node_types = array();
while ($voc = db_fetch_object($result)) {
$node_types[$voc->vid][] = $voc->type;
unset($voc->type);
$voc->nodes = $node_types[$voc->vid];
$vocabularies[$voc->vid] = $voc;
}
return $vocabularies;
}
/**
* Generate a form for selecting terms to associate with a node.
*/
function taxonomy_node_form($type, $node = '', $help = NULL, $name = 'taxonomy') {
if (!array_key_exists('taxonomy', $node)) {
if ($node->nid) {
$terms = taxonomy_node_get_terms($node->nid);
}
else {
$terms = array();
}
}
else {
$terms = $node->taxonomy;
}
$c = db_query("SELECT v.*, n.type FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s' ORDER BY v.weight, v.name", $type);
while ($vocabulary = db_fetch_object($c)) {
if ($vocabulary->tags) {
$typed_terms = array();
foreach ($terms as $term) {
if ($term->vid == $vocabulary->vid) {
// Commas and quotes in terms are special cases, so encode 'em.
if (preg_match('/,/', $term->name) || preg_match('/"/', $term->name)) {
$term->name = '"'.preg_replace('/"/', '""', $term->name).'"';
}
$typed_terms[] = $term->name;
}
}
$typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
$result[] = form_textfield($vocabulary->name, "$name][tags][". $vocabulary->vid, $typed_string, 50, 100, t('A comma-separated list of terms describing this content (Example: funny, bungie jumping, "Company, Inc.").'), NULL, ($vocabulary->required ? TRUE : FALSE));
}
else {
$ntterms = array_key_exists('taxonomy', $node) ? $terms : array_keys($terms);
$result[] = taxonomy_form($vocabulary->vid, $ntterms, $help, $name);
}
}
return $result ? $result : array();
}
/**
* Find all terms associated to the given node, within one vocabulary.
*/
function taxonomy_node_get_terms_by_vocabulary($nid, $vid, $key = 'tid') {
$result = db_query('SELECT t.* FROM {term_data} t, {term_node} r WHERE t.tid = r.tid AND t.vid = %d AND r.nid = %d ORDER BY weight', $vid, $nid);
$terms = array();
while ($term = db_fetch_object($result)) {
$terms[$term->$key] = $term;
}
return $terms;
}
/**
* Find all terms associated to the given node.
*/
function taxonomy_node_get_terms($nid, $key = 'tid') {
static $terms;
if (!isset($terms[$nid])) {
$result = db_query('SELECT t.* FROM {term_data} t, {term_node} r WHERE r.tid = t.tid AND r.nid = %d ORDER BY weight, name', $nid);
$terms[$nid] = array();
while ($term = db_fetch_object($result)) {
$terms[$nid][$term->$key] = $term;
}
}
return $terms[$nid];
}
/**
* Make sure incoming vids are free tagging enabled.
*/
function taxonomy_node_validate(&$node) {
if ($node->taxonomy) {
$terms = $node->taxonomy;
if ($terms['tags']) {
foreach ($terms['tags'] as $vid => $vid_value) {
$vocabulary = taxonomy_get_vocabulary($vid);
if (!$vocabulary->tags) {
form_set_error("taxonomy[tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => theme('placeholder', $vocabulary->name))));
}
}
}
}
}
/**
* Save term associations for a given node.
*/
function taxonomy_node_save($nid, $terms) {
taxonomy_node_delete($nid);
// Free tagging vocabularies do not send their tids in the form,
// so we'll detect them here and process them independently.
if ($terms['tags']) {
$typed_input = $terms['tags'];
unset($terms['tags']);
foreach ($typed_input as $vid => $vid_value) {
// This regexp allows the following types of user input:
// this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
preg_match_all($regexp, $vid_value, $matches);
$typed_terms = $matches[1];
foreach ($typed_terms as $typed_term) {
// If a user has escaped a term (to demonstrate that it is a group,
// or includes a comma or quote character), we remove the escape
// formatting so to save the term into the DB as the user intends.
$typed_term = str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term));
$typed_term = trim($typed_term);
if ($typed_term == "") { continue; }
// See if the term exists in the chosen vocabulary
// and return the tid, otherwise, add a new record.
$possibilities = taxonomy_get_term_by_name($typed_term);
$typed_term_tid = NULL; // tid match if any.
foreach ($possibilities as $possibility) {
if ($possibility->vid == $vid) {
$typed_term_tid = $possibility->tid;
}
}
if (!$typed_term_tid) {
$new_term = taxonomy_save_term(array('vid' => $vid, 'name' => $typed_term));
$typed_term_tid = $new_term['tid'];
}
db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $typed_term_tid);
}
}
}
if (is_array($terms)) {
foreach ($terms as $term) {
if (is_array($term)) {
foreach ($term as $tid) {
if ($tid) {
db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $tid);
}
}
}
else if ($term) {
db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term);
}
}
}
}
/**
* Remove associations of a node to its terms.
*/
function taxonomy_node_delete($nid) {
db_query('DELETE FROM {term_node} WHERE nid = %d', $nid);
}
/**
* Find all term objects related to a given term ID.
*/
function taxonomy_get_related($tid, $key = 'tid') {
if ($tid) {
$result = db_query('SELECT t.*, tid1, tid2 FROM {term_relation}, {term_data} t WHERE (t.tid = tid1 OR t.tid = tid2) AND (tid1 = %d OR tid2 = %d) AND t.tid != %d ORDER BY weight, name', $tid, $tid, $tid);
$related = array();
while ($term = db_fetch_object($result)) {
$related[$term->$key] = $term;
}
return $related;
}
else {
return array();
}
}
/**
* Find all parents of a given term ID.
*/
function taxonomy_get_parents($tid, $key = 'tid') {
if ($tid) {
$result = db_query('SELECT t.* FROM {term_hierarchy} h, {term_data} t WHERE h.parent = t.tid AND h.tid = %d ORDER BY weight, name', $tid);
$parents = array();
while ($parent = db_fetch_object($result)) {
$parents[$parent->$key] = $parent;
}
return $parents;
}
else {
return array();
}
}
/**
* Find all ancestors of a given term ID.
*/
function taxonomy_get_parents_all($tid) {
$parents = array();
if ($tid) {
$parents[] = taxonomy_get_term($tid);
$n = 0;
while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
$parents = array_merge($parents, $parent);
$n++;
}
}
return $parents;
}
/**
* Find all children of a term ID.
*/
function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
if ($vid) {
$result = db_query('SELECT t.* FROM {term_hierarchy} h, {term_data} t WHERE t.vid = %d AND h.tid = t.tid AND h.parent = %d ORDER BY weight, name', $vid, $tid);
}
else {
$result = db_query('SELECT t.* FROM {term_hierarchy} h, {term_data} t WHERE h.tid = t.tid AND parent = %d ORDER BY weight', $tid);
}
$children = array();
while ($term = db_fetch_object($result)) {
$children[$term->$key] = $term;
}
return $children;
}
/**
* Create a hierarchical representation of a vocabulary.
*
* @param $vid
* Which vocabulary to generate the tree for.
*
* @param $parent
* The term ID under which to generate the tree. If 0, generate the tree
* for the entire vocabulary.
*
* @param $depth
* Internal use only.
*
* @param $max_depth
* The number of levels of the tree to return. Leave NULL to return all levels.
*
* @return
* An array of all term objects in the tree. Each term object is extended
* to have "depth" and "parents" attributes in addition to its normal ones.
*/
function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
static $children, $parents, $terms;
$depth++;
// We cache trees, so it's not CPU-intensive to call get_tree() on a term
// and its children, too.
if (!isset($children[$vid])) {
$children[$vid] = array();
$result = db_query('SELECT t.*, parent FROM {term_data} t, {term_hierarchy} h WHERE t.tid = h.tid AND t.vid = %d ORDER BY weight, name', $vid);
while ($term = db_fetch_object($result)) {
$children[$vid][$term->parent][] = $term->tid;
$parents[$vid][$term->tid][] = $term->parent;
$terms[$vid][$term->tid] = $term;
}
}
$max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
if ($children[$vid][$parent]) {
foreach ($children[$vid][$parent] as $child) {
if ($max_depth > $depth) {
$terms[$vid][$child]->depth = $depth;
// The "parent" attribute is not useful, as it would show one parent only.
unset($terms[$vid][$child]->parent);
$terms[$vid][$child]->parents = $parents[$vid][$child];
$tree[] = $terms[$vid][$child];
if ($children[$vid][$child]) {
$tree = array_merge($tree, taxonomy_get_tree($vid, $child, $depth, $max_depth));
}
}
}
}
return $tree ? $tree : array();
}
/**
* Return an array of synonyms of the given term ID.
*/
function taxonomy_get_synonyms($tid) {
if ($tid) {
$result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d', $tid);
while ($synonym = db_fetch_array($result)) {
$synonyms[] = $synonym['name'];
}
return $synonyms ? $synonyms : array();
}
else {
return array();
}
}
/**
* Return the term object that has the given string as a synonym.
*/
function taxonomy_get_synonym_root($synonym) {
return db_fetch_object(db_query("SELECT * FROM {term_synonym} s, {term_data} t WHERE t.tid = s.tid AND s.name = '%s'", $synonym));
}
/**
* Given a term id, count the number of published nodes in it.
*/
function taxonomy_term_count_nodes($tid, $type = 0) {
static $count;
if (!isset($count[$type])) {
// $type == 0 always evaluates true is $type is a string
if (is_numeric($type)) {
$result = db_query(db_rewrite_sql('SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t INNER JOIN {node} n ON t.nid = n.nid WHERE n.status = 1 GROUP BY t.tid'));
}
else {
$result = db_query(db_rewrite_sql("SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t, {node} n WHERE t.nid = n.nid AND n.status = 1 AND n.type = '%s' GROUP BY t.tid"), $type);
}
while ($term = db_fetch_object($result)) {
$count[$type][$term->tid] = $term->c;
}
}
foreach (_taxonomy_term_children($tid) as $c) {
$children_count += taxonomy_term_count_nodes($c, $type);
}
return $count[$type][$tid] + $children_count;
}
/**
* Helper for taxonomy_term_count_nodes().
*/
function _taxonomy_term_children($tid) {
static $children;
if (!isset($children)) {
$result = db_query('SELECT tid, parent FROM {term_hierarchy}');
while ($term = db_fetch_object($result)) {
$children[$term->parent][] = $term->tid;
}
}
return $children[$tid] ? $children[$tid] : array();
}
/**
* Try to map a string to an existing term, as for glossary use.
*
* Provides a case-insensitive and trimmed mapping, to maximize the
* likelihood of a successful match.
*
* @param name
* Name of the term to search for.
*
* @return
* An array of matching term objects.
*/
function taxonomy_get_term_by_name($name) {
$db_result = db_query("SELECT * FROM {term_data} WHERE LOWER('%s') LIKE LOWER(name)", trim($name));
$result = array();
while ($term = db_fetch_object($db_result)) {
$result[] = $term;
}
return $result;
}
/**
* Return the vocabulary object matching a vocabulary ID.
*/
function taxonomy_get_vocabulary($vid) {
$result = db_query('SELECT v.*, n.type FROM {vocabulary} v LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE v.vid = %d ORDER BY v.weight, v.name', $vid);
$node_types = array();
while ($voc = db_fetch_object($result)) {
$node_types[] = $voc->type;
unset($voc->type);
$voc->nodes = $node_types;
$vocabulary = $voc;
}
return $vocabulary;
}
/**
* Return the term object matching a term ID.
*/
function taxonomy_get_term($tid) {
// simple cache using a static var?
return db_fetch_object(db_query('SELECT * FROM {term_data} WHERE tid = %d', $tid));
}
function _taxonomy_term_select($title, $name, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) {
$tree = taxonomy_get_tree($vocabulary_id);
$options = array();
if ($blank) {
$options[0] = $blank;
}
if ($tree) {
foreach ($tree as $term) {
if (!in_array($term->tid, $exclude)) {
$options[$term->tid] = _taxonomy_depth($term->depth, '-') . $term->name;
}
}
if (!$blank && !$value) {
// required but without a predefined value, so set first as predefined
$value = $tree[0]->tid;
}
}
return form_select($title, $name . ($multiple ? '' : ']['), $value, $options, $description, $multiple ? 'size="'. min(12, count($options)) .'"' : 0, $multiple);
}
function _taxonomy_depth($depth, $graphic = '--') {
for ($n = 0; $n < $depth; $n++) {
$result .= $graphic;
}
return $result;
}
function _taxonomy_prepare_update($data) {
foreach ($data as $key => $value) {
$q[] = "$key = '". str_replace('%', '%%', db_escape_string($value)) ."'";
}
$result = implode(', ', $q);
return $result;
}
function _taxonomy_prepare_insert($data, $stage) {
if ($stage == 1) {
$result = implode(', ', array_keys($data));
}
else {
foreach (array_values($data) as $value) {
//Escape strings, but not integers
if (is_int($value)) {
$q[] = $value;
}
else {
$q[] = "'". str_replace('%', '%%', db_escape_string($value)) ."'";
}
}
$result = implode(', ', $q);
}
return "($result)";
}
/**
* Finds all nodes that match selected taxonomy conditions.
*
* @param $tids
* An array of term IDs to match.
* @param $operator
* How to interpret multiple IDs in the array. Can be "or" or "and".
* @param $depth
* How many levels deep to traverse the taxonomy tree. Can be a nonnegative
* integer or "all".
* @param $pager
* Whether the nodes are to be used with a pager (the case on most Drupal
* pages) or not (in an XML feed, for example).
* @return
* A resource identifier pointing to the query results.
*/
function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE) {
if (count($tids) > 0) {
// For each term ID, generate an array of descendant term IDs to the right depth.
$descendant_tids = array();
if ($depth === 'all') {
$depth = NULL;
}
foreach ($tids as $index => $tid) {
$term = taxonomy_get_term($tid);
$tree = taxonomy_get_tree($term->vid, $tid, -1, $depth);
$descendant_tids[] = array_merge(array($tid), array_map('_taxonomy_get_tid_from_term', $tree));
}
if ($operator == 'or') {
$str_tids = implode(',', call_user_func_array('array_merge', $descendant_tids));
$sql = 'SELECT n.nid, n.sticky, n.title, n.created FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. $str_tids .') AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC';
$sql_count = 'SELECT COUNT(n.nid) FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. $str_tids .') AND n.status = 1';
}
else {
$joins = '';
$wheres = '';
foreach ($descendant_tids as $index => $tids) {
$joins .= ' INNER JOIN {term_node} tn'. $index .' ON n.nid = tn'. $index .'.nid';
$wheres .= ' AND tn'. $index .'.tid IN ('. implode(',', $tids) .')';
}
$sql = 'SELECT n.nid, n.sticky, n.title, n.created FROM {node} n '. $joins .' WHERE n.status = 1 '. $wheres .' ORDER BY n.sticky DESC, n.created DESC';
$sql_count = 'SELECT COUNT(n.nid) FROM {node} n '. $joins .' WHERE n.status = 1 ' . $wheres;
}
$sql = db_rewrite_sql($sql);
$sql_count = db_rewrite_sql($sql_count);
if ($pager) {
$result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count);
}
else {
$result = db_query_range($sql, 0, 15);
}
}
return $result;
}
/**
* Accepts the result of a pager_query() call, such as that performed by
* taxonomy_select_nodes(), and formats each node along with a pager.
*/
function taxonomy_render_nodes($result) {
if (db_num_rows($result) > 0) {
while ($node = db_fetch_object($result)) {
$output .= node_view(node_load(array('nid' => $node->nid)), 1);
}
$output .= theme('pager', NULL, variable_get('default_nodes_main', 10), 0);
}
else {
$output .= t('There are currently no posts in this category.');
}
return $output;
}
/**
* Implementation of hook_nodeapi().
*/
function taxonomy_nodeapi($node, $op, $arg = 0) {
switch ($op) {
case 'insert':
taxonomy_node_save($node->nid, $node->taxonomy);
break;
case 'update':
taxonomy_node_save($node->nid, $node->taxonomy);
break;
case 'delete':
taxonomy_node_delete($node->nid);
break;
case 'validate':
taxonomy_node_validate($node);
break;
case 'rss item':
return taxonomy_rss_item($node);
break;
}
}
/**
* Menu callback; displays all nodes associated with a term.
*/
function taxonomy_term_page($str_tids = '', $depth = 0, $op = 'page') {
if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
$operator = 'or';
// The '+' character in a query string may be parsed as ' '.
$tids = preg_split('/[+ ]/', $str_tids);
}
else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
$operator = 'and';
$tids = explode(',', $str_tids);
}
else {
drupal_not_found();
}
// Needed for '+' to show up in RSS discovery URLs
$rss_tids = urlencode($str_tids);
if ($tids) {
// Build title:
$result = db_query('SELECT name FROM {term_data} WHERE tid IN (%s)', implode(',', $tids));
$names = array();
while ($term = db_fetch_object($result)) {
$names[] = $term->name;
}
if ($names) {
drupal_set_title($title = implode(', ', $names));
switch ($op) {
case 'page':
// Build breadcrumb based on first hierarchy of first term:
$current->tid = $tids[0];
$breadcrumbs = array(array('path' => $_GET['q']));
while ($parents = taxonomy_get_parents($current->tid)) {
$current = array_shift($parents);
$breadcrumbs[] = array('path' => 'taxonomy/term/'. $current->tid, 'title' => $current->name);
}
$breadcrumbs = array_reverse($breadcrumbs);
menu_set_location($breadcrumbs);
drupal_set_html_head('<link rel="alternate" type="application/rss+xml" title="RSS - '. $title .'" href="'. url('taxonomy/term/'. $rss_tids .'/'. $depth .'/feed') .'" />');
$output = taxonomy_render_nodes(taxonomy_select_nodes($tids, $operator, $depth, TRUE));
$output .= theme('xml_icon', url("taxonomy/term/$rss_tids/$depth/feed"));
return $output;
break;
case 'feed':
$term = taxonomy_get_term($tids[0]);
$channel['link'] = url('taxonomy/term/'. $str_tids .'/'. $depth, NULL, NULL, TRUE);
$channel['title'] = variable_get('site_name', 'drupal') .' - '. $title;
$channel['description'] = $term->description;
$result = taxonomy_select_nodes($tids, $operator, $depth, FALSE);
node_feed($result, $channel);
break;
default:
drupal_not_found();
}
}
else {
drupal_not_found();
}
}
}
/**
* Menu callback; dispatches to the proper taxonomy administration function.
*/
function taxonomy_admin() {
$op = $_POST['op'];
$edit = $_POST['edit'];
if (empty($op)) {
$op = arg(2);
}
// Special block for our "add term" menu localtask. this covers
// the use of the "add term" LOCAL_TASK on a "view terms" page.
if (is_numeric(arg(2)) && arg(3) == 'add' && arg(4) == 'term') {
if ($op != t('Submit')) {
$output = taxonomy_form_term(array('vid' => arg(2)));
return $output;
}
else {
taxonomy_save_term($edit);
drupal_goto('admin/taxonomy');
}
}
switch ($op) {
case 'add':
if (arg(3) == 'vocabulary') {
$output = taxonomy_form_vocabulary();
}
else if (arg(3) == 'term') {
$output = taxonomy_form_term();
}
break;
case 'edit':
if (arg(3) == 'vocabulary') {
$output = taxonomy_form_vocabulary(object2array(taxonomy_get_vocabulary(arg(4))));
}
else if (arg(3) == 'term') {
$output = taxonomy_form_term(object2array(taxonomy_get_term(arg(4))));
}
break;
case t('Delete'):
if (!$edit['confirm']) {
if (arg(3) == 'vocabulary') {
$output = _taxonomy_confirm_del_vocabulary($edit['vid']);
}
else {
$output = _taxonomy_confirm_del_term($edit['tid']);
}
break;
}
else {
$edit['name'] = 0;
// fall through:
}
case t('Submit'):
if (arg(3) == 'vocabulary') {
taxonomy_save_vocabulary($edit);
}
else {
taxonomy_save_term($edit);
}
drupal_goto('admin/taxonomy');
default:
$output = taxonomy_overview();
}
return $output;
}
/**
* Provides category information for rss feeds
*/
function taxonomy_rss_item($node) {
$output = array();
$terms = taxonomy_node_get_terms($node->nid);
foreach ($terms as $term) {
$output[] = array('key' => 'category',
'value' => check_plain($term->name),
'attributes' => array('domain' => url('taxonomy/term/'.$term->tid, NULL, NULL, TRUE)));
}
return $output;
}
/**
* Implementation of hook_help().
*/
function taxonomy_help($section) {
switch ($section) {
case 'admin/modules#description':
return t('Enables the categorization of content.');
case 'admin/taxonomy':
return t('<p>The taxonomy module allows you to classify content into categories and subcategories; it allows multiple lists of categories for classification (controlled vocabularies) and offers the possibility of creating thesauri (controlled vocabularies that indicate the relationship of terms), taxonomies (controlled vocabularies where relationships are indicated hierarchically), and free vocabularies where terms, or tags, are defined during content creation. To delete a term, choose "edit term". To delete a vocabulary and all its terms, choose "edit vocabulary".</p>');
case 'admin/taxonomy/add/vocabulary':
return t("<p>When you create a controlled vocabulary you are creating a set of terms to use for describing content (known as descriptors in indexing lingo). Drupal allows you to describe each piece of content (blog, story, etc.) using one or many of these terms. For simple implementations, you might create a set of categories without subcategories, similar to Slashdot.org's or Kuro5hin.org's sections. For more complex implementations, you might create a hierarchical list of categories.</p>");
case 'admin/help#taxonomy':
return t('
<h3>Background</h3>
<p>Taxonomy is the study of classification. Drupal\'s taxonomy module allows you to define vocabularies which are used to classify content. The module supports hierarchical classification and association between terms, allowing for truly flexible information retrieval and classification. For more details about <a href="%classification-types">classification types</a> and insight into the development of the taxonomy module, see this <a href="%drupal-dis">drupal.org discussion</a>.</p>
<h3>An example taxonomy: food</h3>
<ul><li>Dairy<ul><li>Milk</li></ul></li><li>Drink<ul><li>Alcohol<ul><li>Beer</li><li>Wine</li></ul></li><li>Pop</li><li>Milk</li></ul></li><li>Meat<ul><li>Beef</li><li>Chicken</li><li>Lamb</li></ul></li><li>Spices<ul><li>Sugar</li></ul></li></ul>
<p><strong>Notes</strong></p><ul><li>The term <em>Milk</em> appears within both <em>Dairy</em> and <em>Drink</em>. This is an example of <em>multiple parents</em> for a term.</li><li>In Drupal the order of siblings (e.g. <em>Beef</em>, <em>Chicken</em>, <em>Lamb</em>) in a vocabulary may be controlled with the <em>weight</em> parameter.</li></ul>
<h3>Vocabularies</h3>
<p>When you create a controlled vocabulary you are creating a set of terms to use for describing content (known as descriptors in indexing lingo). Drupal allows you to describe each piece of content (blog, story, etc.) using one or many of these terms. For simple implementations, you might create a set of categories without subcategories, similar to <a href="%slashdot">Slashdot</a>\'s sections. For more complex implementations, you might create a hierarchical list of categories such as <em>Food</em> taxonomy shown above.</p>
<h4>Setting up a vocabulary</h4>
<p>When setting up a controlled vocabulary, if you select the <em>hierarchy</em> option, you will be defining a tree structure of terms, as in a thesaurus. If you select the <em>related terms</em> option, you are allowing the definition of related terms (think <em>see also</em>), as in a thesaurus. Selecting <em>multiple select</em> will allow you to describe a piece of content using more than one term. That content will then appear on each term\'s page, increasing the chance that a user will find it.</p>
<p>When setting up a controlled vocabulary you are asked for: <ul>
<li><strong>Vocabulary name</strong>: The name for this vocabulary. Example: <em>Dairy</em>.</li>
<li><strong>Description</strong>: Description of the vocabulary. This can be used by modules and feeds.</li>
<li><strong>Types</strong>: The list of content types you want to associate this vocabulary with. Some available types are blog, book, forum, page, and story.</li>
<li><a id="hierarchy"></a><strong>Hierarchy</strong>: Allows a tree-like vocabulary, as in our <em>Foods</em> example above.</li>
<li><a id="related-terms"></a><strong>Related terms</strong>: Allows relationships between terms within this vocabulary. Think of these as <em>see also</em> references.</li>
<li><strong>Free tagging</strong>: Content is categorized at time of creation by typing comma-separated terms instead of choosing from a pre-defined list. This is helpful if you\'d like to define and expand the vocabulary during content creation, as opposed to deciding all terms beforehand and then choosing from a controlled list. Administrators may also edit and modify the vocabulary through the normal administrative screens.</li>
<li><strong>Multiple select</strong>: Allows pieces of content to be described using more than one term. Content may then appear on multiple taxonomy pages.</li>
<li><strong>Required</strong>: If selected, each piece of content must have a term in this vocabulary associated with it.</li>
<li><strong>Weight</strong>: The overall weight for this vocabulary in listings with multiple vocabularies.</li>
</ul></p>
<h4>Adding terms to a vocabulary</h4>
<p>Once done defining the vocabulary, you have to add terms to it to make it useful. The options you see when adding a term to a vocabulary will depend on what you selected for <em>related terms</em>, <em>hierarchy</em> and <em>multiple select</em>. These options are:</p>
<p><ul>
<li><strong>Term name</strong>: The name for this term. Example: <em>Milk</em>.</li>
<li><strong>Description</strong>: Description of the term that may be used by modules and feeds. This is synonymous with a "scope note".</li>
<li><strong><a id="parent"></a>Parent</strong>: Select the term under which this term is a subset -- the branch of the hierarchy that this term belongs under. This is also known as the "Broader term" indicator used in thesauri.</li>
<li><strong><a id="synonyms"></a>Synonyms</strong>: Enter synonyms for this term, one synonym per line. Synonyms can be used for variant spellings, acronyms, and other terms that have the same meaning as the added term, but which are not explicitly listed in this vocabulary (i.e. <em>unauthorized terms</em>).</li>
<li><strong>Weight</strong>: The weight is used to sort the terms of this vocabulary.</li>
</ul></p>
<h3><a id="taxonomy-url"></a>Displaying content organized by terms</h3>
<p>In order to view the content associated with a term or a collection of terms, you should browse to a properly formed Taxonomy URL. For example, <a href="%taxo-example">taxonomy/term/1+2</a>. Taxonomy URLs always contain one or more term IDs at the end of the URL. You may learn the term ID for a given term by hovering over that term in the <a href="%taxo-overview">taxonomy overview</a> page and noting the number at the end or the URL. To build a Taxonomy URL start with "taxonomy/term/". Then list the term IDs, separated by "+" to choose content tagged with <strong>any</strong> of the given term IDs, or separated by "," to choose content tagged with <strong>all</strong> of the given term IDs. In other words, "+" is less specific than ",". Finally, you may optionally specify a "depth" in the vocabulary hierarchy. This defaults to "0", which means only the explicitly listed terms are searched. A positive number indicates the number of additional levels of the tree to search. You may also use the value "all", which means that all descendant terms are searched.</p>
<h3>RSS feeds</h3>
<p>Every term, or collection of terms, provides an <a href="%userland-rss">RSS</a> feed to which interested users may subscribe. The URL format for a sample RSS feed is <a href="%sample-rss">taxonomy/term/1+2/0/feed</a>. These are built just like <a href="%taxo-help">Taxonomy URLs</a>, but are followed by the word "feed".</p>', array('%classification-types' => 'http://www.eleganthack.com/archives/002165.html', '%drupal-dis' => 'http://drupal.org/node/55', '%slashdot' => 'http://slashdot.com/', '%taxo-example' => url('taxonomy/term/1+2'), '%taxo-overview' => url('admin/taxonomy'), '%userland-rss' => 'http://backend.userland.com/stories/rss', '%sample-rss' => url('taxonomy/term/1+2/feed'), '%taxo-help' => url('admin/help/taxonomy', NULL, 'taxonomy-url')));
}
}
/**
* Helper function for array_map purposes.
*/
function _taxonomy_get_tid_from_term($term) {
return $term->tid;
}
?>