2 // $Id: taxonomy.module,v 1.330.2.14 2007/12/06 18:16:38 drumm Exp $
6 * Enables the organization of content into categories.
10 * Implementation of hook_perm().
12 function taxonomy_perm() {
13 return array('administer taxonomy');
17 * Implementation of hook_link().
19 * This hook is extended with $type = 'taxonomy terms' to allow themes to
20 * print lists of terms associated with a node. Themes can print taxonomy
23 * if (module_exists('taxonomy')) {
24 * $terms = taxonomy_link('taxonomy terms', $node);
25 * print theme('links', $terms);
28 function taxonomy_link($type, $node = NULL) {
29 if ($type == 'taxonomy terms' && $node != NULL) {
31 if (array_key_exists('taxonomy', $node)) {
32 foreach ($node->taxonomy as $term) {
33 $links['taxonomy_term_'. $term->tid] = array(
34 'title' => $term->name,
35 'href' => taxonomy_term_path($term),
36 'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
41 // We call this hook again because some modules and themes call taxonomy_link('taxonomy terms') directly
42 foreach (module_implements('link_alter') as $module) {
43 $function = $module .'_link_alter';
44 $function($node, $links);
52 * For vocabularies not maintained by taxonomy.module, give the maintaining
53 * module a chance to provide a path for terms in that vocabulary.
58 * An internal Drupal path.
61 function taxonomy_term_path($term) {
62 $vocabulary = taxonomy_get_vocabulary($term->vid);
63 if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
66 return 'taxonomy/term/'. $term->tid;
70 * Implementation of hook_menu().
72 function taxonomy_menu($may_cache) {
76 $items[] = array('path' => 'admin/content/taxonomy',
77 'title' => t('Categories'),
78 'description' => t('Create vocabularies and terms to categorize your content.'),
79 'callback' => 'taxonomy_overview_vocabularies',
80 'access' => user_access('administer taxonomy'));
82 $items[] = array('path' => 'admin/content/taxonomy/list',
84 'type' => MENU_DEFAULT_LOCAL_TASK,
87 $items[] = array('path' => 'admin/content/taxonomy/add/vocabulary',
88 'title' => t('Add vocabulary'),
89 'callback' => 'drupal_get_form',
90 'callback arguments' => array('taxonomy_form_vocabulary'),
91 'access' => user_access('administer taxonomy'),
92 'type' => MENU_LOCAL_TASK);
94 $items[] = array('path' => 'admin/content/taxonomy/edit/vocabulary',
95 'title' => t('Edit vocabulary'),
96 'callback' => 'taxonomy_admin_vocabulary_edit',
97 'access' => user_access('administer taxonomy'),
98 'type' => MENU_CALLBACK);
100 $items[] = array('path' => 'admin/content/taxonomy/edit/term',
101 'title' => t('Edit term'),
102 'callback' => 'taxonomy_admin_term_edit',
103 'access' => user_access('administer taxonomy'),
104 'type' => MENU_CALLBACK);
106 $items[] = array('path' => 'taxonomy/term',
107 'title' => t('Taxonomy term'),
108 'callback' => 'taxonomy_term_page',
109 'access' => user_access('access content'),
110 'type' => MENU_CALLBACK);
112 $items[] = array('path' => 'taxonomy/autocomplete',
113 'title' => t('Autocomplete taxonomy'),
114 'callback' => 'taxonomy_autocomplete',
115 'access' => user_access('access content'),
116 'type' => MENU_CALLBACK);
119 if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'taxonomy' && is_numeric(arg(3))) {
121 $items[] = array('path' => 'admin/content/taxonomy/'. $vid,
122 'title' => t('List terms'),
123 'callback' => 'taxonomy_overview_terms',
124 'callback arguments' => array($vid),
125 'access' => user_access('administer taxonomy'),
126 'type' => MENU_CALLBACK);
128 $items[] = array('path' => 'admin/content/taxonomy/'. $vid .'/list',
129 'title' => t('List'),
130 'type' => MENU_DEFAULT_LOCAL_TASK,
133 $items[] = array('path' => 'admin/content/taxonomy/'. $vid .'/add/term',
134 'title' => t('Add term'),
135 'callback' => 'drupal_get_form',
136 'callback arguments' => array('taxonomy_form_term', $vid),
137 'access' => user_access('administer taxonomy'),
138 'type' => MENU_LOCAL_TASK);
146 * List and manage vocabularies.
148 function taxonomy_overview_vocabularies() {
149 $vocabularies = taxonomy_get_vocabularies();
151 foreach ($vocabularies as $vocabulary) {
153 foreach ($vocabulary->nodes as $type) {
154 $node_type = node_get_types('name', $type);
155 $types[] = $node_type ? check_plain($node_type) : check_plain($type);
157 $rows[] = array('name' => check_plain($vocabulary->name),
158 'type' => implode(', ', $types),
159 'edit' => l(t('edit vocabulary'), "admin/content/taxonomy/edit/vocabulary/$vocabulary->vid"),
160 'list' => l(t('list terms'), "admin/content/taxonomy/$vocabulary->vid"),
161 'add' => l(t('add terms'), "admin/content/taxonomy/$vocabulary->vid/add/term")
165 $rows[] = array(array('data' => t('No categories available.'), 'colspan' => '5'));
167 $header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3'));
169 return theme('table', $header, $rows, array('id' => 'taxonomy'));
173 * Display a tree of all the terms in a vocabulary, with options to edit
176 function taxonomy_overview_terms($vid) {
177 $destination = drupal_get_destination();
179 $header = array(t('Name'), t('Operations'));
180 $vocabulary = taxonomy_get_vocabulary($vid);
182 return drupal_not_found();
185 drupal_set_title(check_plain($vocabulary->name));
186 $start_from = $_GET['page'] ? $_GET['page'] : 0;
187 $total_entries = 0; // total count for pager
188 $page_increment = 25; // number of tids per page
189 $displayed_count = 0; // number of tids shown
191 if ($vocabulary->tags) {
192 // We are not calling taxonomy_get_tree because that might fail with a big
193 // number of tags in the freetagging vocabulary.
194 $results = pager_query(db_rewrite_sql('SELECT t.*, h.parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $page_increment, 0, NULL, $vid);
195 while ($term = db_fetch_object($results)) {
197 l($term->name, "taxonomy/term/$term->tid"),
198 l(t('edit'), "admin/content/taxonomy/edit/term/$term->tid", array(), $destination),
203 $tree = taxonomy_get_tree($vocabulary->vid);
204 foreach ($tree as $term) {
205 $total_entries++; // we're counting all-totals, not displayed
206 if (($start_from && ($start_from * $page_increment) >= $total_entries) || ($displayed_count == $page_increment)) {
209 $rows[] = array(str_repeat('--', $term->depth) .' '. l($term->name, "taxonomy/term/$term->tid"), l(t('edit'), "admin/content/taxonomy/edit/term/$term->tid", array(), $destination));
210 $displayed_count++; // we're counting tids displayed
214 $rows[] = array(array('data' => t('No terms available.'), 'colspan' => '2'));
217 $GLOBALS['pager_page_array'][] = $start_from; // FIXME
218 $GLOBALS['pager_total'][] = intval($total_entries / $page_increment) + 1; // FIXME
221 $output .= theme('table', $header, $rows, array('id' => 'taxonomy'));
222 if ($vocabulary->tags || $total_entries >= $page_increment) {
223 $output .= theme('pager', NULL, $page_increment);
230 * Display form for adding and editing vocabularies.
232 function taxonomy_form_vocabulary($edit = array()) {
233 $form['name'] = array('#type' => 'textfield',
234 '#title' => t('Vocabulary name'),
235 '#default_value' => $edit['name'],
237 '#description' => t('The name for this vocabulary. Example: "Topic".'),
240 $form['description'] = array('#type' => 'textarea',
241 '#title' => t('Description'),
242 '#default_value' => $edit['description'],
243 '#description' => t('Description of the vocabulary; can be used by modules.'),
245 $form['help'] = array('#type' => 'textfield',
246 '#title' => t('Help text'),
248 '#default_value' => $edit['help'],
249 '#description' => t('Instructions to present to the user when choosing a term.'),
251 $form['nodes'] = array('#type' => 'checkboxes',
252 '#title' => t('Types'),
253 '#default_value' => $edit['nodes'],
254 '#options' => array_map('check_plain', node_get_types('names')),
255 '#description' => t('A list of node types you want to associate with this vocabulary.'),
258 $form['hierarchy'] = array('#type' => 'radios',
259 '#title' => t('Hierarchy'),
260 '#default_value' => $edit['hierarchy'],
261 '#options' => array(t('Disabled'), t('Single'), t('Multiple')),
262 '#description' => 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'))),
264 $form['relations'] = array('#type' => 'checkbox',
265 '#title' => t('Related terms'),
266 '#default_value' => $edit['relations'],
267 '#description' => t('Allows <a href="@help-url">related terms</a> in this vocabulary.', array('@help-url' => url('admin/help/taxonomy', NULL, NULL, 'related-terms'))),
269 $form['tags'] = array('#type' => 'checkbox',
270 '#title' => t('Free tagging'),
271 '#default_value' => $edit['tags'],
272 '#description' => t('Content is categorized by typing terms instead of choosing from a list.'),
274 $form['multiple'] = array('#type' => 'checkbox',
275 '#title' => t('Multiple select'),
276 '#default_value' => $edit['multiple'],
277 '#description' => t('Allows nodes to have more than one term from this vocabulary (always true for free tagging).'),
279 $form['required'] = array('#type' => 'checkbox',
280 '#title' => t('Required'),
281 '#default_value' => $edit['required'],
282 '#description' => t('If enabled, every node <strong>must</strong> have at least one term in this vocabulary.'),
284 $form['weight'] = array('#type' => 'weight',
285 '#title' => t('Weight'),
286 '#default_value' => $edit['weight'],
287 '#description' => t('In listings, the heavier vocabularies will sink and the lighter vocabularies will be positioned nearer the top.'),
290 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
292 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
293 $form['vid'] = array('#type' => 'value', '#value' => $edit['vid']);
294 $form['module'] = array('#type' => 'value', '#value' => $edit['module']);
300 * Accept the form submission for a vocabulary and save the results.
302 function taxonomy_form_vocabulary_submit($form_id, $form_values) {
303 // Fix up the nodes array to remove unchecked nodes.
304 $form_values['nodes'] = array_filter($form_values['nodes']);
305 switch (taxonomy_save_vocabulary($form_values)) {
307 drupal_set_message(t('Created new vocabulary %name.', array('%name' => $form_values['name'])));
308 watchdog('taxonomy', t('Created new vocabulary %name.', array('%name' => $form_values['name'])), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/vocabulary/'. $form_values['vid']));
311 drupal_set_message(t('Updated vocabulary %name.', array('%name' => $form_values['name'])));
312 watchdog('taxonomy', t('Updated vocabulary %name.', array('%name' => $form_values['name'])), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/vocabulary/'. $form_values['vid']));
315 return 'admin/content/taxonomy';
318 function taxonomy_save_vocabulary(&$edit) {
319 $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
321 if ($edit['vid'] && $edit['name']) {
322 db_query("UPDATE {vocabulary} SET name = '%s', description = '%s', help = '%s', multiple = %d, required = %d, hierarchy = %d, relations = %d, tags = %d, weight = %d, module = '%s' WHERE vid = %d", $edit['name'], $edit['description'], $edit['help'], $edit['multiple'], $edit['required'], $edit['hierarchy'], $edit['relations'], $edit['tags'], $edit['weight'], isset($edit['module']) ? $edit['module'] : 'taxonomy', $edit['vid']);
323 db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d", $edit['vid']);
324 foreach ($edit['nodes'] as $type => $selected) {
325 db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
327 module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
328 $status = SAVED_UPDATED;
330 else if ($edit['vid']) {
331 $status = taxonomy_del_vocabulary($edit['vid']);
334 $edit['vid'] = db_next_id('{vocabulary}_vid');
335 db_query("INSERT INTO {vocabulary} (vid, name, description, help, multiple, required, hierarchy, relations, tags, weight, module) VALUES (%d, '%s', '%s', '%s', %d, %d, %d, %d, %d, %d, '%s')", $edit['vid'], $edit['name'], $edit['description'], $edit['help'], $edit['multiple'], $edit['required'], $edit['hierarchy'], $edit['relations'], $edit['tags'], $edit['weight'], isset($edit['module']) ? $edit['module'] : 'taxonomy');
336 foreach ($edit['nodes'] as $type => $selected) {
337 db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
339 module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
349 * Delete a vocabulary.
354 * Constant indicating items were deleted.
356 function taxonomy_del_vocabulary($vid) {
357 $vocabulary = (array) taxonomy_get_vocabulary($vid);
359 db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
360 db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
361 $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
362 while ($term = db_fetch_object($result)) {
363 taxonomy_del_term($term->tid);
366 module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
370 return SAVED_DELETED;
373 function taxonomy_vocabulary_confirm_delete($vid) {
374 $vocabulary = taxonomy_get_vocabulary($vid);
376 $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
377 $form['vid'] = array('#type' => 'value', '#value' => $vid);
378 $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
379 return confirm_form($form,
380 t('Are you sure you want to delete the vocabulary %title?',
381 array('%title' => $vocabulary->name)),
382 'admin/content/taxonomy',
383 t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
388 function taxonomy_vocabulary_confirm_delete_submit($form_id, $form_values) {
389 $status = taxonomy_del_vocabulary($form_values['vid']);
390 drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_values['name'])));
391 watchdog('taxonomy', t('Deleted vocabulary %name.', array('%name' => $form_values['name'])), WATCHDOG_NOTICE);
392 return 'admin/content/taxonomy';
395 function taxonomy_form_term($vocabulary_id, $edit = array()) {
396 $vocabulary = taxonomy_get_vocabulary($vocabulary_id);
397 drupal_set_title(check_plain($vocabulary->name));
399 $form['name'] = array(
400 '#type' => 'textfield',
401 '#title' => t('Term name'),
402 '#default_value' => $edit['name'],
404 '#description' => t('The name of this term.'),
405 '#required' => TRUE);
407 $form['description'] = array(
408 '#type' => 'textarea',
409 '#title' => t('Description'),
410 '#default_value' => $edit['description'],
411 '#description' => t('A description of the term.'));
413 if ($vocabulary->hierarchy) {
414 $parent = array_keys(taxonomy_get_parents($edit['tid']));
415 $children = taxonomy_get_tree($vocabulary_id, $edit['tid']);
417 // A term can't be the child of itself, nor of its children.
418 foreach ($children as $child) {
419 $exclude[] = $child->tid;
421 $exclude[] = $edit['tid'];
423 if ($vocabulary->hierarchy == 1) {
424 $form['parent'] = _taxonomy_term_select(t('Parent'), 'parent', $parent, $vocabulary_id, l(t('Parent term'), 'admin/help/taxonomy', NULL, NULL, 'parent') .'.', 0, '<'. t('root') .'>', $exclude);
426 elseif ($vocabulary->hierarchy == 2) {
427 $form['parent'] = _taxonomy_term_select(t('Parents'), 'parent', $parent, $vocabulary_id, l(t('Parent terms'), 'admin/help/taxonomy', NULL, NULL, 'parent') .'.', 1, '<'. t('root') .'>', $exclude);
431 if ($vocabulary->relations) {
432 $form['relations'] = _taxonomy_term_select(t('Related terms'), 'relations', array_keys(taxonomy_get_related($edit['tid'])), $vocabulary_id, NULL, 1, '<'. t('none') .'>', array($edit['tid']));
435 $form['synonyms'] = array(
436 '#type' => 'textarea',
437 '#title' => t('Synonyms'),
438 '#default_value' => implode("\n", taxonomy_get_synonyms($edit['tid'])),
439 '#description' => t('<a href="@help-url">Synonyms</a> of this term, one synonym per line.', array('@help-url' => url('admin/help/taxonomy', NULL, NULL, 'synonyms'))));
440 $form['weight'] = array(
442 '#title' => t('Weight'),
443 '#default_value' => $edit['weight'],
444 '#description' => t('In listings, the heavier terms will sink and the lighter terms will be positioned nearer the top.'));
445 $form['vid'] = array(
447 '#value' => $vocabulary->vid);
448 $form['submit'] = array(
450 '#value' => t('Submit'));
453 $form['delete'] = array(
455 '#value' => t('Delete'));
456 $form['tid'] = array(
458 '#value' => $edit['tid']);
461 $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
468 * Accept the form submission for a taxonomy term and save the result.
470 function taxonomy_form_term_submit($form_id, $form_values) {
471 switch (taxonomy_save_term($form_values)) {
473 drupal_set_message(t('Created new term %term.', array('%term' => $form_values['name'])));
474 watchdog('taxonomy', t('Created new term %term.', array('%term' => $form_values['name'])), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/term/'. $form_values['tid']));
477 drupal_set_message(t('Updated term %term.', array('%term' => $form_values['name'])));
478 watchdog('taxonomy', t('Updated term %term.', array('%term' => $form_values['name'])), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/term/'. $form_values['tid']));
481 return 'admin/content/taxonomy';
485 * Helper function for taxonomy_form_term_submit().
487 * @param $form_values
489 * Status constant indicating if term was inserted or updated.
491 function taxonomy_save_term(&$form_values) {
492 if ($form_values['tid'] && $form_values['name']) {
493 db_query("UPDATE {term_data} SET name = '%s', description = '%s', weight = %d WHERE tid = %d", $form_values['name'], $form_values['description'], $form_values['weight'], $form_values['tid']);
495 $status = SAVED_UPDATED;
497 else if ($form_values['tid']) {
498 return taxonomy_del_term($form_values['tid']);
501 $form_values['tid'] = db_next_id('{term_data}_tid');
502 db_query("INSERT INTO {term_data} (tid, name, description, vid, weight) VALUES (%d, '%s', '%s', %d, %d)", $form_values['tid'], $form_values['name'], $form_values['description'], $form_values['vid'], $form_values['weight']);
507 db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $form_values['tid'], $form_values['tid']);
508 if ($form_values['relations']) {
509 foreach ($form_values['relations'] as $related_id) {
510 if ($related_id != 0) {
511 db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $form_values['tid'], $related_id);
516 db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $form_values['tid']);
517 if (!isset($form_values['parent']) || empty($form_values['parent'])) {
518 $form_values['parent'] = array(0);
520 if (is_array($form_values['parent'])) {
521 foreach ($form_values['parent'] as $parent) {
522 if (is_array($parent)) {
523 foreach ($parent as $tid) {
524 db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $tid);
528 db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $parent);
533 db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $form_values['parent']);
536 db_query('DELETE FROM {term_synonym} WHERE tid = %d', $form_values['tid']);
537 if ($form_values['synonyms']) {
538 foreach (explode ("\n", str_replace("\r", '', $form_values['synonyms'])) as $synonym) {
540 db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $form_values['tid'], chop($synonym));
546 module_invoke_all('taxonomy', $hook, 'term', $form_values);
560 * Status constant indicating deletion.
562 function taxonomy_del_term($tid) {
565 $children_tids = $orphans = array();
566 foreach ($tids as $tid) {
567 // See if any of the term's children are about to be become orphans:
568 if ($children = taxonomy_get_children($tid)) {
569 foreach ($children as $child) {
570 // If the term has multiple parents, we don't delete it.
571 $parents = taxonomy_get_parents($child->tid);
572 if (count($parents) == 1) {
573 $orphans[] = $child->tid;
578 $term = (array) taxonomy_get_term($tid);
580 db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
581 db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
582 db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
583 db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
584 db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
586 module_invoke_all('taxonomy', 'delete', 'term', $term);
594 return SAVED_DELETED;
597 function taxonomy_term_confirm_delete($tid) {
598 $term = taxonomy_get_term($tid);
600 $form['type'] = array('#type' => 'value', '#value' => 'term');
601 $form['name'] = array('#type' => 'value', '#value' => $term->name);
602 $form['tid'] = array('#type' => 'value', '#value' => $tid);
603 return confirm_form($form,
604 t('Are you sure you want to delete the term %title?',
605 array('%title' => $term->name)),
606 'admin/content/taxonomy',
607 t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
612 function taxonomy_term_confirm_delete_submit($form_id, $form_values) {
613 taxonomy_del_term($form_values['tid']);
614 drupal_set_message(t('Deleted term %name.', array('%name' => $form_values['name'])));
615 watchdog('taxonomy', t('Deleted term %name.', array('%name' => $form_values['name'])), WATCHDOG_NOTICE);
616 return 'admin/content/taxonomy';
620 * Generate a form element for selecting terms from a vocabulary.
622 function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') {
623 $vocabulary = taxonomy_get_vocabulary($vid);
624 $help = ($help) ? $help : $vocabulary->help;
627 if (!$vocabulary->multiple) {
628 $blank = ($vocabulary->required) ? t('- Please choose -') : t('- None selected -');
631 return _taxonomy_term_select(check_plain($vocabulary->name), $name, $value, $vid, $help, intval($vocabulary->multiple), $blank);
635 * Generate a set of options for selecting a term from all vocabularies.
637 function taxonomy_form_all($free_tags = 0) {
638 $vocabularies = taxonomy_get_vocabularies();
640 foreach ($vocabularies as $vid => $vocabulary) {
641 if ($vocabulary->tags && !$free_tags) { continue; }
642 $tree = taxonomy_get_tree($vid);
643 if ($tree && (count($tree) > 0)) {
644 $options[$vocabulary->name] = array();
645 foreach ($tree as $term) {
646 $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name;
654 * Return an array of all vocabulary objects.
657 * If set, return only those vocabularies associated with this node type.
659 function taxonomy_get_vocabularies($type = NULL) {
661 $result = db_query(db_rewrite_sql("SELECT v.vid, 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", 'v', 'vid'), $type);
664 $result = db_query(db_rewrite_sql('SELECT v.*, n.type FROM {vocabulary} v LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid ORDER BY v.weight, v.name', 'v', 'vid'));
667 $vocabularies = array();
668 $node_types = array();
669 while ($voc = db_fetch_object($result)) {
670 $node_types[$voc->vid][] = $voc->type;
672 $voc->nodes = $node_types[$voc->vid];
673 $vocabularies[$voc->vid] = $voc;
676 return $vocabularies;
680 * Implementation of hook_form_alter().
681 * Generate a form for selecting terms to associate with a node.
683 function taxonomy_form_alter($form_id, &$form) {
684 if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
685 $node = $form['#node'];
687 if (!isset($node->taxonomy)) {
689 $terms = taxonomy_node_get_terms($node->nid);
696 $terms = $node->taxonomy;
699 $c = db_query(db_rewrite_sql("SELECT v.* FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s' ORDER BY v.weight, v.name", 'v', 'vid'), $node->type);
701 while ($vocabulary = db_fetch_object($c)) {
702 if ($vocabulary->tags) {
703 $typed_terms = array();
704 foreach ($terms as $term) {
705 // Extract terms belonging to the vocabulary in question.
706 if ($term->vid == $vocabulary->vid) {
708 // Commas and quotes in terms are special cases, so encode 'em.
709 if (strpos($term->name, ',') !== FALSE || strpos($term->name, '"') !== FALSE) {
710 $term->name = '"'.str_replace('"', '""', $term->name).'"';
713 $typed_terms[] = $term->name;
716 $typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
718 if ($vocabulary->help) {
719 $help = $vocabulary->help;
722 $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
724 $form['taxonomy']['tags'][$vocabulary->vid] = array('#type' => 'textfield',
725 '#title' => $vocabulary->name,
726 '#description' => $help,
727 '#required' => $vocabulary->required,
728 '#default_value' => $typed_string,
729 '#autocomplete_path' => 'taxonomy/autocomplete/'. $vocabulary->vid,
730 '#weight' => $vocabulary->weight,
735 // Extract terms belonging to the vocabulary in question.
736 $default_terms = array();
737 foreach ($terms as $term) {
738 if ($term->vid == $vocabulary->vid) {
739 $default_terms[$term->tid] = $term;
742 $form['taxonomy'][$vocabulary->vid] = taxonomy_form($vocabulary->vid, array_keys($default_terms), $vocabulary->help);
743 $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
744 $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
747 if (is_array($form['taxonomy']) && !empty($form['taxonomy'])) {
748 if (count($form['taxonomy']) > 1) { // Add fieldset only if form has more than 1 element.
749 $form['taxonomy'] += array(
750 '#type' => 'fieldset',
751 '#title' => t('Categories'),
752 '#collapsible' => TRUE,
753 '#collapsed' => FALSE,
756 $form['taxonomy']['#weight'] = -3;
757 $form['taxonomy']['#tree'] = TRUE;
763 * Find all terms associated with the given node, within one vocabulary.
765 function taxonomy_node_get_terms_by_vocabulary($nid, $vid, $key = 'tid') {
766 $result = db_query(db_rewrite_sql('SELECT t.tid, t.* FROM {term_data} t INNER JOIN {term_node} r ON r.tid = t.tid WHERE t.vid = %d AND r.nid = %d ORDER BY weight', 't', 'tid'), $vid, $nid);
768 while ($term = db_fetch_object($result)) {
769 $terms[$term->$key] = $term;
775 * Find all terms associated with the given node, ordered by vocabulary and term weight.
777 function taxonomy_node_get_terms($nid, $key = 'tid') {
780 if (!isset($terms[$nid][$key])) {
781 $result = db_query(db_rewrite_sql('SELECT t.* FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.nid = %d ORDER BY v.weight, t.weight, t.name', 't', 'tid'), $nid);
782 $terms[$nid][$key] = array();
783 while ($term = db_fetch_object($result)) {
784 $terms[$nid][$key][$term->$key] = $term;
787 return $terms[$nid][$key];
791 * Make sure incoming vids are free tagging enabled.
793 function taxonomy_node_validate(&$node) {
794 if ($node->taxonomy) {
795 $terms = $node->taxonomy;
796 if ($terms['tags']) {
797 foreach ($terms['tags'] as $vid => $vid_value) {
798 $vocabulary = taxonomy_get_vocabulary($vid);
799 if (!$vocabulary->tags) {
800 // see form_get_error $key = implode('][', $element['#parents']);
801 // on why this is the key
802 form_set_error("taxonomy][tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => $vocabulary->name)));
810 * Save term associations for a given node.
812 function taxonomy_node_save($nid, $terms) {
813 taxonomy_node_delete($nid);
815 // Free tagging vocabularies do not send their tids in the form,
816 // so we'll detect them here and process them independently.
817 if (isset($terms['tags'])) {
818 $typed_input = $terms['tags'];
819 unset($terms['tags']);
821 foreach ($typed_input as $vid => $vid_value) {
822 // This regexp allows the following types of user input:
823 // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
824 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
825 preg_match_all($regexp, $vid_value, $matches);
826 $typed_terms = array_unique($matches[1]);
829 foreach ($typed_terms as $typed_term) {
830 // If a user has escaped a term (to demonstrate that it is a group,
831 // or includes a comma or quote character), we remove the escape
832 // formatting so to save the term into the database as the user intends.
833 $typed_term = str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term));
834 $typed_term = trim($typed_term);
835 if ($typed_term == "") { continue; }
837 // See if the term exists in the chosen vocabulary
838 // and return the tid; otherwise, add a new record.
839 $possibilities = taxonomy_get_term_by_name($typed_term);
840 $typed_term_tid = NULL; // tid match, if any.
841 foreach ($possibilities as $possibility) {
842 if ($possibility->vid == $vid) {
843 $typed_term_tid = $possibility->tid;
847 if (!$typed_term_tid) {
848 $edit = array('vid' => $vid, 'name' => $typed_term);
849 $status = taxonomy_save_term($edit);
850 $typed_term_tid = $edit['tid'];
853 // Defend against duplicate, differently cased tags
854 if (!isset($inserted[$typed_term_tid])) {
855 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $typed_term_tid);
856 $inserted[$typed_term_tid] = TRUE;
862 if (is_array($terms)) {
863 foreach ($terms as $term) {
864 if (is_array($term)) {
865 foreach ($term as $tid) {
867 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $tid);
871 else if (is_object($term)) {
872 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term->tid);
875 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term);
882 * Remove associations of a node to its terms.
884 function taxonomy_node_delete($nid) {
885 db_query('DELETE FROM {term_node} WHERE nid = %d', $nid);
889 * Implementation of hook_node_type().
891 function taxonomy_node_type($op, $info) {
892 if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) {
893 db_query("UPDATE {vocabulary_node_types} SET type = '%s' WHERE type = '%s'", $info->type, $info->old_type);
895 elseif ($op == 'delete') {
896 db_query("DELETE FROM {vocabulary_node_types} WHERE type = '%s'", $info->type);
901 * Find all term objects related to a given term ID.
903 function taxonomy_get_related($tid, $key = 'tid') {
905 $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);
907 while ($term = db_fetch_object($result)) {
908 $related[$term->$key] = $term;
918 * Find all parents of a given term ID.
920 function taxonomy_get_parents($tid, $key = 'tid') {
922 $result = db_query(db_rewrite_sql('SELECT t.tid, t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON h.parent = t.tid WHERE h.tid = %d ORDER BY weight, name', 't', 'tid'), $tid);
924 while ($parent = db_fetch_object($result)) {
925 $parents[$parent->$key] = $parent;
935 * Find all ancestors of a given term ID.
937 function taxonomy_get_parents_all($tid) {
940 $parents[] = taxonomy_get_term($tid);
942 while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
943 $parents = array_merge($parents, $parent);
951 * Find all children of a term ID.
953 function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
955 $result = db_query(db_rewrite_sql('SELECT t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON h.tid = t.tid WHERE t.vid = %d AND h.parent = %d ORDER BY weight, name', 't', 'tid'), $vid, $tid);
958 $result = db_query(db_rewrite_sql('SELECT t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON h.tid = t.tid WHERE parent = %d ORDER BY weight, name', 't', 'tid'), $tid);
961 while ($term = db_fetch_object($result)) {
962 $children[$term->$key] = $term;
968 * Create a hierarchical representation of a vocabulary.
971 * Which vocabulary to generate the tree for.
974 * The term ID under which to generate the tree. If 0, generate the tree
975 * for the entire vocabulary.
981 * The number of levels of the tree to return. Leave NULL to return all levels.
984 * An array of all term objects in the tree. Each term object is extended
985 * to have "depth" and "parents" attributes in addition to its normal ones.
986 * Results are statically cached.
988 function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
989 static $children, $parents, $terms;
993 // We cache trees, so it's not CPU-intensive to call get_tree() on a term
994 // and its children, too.
995 if (!isset($children[$vid])) {
996 $children[$vid] = array();
998 $result = db_query(db_rewrite_sql('SELECT t.tid, t.*, parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $vid);
999 while ($term = db_fetch_object($result)) {
1000 $children[$vid][$term->parent][] = $term->tid;
1001 $parents[$vid][$term->tid][] = $term->parent;
1002 $terms[$vid][$term->tid] = $term;
1006 $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
1007 if ($children[$vid][$parent]) {
1008 foreach ($children[$vid][$parent] as $child) {
1009 if ($max_depth > $depth) {
1010 $term = drupal_clone($terms[$vid][$child]);
1011 $term->depth = $depth;
1012 // The "parent" attribute is not useful, as it would show one parent only.
1013 unset($term->parent);
1014 $term->parents = $parents[$vid][$child];
1017 if ($children[$vid][$child]) {
1018 $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $depth, $max_depth));
1024 return $tree ? $tree : array();
1028 * Return an array of synonyms of the given term ID.
1030 function taxonomy_get_synonyms($tid) {
1032 $result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d', $tid);
1033 while ($synonym = db_fetch_array($result)) {
1034 $synonyms[] = $synonym['name'];
1036 return $synonyms ? $synonyms : array();
1044 * Return the term object that has the given string as a synonym.
1046 function taxonomy_get_synonym_root($synonym) {
1047 return db_fetch_object(db_query("SELECT * FROM {term_synonym} s, {term_data} t WHERE t.tid = s.tid AND s.name = '%s'", $synonym));
1051 * Count the number of published nodes classified by a term.
1057 * The $node->type. If given, taxonomy_term_count_nodes only counts
1058 * nodes of $type that are classified with the term $tid.
1061 * An integer representing a number of nodes.
1062 * Results are statically cached.
1064 function taxonomy_term_count_nodes($tid, $type = 0) {
1067 if (!isset($count[$type])) {
1068 // $type == 0 always evaluates TRUE if $type is a string
1069 if (is_numeric($type)) {
1070 $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'));
1073 $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 AND n.type = '%s' GROUP BY t.tid"), $type);
1075 while ($term = db_fetch_object($result)) {
1076 $count[$type][$term->tid] = $term->c;
1080 foreach (_taxonomy_term_children($tid) as $c) {
1081 $children_count += taxonomy_term_count_nodes($c, $type);
1083 return $count[$type][$tid] + $children_count;
1087 * Helper for taxonomy_term_count_nodes(). Used to find out
1088 * which terms are children of a parent term.
1091 * The parent term's ID
1094 * An array of term IDs representing the children of $tid.
1095 * Results are statically cached.
1098 function _taxonomy_term_children($tid) {
1101 if (!isset($children)) {
1102 $result = db_query('SELECT tid, parent FROM {term_hierarchy}');
1103 while ($term = db_fetch_object($result)) {
1104 $children[$term->parent][] = $term->tid;
1107 return $children[$tid] ? $children[$tid] : array();
1111 * Try to map a string to an existing term, as for glossary use.
1113 * Provides a case-insensitive and trimmed mapping, to maximize the
1114 * likelihood of a successful match.
1117 * Name of the term to search for.
1120 * An array of matching term objects.
1122 function taxonomy_get_term_by_name($name) {
1123 $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t WHERE LOWER('%s') LIKE LOWER(t.name)", 't', 'tid'), trim($name));
1125 while ($term = db_fetch_object($db_result)) {
1133 * Return the vocabulary object matching a vocabulary ID.
1136 * The vocabulary's ID
1139 * The vocabulary object with all of its metadata.
1140 * Results are statically cached.
1142 function taxonomy_get_vocabulary($vid) {
1143 static $vocabularies = array();
1145 if (!array_key_exists($vid, $vocabularies)) {
1146 $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);
1147 $node_types = array();
1148 while ($voc = db_fetch_object($result)) {
1149 $node_types[] = $voc->type;
1151 $voc->nodes = $node_types;
1152 $vocabularies[$vid] = $voc;
1156 return $vocabularies[$vid];
1160 * Return the term object matching a term ID.
1166 * A term object. Results are statically cached.
1168 function taxonomy_get_term($tid) {
1169 static $terms = array();
1171 if (!isset($terms[$tid])) {
1172 $terms[$tid] = db_fetch_object(db_query('SELECT * FROM {term_data} WHERE tid = %d', $tid));
1175 return $terms[$tid];
1178 function _taxonomy_term_select($title, $name, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) {
1179 $tree = taxonomy_get_tree($vocabulary_id);
1183 $options[''] = $blank;
1186 foreach ($tree as $term) {
1187 if (!in_array($term->tid, $exclude)) {
1188 $choice = new stdClass();
1189 $choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
1190 $options[] = $choice;
1195 return array('#type' => 'select',
1197 '#default_value' => $value,
1198 '#options' => $options,
1199 '#description' => $description,
1200 '#multiple' => $multiple,
1201 '#size' => $multiple ? min(9, count($options)) : 0,
1203 '#theme' => 'taxonomy_term_select',
1208 * We use the default selection field for choosing terms.
1210 function theme_taxonomy_term_select($element) {
1211 return theme('select', $element);
1215 * Finds all nodes that match selected taxonomy conditions.
1218 * An array of term IDs to match.
1220 * How to interpret multiple IDs in the array. Can be "or" or "and".
1222 * How many levels deep to traverse the taxonomy tree. Can be a nonnegative
1225 * Whether the nodes are to be used with a pager (the case on most Drupal
1226 * pages) or not (in an XML feed, for example).
1228 * The order clause for the query that retrieve the nodes.
1230 * A resource identifier pointing to the query results.
1232 function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC') {
1233 if (count($tids) > 0) {
1234 // For each term ID, generate an array of descendant term IDs to the right depth.
1235 $descendant_tids = array();
1236 if ($depth === 'all') {
1239 foreach ($tids as $index => $tid) {
1240 $term = taxonomy_get_term($tid);
1241 $tree = taxonomy_get_tree($term->vid, $tid, -1, $depth);
1242 $descendant_tids[] = array_merge(array($tid), array_map('_taxonomy_get_tid_from_term', $tree));
1245 if ($operator == 'or') {
1246 $args = call_user_func_array('array_merge', $descendant_tids);
1247 $placeholders = implode(',', array_fill(0, count($args), '%d'));
1248 $sql = 'SELECT DISTINCT(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 ('. $placeholders .') AND n.status = 1 ORDER BY '. $order;
1249 $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. $placeholders .') AND n.status = 1';
1255 foreach ($descendant_tids as $index => $tids) {
1256 $joins .= ' INNER JOIN {term_node} tn'. $index .' ON n.nid = tn'. $index .'.nid';
1257 $placeholders = implode(',', array_fill(0, count($tids), '%d'));
1258 $wheres .= ' AND tn'. $index .'.tid IN ('. $placeholders .')';
1259 $args = array_merge($args, $tids);
1261 $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n '. $joins .' WHERE n.status = 1 '. $wheres .' ORDER BY '. $order;
1262 $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n '. $joins .' WHERE n.status = 1 '. $wheres;
1264 $sql = db_rewrite_sql($sql);
1265 $sql_count = db_rewrite_sql($sql_count);
1267 $result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count, $args);
1270 $result = db_query_range($sql, $args, 0, variable_get('feed_default_items', 10));
1278 * Accepts the result of a pager_query() call, such as that performed by
1279 * taxonomy_select_nodes(), and formats each node along with a pager.
1281 function taxonomy_render_nodes($result) {
1283 if (db_num_rows($result) > 0) {
1284 while ($node = db_fetch_object($result)) {
1285 $output .= node_view(node_load($node->nid), 1);
1287 $output .= theme('pager', NULL, variable_get('default_nodes_main', 10), 0);
1290 $output .= '<p>'. t('There are currently no posts in this category.') .'</p>';
1296 * Implementation of hook_nodeapi().
1298 function taxonomy_nodeapi($node, $op, $arg = 0) {
1301 $output['taxonomy'] = taxonomy_node_get_terms($node->nid);
1304 taxonomy_node_save($node->nid, $node->taxonomy);
1307 taxonomy_node_save($node->nid, $node->taxonomy);
1310 taxonomy_node_delete($node->nid);
1313 taxonomy_node_validate($node);
1316 return taxonomy_rss_item($node);
1317 case 'update index':
1318 return taxonomy_node_update_index($node);
1323 * Implementation of hook_nodeapi('update_index').
1325 function taxonomy_node_update_index(&$node) {
1327 foreach ($node->taxonomy as $term) {
1328 $output[] = $term->name;
1330 if (count($output)) {
1331 return '<strong>('. implode(', ', $output) .')</strong>';
1336 * Parses a comma or plus separated string of term IDs.
1339 * A string of term IDs, separated by plus or comma.
1340 * comma (,) means AND
1343 * @return an associative array with an operator key (either 'and'
1344 * or 'or') and a tid key containing an array of the term ids.
1346 function taxonomy_terms_parse_string($str_tids) {
1348 if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
1349 $terms['operator'] = 'or';
1350 // The '+' character in a query string may be parsed as ' '.
1351 $terms['tids'] = preg_split('/[+ ]/', $str_tids);
1353 else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
1354 $terms['operator'] = 'and';
1355 $terms['tids'] = explode(',', $str_tids);
1362 * Menu callback; displays all nodes associated with a term.
1364 function taxonomy_term_page($str_tids = '', $depth = 0, $op = 'page') {
1365 $terms = taxonomy_terms_parse_string($str_tids);
1366 if ($terms['operator'] != 'and' && $terms['operator'] != 'or') {
1370 if ($terms['tids']) {
1371 $placeholders = implode(',', array_fill(0, count($terms['tids']), '%d'));
1372 $result = db_query(db_rewrite_sql('SELECT t.tid, t.name FROM {term_data} t WHERE t.tid IN ('. $placeholders .')', 't', 'tid'), $terms['tids']);
1373 $tids = array(); // we rebuild the $tids-array so it only contains terms the user has access to.
1375 while ($term = db_fetch_object($result)) {
1376 $tids[] = $term->tid;
1377 $names[] = $term->name;
1381 $title = check_plain(implode(', ', $names));
1382 drupal_set_title($title);
1386 // Build breadcrumb based on first hierarchy of first term:
1387 $current->tid = $tids[0];
1388 $breadcrumbs = array(array('path' => $_GET['q'], 'title' => $names[0]));
1389 while ($parents = taxonomy_get_parents($current->tid)) {
1390 $current = array_shift($parents);
1391 $breadcrumbs[] = array('path' => 'taxonomy/term/'. $current->tid, 'title' => $current->name);
1393 $breadcrumbs = array_reverse($breadcrumbs);
1394 menu_set_location($breadcrumbs);
1396 $output = taxonomy_render_nodes(taxonomy_select_nodes($tids, $terms['operator'], $depth, TRUE));
1397 drupal_add_feed(url('taxonomy/term/'. $str_tids .'/'. $depth .'/feed'), 'RSS - '. $title);
1402 $term = taxonomy_get_term($tids[0]);
1403 $channel['link'] = url('taxonomy/term/'. $str_tids .'/'. $depth, NULL, NULL, TRUE);
1404 $channel['title'] = variable_get('site_name', 'Drupal') .' - '. $title;
1405 $channel['description'] = $term->description;
1407 $result = taxonomy_select_nodes($tids, $terms['operator'], $depth, FALSE);
1408 node_feed($result, $channel);
1421 * Page to edit a vocabulary.
1423 function taxonomy_admin_vocabulary_edit($vid = NULL) {
1424 if ($_POST['op'] == t('Delete') || $_POST['confirm']) {
1425 return drupal_get_form('taxonomy_vocabulary_confirm_delete', $vid);
1427 if ($vocabulary = (array)taxonomy_get_vocabulary($vid)) {
1428 return drupal_get_form('taxonomy_form_vocabulary', $vocabulary);
1430 return drupal_not_found();
1434 * Page to edit a vocabulary term.
1436 function taxonomy_admin_term_edit($tid) {
1437 if ($_POST['op'] == t('Delete') || $_POST['confirm']) {
1438 return drupal_get_form('taxonomy_term_confirm_delete', $tid);
1440 if ($term = (array)taxonomy_get_term($tid)) {
1441 return drupal_get_form('taxonomy_form_term', $term['vid'], $term);
1443 return drupal_not_found();
1447 * Provides category information for RSS feeds.
1449 function taxonomy_rss_item($node) {
1451 foreach ($node->taxonomy as $term) {
1452 $output[] = array('key' => 'category',
1453 'value' => check_plain($term->name),
1454 'attributes' => array('domain' => url('taxonomy/term/'. $term->tid, NULL, NULL, TRUE)));
1460 * Implementation of hook_help().
1462 function taxonomy_help($section) {
1464 case 'admin/help#taxonomy':
1465 $output = '<p>'. t('The taxonomy module is one of the most popular features because users often want to create categories to organize content by type. A simple example would be organizing a list of music reviews by musical genre.') .'</p>';
1466 $output .= '<p>'. t('Taxonomy is the study of classification. The taxonomy module allows you to define vocabularies (sets of categories) which are used to classify content. The module supports hierarchical classification and association between terms, allowing for truly flexible information retrieval and classification. The taxonomy module 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 view and manage the terms of each vocabulary, click on the associated <em>list terms</em> link. To delete a vocabulary and all its terms, choose <em>edit vocabulary.</em>') .'</p>';
1467 $output .= '<p>'. t('A controlled vocabulary is 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.') .'</p>';
1468 $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@taxonomy">Taxonomy page</a>.', array('@taxonomy' => 'http://drupal.org/handbook/modules/taxonomy/')) .'</p>';
1470 case 'admin/content/taxonomy':
1471 return '<p>'. 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), taxonomies (controlled vocabularies where relationships are indicated hierarchically), and free vocabularies where terms, or tags, are defined during content creation. To view and manage the terms of each vocabulary, click on the associated <em>list terms</em> link. To delete a vocabulary and all its terms, choose "edit vocabulary".') .'</p>';
1472 case 'admin/content/taxonomy/add/vocabulary':
1473 return '<p>'. 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. For more complex implementations, you might create a hierarchical list of categories.") .'</p>';
1478 * Helper function for array_map purposes.
1480 function _taxonomy_get_tid_from_term($term) {
1485 * Helper function for autocompletion
1487 function taxonomy_autocomplete($vid, $string = '') {
1488 // The user enters a comma-separated list of tags. We only autocomplete the last tag.
1489 // This regexp allows the following types of user input:
1490 // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
1491 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
1492 preg_match_all($regexp, $string, $matches);
1493 $array = $matches[1];
1496 $last_string = trim(array_pop($array));
1497 if ($last_string != '') {
1498 $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), $vid, $last_string, 0, 10);
1500 $prefix = count($array) ? implode(', ', $array) .', ' : '';
1503 while ($tag = db_fetch_object($result)) {
1505 // Commas and quotes in terms are special cases, so encode 'em.
1506 if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
1507 $n = '"'. str_replace('"', '""', $tag->name) .'"';
1509 $matches[$prefix . $n] = check_plain($tag->name);
1511 print drupal_to_js($matches);