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/preview/vocabulary', 'title' => t('preview 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); } 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_checkbox(t('Related terms'), 'relations', 1, $edit['relations'], t('Allows related terms in this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'related-terms')))); $form .= form_radios(t('Hierarchy'), 'hierarchy', $edit['hierarchy'], array(t('Disabled'), t('Single'), t('Multiple')), t('Allows a tree-like hierarchy between terms of this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'hierarchy')))); $form .= form_checkbox(t('Multiple select'), 'multiple', 1, $edit['multiple'], t('Allows nodes to have more than one term in this vocabulary.')); $form .= form_checkbox(t('Required'), 'required', 1, $edit['required'], t('If enabled, every node must 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) { if (!$edit['nodes']) { $edit['nodes'] = array(); } $data = array('name' => $edit['name'], 'description' => $edit['description'], 'help' => $edit['help'], 'multiple' => $edit['multiple'], 'required' => $edit['required'], 'hierarchy' => $edit['hierarchy'], 'relations' => $edit['relations'], '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' => ''. $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' => ''. $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('Synonyms 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' => ''. $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' => ''. $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' => ''. $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' => ''. $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() { $header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3')); $vocabularies = taxonomy_get_vocabularies(); foreach ($vocabularies as $vocabulary) { if ($vocabulary->module == 'taxonomy') { // only show vocabularies that can be configured through the vocabulary module $types = array(); foreach($vocabulary->nodes as $type) { $node_type = node_invoke($type, 'node_name'); $types[] = $node_type ? $node_type : $type; } $rows[] = array($vocabulary->name, implode(', ', $types), l(t('edit vocabulary'), "admin/taxonomy/edit/vocabulary/$vocabulary->vid"), l(t('add term'), "admin/taxonomy/add/term/$vocabulary->vid"), l(t('preview form'), "admin/taxonomy/preview/vocabulary/$vocabulary->vid")); $tree = taxonomy_get_tree($vocabulary->vid); if ($tree) { unset($data); foreach ($tree as $term) { $data .= _taxonomy_depth($term->depth) .' '. $term->name .' ('. l(t('edit term'), "admin/taxonomy/edit/term/$term->tid") .')
'; } $rows[] = array(array('data' => $data, 'colspan' => '5')); } } } if (!$rows) { $rows[] = array(array('data' => t('No categories available.'), 'colspan' => '5')); } return theme('table', $header, $rows); } /** * 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($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($value = 0, $help = NULL, $name = 'taxonomy') { $vocabularies = taxonomy_get_vocabularies(); $options = array(); foreach ($vocabularies as $vid => $vocabulary) { $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 (!$node->taxonomy) { if ($node->nid) { $terms = array_keys(taxonomy_node_get_terms($node->nid)); } else { $terms = 0; } } 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)) { $result[] = taxonomy_form($vocabulary->vid, $terms, $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]; } /** * Save term associations for a given node. */ function taxonomy_node_save($nid, $terms) { taxonomy_node_delete($nid); 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]; $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 .'][', $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.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.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 '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(); } 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(''); $output = taxonomy_render_nodes(taxonomy_select_nodes($tids, $operator, $depth, TRUE)); $output .= theme('xml_icon', url("taxonomy/term/$str_tids/$depth/feed")); print theme('page', $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); } 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 'preview': $output = taxonomy_form(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(); } print theme('page', $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' => $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 organization of content into categories.'); case 'admin/taxonomy': return t('

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) and taxonomies (controlled vocabularies where relationships are indicated hierarchically). To delete a term choose "edit term". To delete a vocabulary, and all its terms, choose "edit vocabulary".

'); case 'admin/taxonomy/add/vocabulary': return t("

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.

"); case 'admin/help#taxonomy': return t('

Background

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 classification types and insight into the development of the taxonomy module, see this drupal.org discussion.

An example taxonomy: food

Notes

Vocabularies

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\'s sections. For more complex implementations, you might create a hierarchical list of categories such as Food taxonomy shown above.

Setting up a vocabulary

When setting up a controlled vocabulary, if you select the hierarchy option, you will be defining a tree structure of terms, as in a thesaurus. If you select the related terms option, you are allowing the definition of related terms (think see also), as in a thesaurus. Selecting multiple select 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.

When setting up a controlled vocabulary you are asked for:

Adding terms to a vocabulary

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 related terms, hierarchy and multiple select. These options are:

Displaying content organized by terms

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, taxonomy/term/1+2. 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 taxonomy overview 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 any of the given term IDs, or separated by "," to choose content tagged with all 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.

RSS feeds

Every term, or collection of terms, provides an RSS feed to which interested users may subscribe. The URL format for a sample RSS feed is taxonomy/term/1+2/0/feed. These are built just like Taxonomy URLs, but are followed by the word "feed".

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