2 // $Id: taxonomy.module 144 2007-03-28 07:52:20Z thierry $
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_exist('taxonomy')) {
24 * $this->links(taxonomy_link('taxonomy terms', $node));
27 function taxonomy_link($type, $node = NULL) {
28 if ($type == 'taxonomy terms' && $node != NULL) {
30 if (array_key_exists('taxonomy', $node)) {
31 foreach ($node->taxonomy as $term) {
32 $links[] = l($term->name, taxonomy_term_path($term), array('rel' => 'tag', 'title' => strip_tags($term->description)));
39 function taxonomy_term_path($term) {
40 $vocabulary = taxonomy_get_vocabulary($term->vid);
41 if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
44 return 'taxonomy/term/'. $term->tid;
48 * Implementation of hook_menu().
50 function taxonomy_menu($may_cache) {
54 $items[] = array('path' => 'admin/taxonomy',
55 'title' => t('categories'),
56 'callback' => 'taxonomy_overview_vocabularies',
57 'access' => user_access('administer taxonomy'));
59 $items[] = array('path' => 'admin/taxonomy/list',
61 'type' => MENU_DEFAULT_LOCAL_TASK,
64 $items[] = array('path' => 'admin/taxonomy/add/vocabulary',
65 'title' => t('add vocabulary'),
66 'callback' => 'taxonomy_admin_vocabulary_edit',
67 'access' => user_access('administer taxonomy'),
68 'type' => MENU_LOCAL_TASK);
70 $items[] = array('path' => 'admin/taxonomy/edit/vocabulary',
71 'title' => t('edit vocabulary'),
72 'callback' => 'taxonomy_admin_vocabulary_edit',
73 'access' => user_access('administer taxonomy'),
74 'type' => MENU_CALLBACK);
76 $items[] = array('path' => 'admin/taxonomy/edit/term',
77 'title' => t('edit term'),
78 'callback' => 'taxonomy_admin_term_edit',
79 'access' => user_access('administer taxonomy'),
80 'type' => MENU_CALLBACK);
82 $items[] = array('path' => 'taxonomy/term',
83 'title' => t('taxonomy term'),
84 'callback' => 'taxonomy_term_page',
85 'access' => user_access('access content'),
86 'type' => MENU_CALLBACK);
88 $items[] = array('path' => 'taxonomy/autocomplete',
89 'title' => t('autocomplete taxonomy'),
90 'callback' => 'taxonomy_autocomplete',
91 'access' => user_access('access content'),
92 'type' => MENU_CALLBACK);
95 if (is_numeric(arg(2))) {
96 $items[] = array('path' => 'admin/taxonomy/' . arg(2),
97 'title' => t('list terms'),
98 'callback' => 'taxonomy_overview_terms',
99 'callback arguments' => array(arg(2)),
100 'access' => user_access('administer taxonomy'),
101 'type' => MENU_CALLBACK);
103 $items[] = array('path' => 'admin/taxonomy/' . arg(2) . '/list',
104 'title' => t('list'),
105 'type' => MENU_DEFAULT_LOCAL_TASK,
108 $items[] = array('path' => 'admin/taxonomy/' . arg(2) . '/add/term',
109 'title' => t('add term'),
110 'callback' => 'taxonomy_form_term',
111 'callback arguments' => array(array('vid' => arg(2))),
112 'access' => user_access('administer taxonomy'),
113 'type' => MENU_LOCAL_TASK);
121 * List and manage vocabularies.
123 function taxonomy_overview_vocabularies() {
124 $vocabularies = taxonomy_get_vocabularies();
126 foreach ($vocabularies as $vocabulary) {
128 foreach ($vocabulary->nodes as $type) {
129 $node_type = node_get_name($type);
130 $types[] = $node_type ? $node_type : $type;
132 $rows[] = array('name' => check_plain($vocabulary->name),
133 'type' => implode(', ', $types),
134 'edit' => l(t('edit vocabulary'), "admin/taxonomy/edit/vocabulary/$vocabulary->vid"),
135 'list' => l(t('list terms'), "admin/taxonomy/$vocabulary->vid"),
136 'add' => l(t('add terms'), "admin/taxonomy/$vocabulary->vid/add/term")
140 $rows[] = array(array('data' => t('No categories available.'), 'colspan' => '5', 'class' => 'message'));
142 $header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3'));
144 return theme('table', $header, $rows, array('id' => 'taxonomy'));
148 * Display a tree of all the terms in a vocabulary, with options to edit
151 function taxonomy_overview_terms($vid) {
152 $destination = drupal_get_destination();
154 $header = array(t('Name'), t('Operations'));
155 $vocabulary = taxonomy_get_vocabulary($vid);
157 drupal_set_title(check_plain($vocabulary->name));
158 $start_from = $_GET['page'] ? $_GET['page'] : 0;
159 $total_entries = 0; // total count for pager
160 $page_increment = 25; // number of tids per page
161 $displayed_count = 0; // number of tids shown
163 $tree = taxonomy_get_tree($vocabulary->vid);
164 foreach ($tree as $term) {
165 $total_entries++; // we're counting all-totals, not displayed
166 if (($start_from && ($start_from * $page_increment) >= $total_entries) || ($displayed_count == $page_increment)) { continue; }
167 $rows[] = array(_taxonomy_depth($term->depth) . ' ' . l($term->name, "taxonomy/term/$term->tid"), l(t('edit'), "admin/taxonomy/edit/term/$term->tid", array(), $destination));
168 $displayed_count++; // we're counting tids displayed
172 $rows[] = array(array('data' => t('No terms available.'), 'colspan' => '2'));
175 $GLOBALS['pager_page_array'][] = $start_from; // FIXME
176 $GLOBALS['pager_total'][] = intval($total_entries / $page_increment) + 1; // FIXME
178 if ($total_entries >= $page_increment) {
179 $rows[] = array(array('data' => theme('pager', NULL, $page_increment), 'colspan' => '2'));
182 return theme('table', $header, $rows, array('id' => 'taxonomy'));
186 * Display form for adding and editing vocabularies.
188 function taxonomy_form_vocabulary($edit = array()) {
189 $form['name'] = array('#type' => 'textfield',
190 '#title' => t('Vocabulary name'),
191 '#default_value' => $edit['name'],
193 '#description' => t('The name for this vocabulary. Example: "Topic".'),
196 $form['description'] = array('#type' => 'textarea',
197 '#title' => t('Description'),
198 '#default_value' => $edit['description'],
199 '#description' => t('Description of the vocabulary; can be used by modules.'),
201 $form['help'] = array('#type' => 'textfield',
202 '#title' => t('Help text'),
203 '#default_value' => $edit['help'],
204 '#description' => t('Instructions to present to the user when choosing a term.'),
206 $form['nodes'] = array('#type' => 'checkboxes',
207 '#title' => t('Types'),
208 '#default_value' => $edit['nodes'],
209 '#options' => node_get_types(),
210 '#description' => t('A list of node types you want to associate with this vocabulary.'),
213 $form['hierarchy'] = array('#type' => 'radios',
214 '#title' => t('Hierarchy'),
215 '#default_value' => $edit['hierarchy'],
216 '#options' => array(t('Disabled'), t('Single'), t('Multiple')),
217 '#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'))),
219 $form['relations'] = array('#type' => 'checkbox',
220 '#title' => t('Related terms'),
221 '#default_value' => $edit['relations'],
222 '#description' => t('Allows <a href="%help-url">related terms</a> in this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'related-terms'))),
224 $form['tags'] = array('#type' => 'checkbox',
225 '#title' => t('Free tagging'),
226 '#default_value' => $edit['tags'],
227 '#description' => t('Content is categorized by typing terms instead of choosing from a list.'),
229 $form['multiple'] = array('#type' => 'checkbox',
230 '#title' => t('Multiple select'),
231 '#default_value' => $edit['multiple'],
232 '#description' => t('Allows nodes to have more than one term from this vocabulary (always true for free tagging).'),
234 $form['required'] = array('#type' => 'checkbox',
235 '#title' => t('Required'),
236 '#default_value' => $edit['required'],
237 '#description' => t('If enabled, every node <strong>must</strong> have at least one term in this vocabulary.'),
239 $form['weight'] = array('#type' => 'weight',
240 '#title' => t('Weight'),
241 '#default_value' => $edit['weight'],
242 '#description' => t('In listings, the heavier vocabularies will sink and the lighter vocabularies will be positioned nearer the top.'),
245 // Add extra vocabulary form elements.
246 $extra = module_invoke_all('taxonomy', 'form', 'vocabulary');
247 if (is_array($extra)) {
248 foreach ($extra as $key => $element) {
249 $extra[$key]['#weight'] = isset($extra[$key]['#weight']) ? $extra[$key]['#weight'] : -18;
251 $form = array_merge($form, $extra);
254 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
256 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
257 $form['vid'] = array('#type' => 'value', '#value' => $edit['vid']);
258 $form['module'] = array('#type' => 'value', '#value' => $edit['module']);
260 return drupal_get_form('taxonomy_form_vocabulary', $form);
264 * Accept the form submission for a vocabulary and save the results.
266 function taxonomy_form_vocabulary_submit($form_id, $form_values) {
267 // Fix up the nodes array to remove unchecked nodes.
268 $form_values['nodes'] = array_filter($form_values['nodes']);
269 switch (taxonomy_save_vocabulary($form_values)) {
271 drupal_set_message(t('Created new vocabulary %name.', array('%name' => theme('placeholder', $form_values['name']))));
274 drupal_set_message(t('Updated vocabulary %name.', array('%name' => theme('placeholder', $form_values['name']))));
277 return 'admin/taxonomy';
280 function taxonomy_save_vocabulary(&$edit) {
281 $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
283 if ($edit['vid'] && $edit['name']) {
284 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']);
285 db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d", $edit['vid']);
286 foreach ($edit['nodes'] as $type => $selected) {
287 db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
289 module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
290 $status = SAVED_UPDATED;
292 else if ($edit['vid']) {
293 $status = taxonomy_del_vocabulary($edit['vid']);
296 $edit['vid'] = db_next_id('{vocabulary}_vid');
297 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');
298 foreach ($edit['nodes'] as $type => $selected) {
299 db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
301 module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
310 function taxonomy_del_vocabulary($vid) {
311 $vocabulary = (array) taxonomy_get_vocabulary($vid);
313 db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
314 db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
315 $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
316 while ($term = db_fetch_object($result)) {
317 taxonomy_del_term($term->tid);
320 module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
324 return SAVED_DELETED;
327 function _taxonomy_confirm_del_vocabulary($vid) {
328 $vocabulary = taxonomy_get_vocabulary($vid);
330 $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
331 $form['vid'] = array('#type' => 'value', '#value' => $vid);
332 $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
333 return confirm_form('taxonomy_vocabulary_confirm_delete', $form,
334 t('Are you sure you want to delete the vocabulary %title?',
335 array('%title' => theme('placeholder', $vocabulary->name))),
336 'admin/taxonomy', t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
341 function taxonomy_vocabulary_confirm_delete_submit($form_id, $form_values) {
342 $status = taxonomy_del_vocabulary($form_values['vid']);
343 drupal_set_message(t('Deleted vocabulary %name.', array('%name' => theme('placeholder', $form_values['name']))));
344 return 'admin/taxonomy';
347 function taxonomy_form_term($edit = array()) {
348 $vocabulary_id = isset($edit['vid']) ? $edit['vid'] : arg(4);
349 $vocabulary = taxonomy_get_vocabulary($vocabulary_id);
351 $form['name'] = array('#type' => 'textfield', '#title' => t('Term name'), '#default_value' => $edit['name'], '#maxlength' => 64, '#description' => t('The name for this term. Example: "Linux".'), '#required' => TRUE);
353 $form['description'] = array('#type' => 'textarea', '#title' => t('Description'), '#default_value' => $edit['description'], '#description' => t('A description of the term.'));
355 if ($vocabulary->hierarchy) {
356 $parent = array_keys(taxonomy_get_parents($edit['tid']));
357 $children = taxonomy_get_tree($vocabulary_id, $edit['tid']);
359 // A term can't be the child of itself, nor of its children.
360 foreach ($children as $child) {
361 $exclude[] = $child->tid;
363 $exclude[] = $edit['tid'];
365 if ($vocabulary->hierarchy == 1) {
366 $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);
368 elseif ($vocabulary->hierarchy == 2) {
369 $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);
373 if ($vocabulary->relations) {
374 $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']));
377 $form['synonyms'] = array('#type' => 'textarea', '#title' => t('Synonyms'), '#default_value' => implode("\n", taxonomy_get_synonyms($edit['tid'])), '#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'))));
378 $form['weight'] = array('#type' => 'weight', '#title' => t('Weight'), '#default_value' => $edit['weight'], '#description' => t('In listings, the heavier terms will sink and the lighter terms will be positioned nearer the top.'));
380 // Add extra term form elements.
381 $extra = module_invoke_all('taxonomy', 'form', 'term');
382 if (is_array($extra)) {
383 foreach ($extra as $key => $element) {
384 $extra[$key]['#weight'] = isset($extra[$key]['#weight']) ? $extra[$key]['#weight'] : -18;
386 $form = array_merge($form, $extra);
390 $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid);
391 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
394 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
395 $form['tid'] = array('#type' => 'value', '#value' => $edit['tid']);
398 $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
401 return drupal_get_form('taxonomy_form_term', $form);
405 * Accept the form submission for a taxonomy term and save the result.
407 function taxonomy_form_term_submit($form_id, $form_values) {
408 switch (taxonomy_save_term($form_values)) {
410 drupal_set_message(t('Created new term %term.', array('%term' => theme('placeholder', $form_values['name']))));
413 drupal_set_message(t('The term %term has been updated.', array('%term' => theme('placeholder', $form_values['name']))));
416 return 'admin/taxonomy';
419 function taxonomy_save_term(&$edit) {
420 if ($edit['tid'] && $edit['name']) {
421 db_query("UPDATE {term_data} SET name = '%s', description = '%s', weight = %d WHERE tid = %d", $edit['name'], $edit['description'], $edit['weight'], $edit['tid']);
422 module_invoke_all('taxonomy', 'update', 'term', $edit);
423 $status = SAVED_UPDATED;
425 else if ($edit['tid']) {
426 return taxonomy_del_term($edit['tid']);
429 $edit['tid'] = db_next_id('{term_data}_tid');
430 db_query("INSERT INTO {term_data} (tid, name, description, vid, weight) VALUES (%d, '%s', '%s', %d, %d)", $edit['tid'], $edit['name'], $edit['description'], $edit['vid'], $edit['weight']);
431 module_invoke_all('taxonomy', 'insert', 'term', $edit);
435 db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $edit['tid'], $edit['tid']);
436 if ($edit['relations']) {
437 foreach ($edit['relations'] as $related_id) {
438 if ($related_id != 0) {
439 db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $edit['tid'], $related_id);
444 db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $edit['tid']);
445 if (!isset($edit['parent']) || empty($edit['parent'])) {
446 $edit['parent'] = array(0);
448 if (is_array($edit['parent'])) {
449 foreach ($edit['parent'] as $parent) {
450 if (is_array($parent)) {
451 foreach ($parent as $tid) {
452 db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $edit['tid'], $tid);
456 db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $edit['tid'], $parent);
461 db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $edit['tid'], $edit['parent']);
464 db_query('DELETE FROM {term_synonym} WHERE tid = %d', $edit['tid']);
465 if ($edit['synonyms']) {
466 foreach (explode ("\n", str_replace("\r", '', $edit['synonyms'])) as $synonym) {
468 db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $edit['tid'], chop($synonym));
478 function taxonomy_del_term($tid) {
481 $children_tids = $orphans = array();
482 foreach ($tids as $tid) {
483 // See if any of the term's children are about to be become orphans:
484 if ($children = taxonomy_get_children($tid)) {
485 foreach ($children as $child) {
486 // If the term has multiple parents, we don't delete it.
487 $parents = taxonomy_get_parents($child->tid);
488 if (count($parents) == 1) {
489 $orphans[] = $child->tid;
494 $term = (array) taxonomy_get_term($tid);
496 db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
497 db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
498 db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
499 db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
500 db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
502 module_invoke_all('taxonomy', 'delete', 'term', $term);
510 return SAVED_DELETED;
513 function _taxonomy_confirm_del_term($tid) {
514 $term = taxonomy_get_term($tid);
516 $form['type'] = array('#type' => 'value', '#value' => 'term');
517 $form['name'] = array('#type' => 'value', '#value' => $term->name);
518 $form['tid'] = array('#type' => 'value', '#value' => $tid);
519 return confirm_form('taxonomy_term_confirm_delete', $form,
520 t('Are you sure you want to delete the term %title?',
521 array('%title' => theme('placeholder', $term->name))),
523 t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
528 function taxonomy_term_confirm_delete_submit($form_id, $form_values) {
529 taxonomy_del_term($form_values['tid']);
530 drupal_set_message(t('Deleted term %name.', array('%name' => theme('placeholder', $form_values['name']))));
531 return 'admin/taxonomy';
535 * Generate a form element for selecting terms from a vocabulary.
537 function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') {
538 $vocabulary = taxonomy_get_vocabulary($vid);
539 $help = ($help) ? $help : $vocabulary->help;
540 if ($vocabulary->required) {
544 $blank = '<'. t('none') .'>';
547 return _taxonomy_term_select(check_plain($vocabulary->name), $name, $value, $vid, $help, intval($vocabulary->multiple), $blank);
551 * Generate a set of options for selecting a term from all vocabularies. Can be
552 * passed to form_select.
554 function taxonomy_form_all($free_tags = 0) {
555 $vocabularies = taxonomy_get_vocabularies();
557 foreach ($vocabularies as $vid => $vocabulary) {
558 if ($vocabulary->tags && !$free_tags) { continue; }
559 $tree = taxonomy_get_tree($vid);
560 $options[$vocabulary->name] = array();
562 foreach ($tree as $term) {
563 $options[$vocabulary->name][$term->tid] = _taxonomy_depth($term->depth, '-') . $term->name;
571 * Return an array of all vocabulary objects.
574 * If set, return only those vocabularies associated with this node type.
576 function taxonomy_get_vocabularies($type = NULL) {
578 $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);
581 $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'));
584 $vocabularies = array();
585 $node_types = array();
586 while ($voc = db_fetch_object($result)) {
587 $node_types[$voc->vid][] = $voc->type;
589 $voc->nodes = $node_types[$voc->vid];
590 $vocabularies[$voc->vid] = $voc;
593 return $vocabularies;
597 * Generate a form for selecting terms to associate with a node.
599 function taxonomy_form_alter($form_id, &$form) {
600 if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
601 $node = $form['#node'];
603 if (!isset($node->taxonomy)) {
605 $terms = taxonomy_node_get_terms($node->nid);
612 $terms = $node->taxonomy;
615 $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);
617 while ($vocabulary = db_fetch_object($c)) {
618 if ($vocabulary->tags) {
619 $typed_terms = array();
620 foreach ($terms as $term) {
621 // Extract terms belonging to the vocabulary in question.
622 if ($term->vid == $vocabulary->vid) {
624 // Commas and quotes in terms are special cases, so encode 'em.
625 if (preg_match('/,/', $term->name) || preg_match('/"/', $term->name)) {
626 $term->name = '"'.preg_replace('/"/', '""', $term->name).'"';
629 $typed_terms[] = $term->name;
632 $typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
634 if ($vocabulary->help) {
635 $help = $vocabulary->help;
638 $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
640 $form['taxonomy']['tags'][$vocabulary->vid] = array('#type' => 'textfield',
641 '#title' => $vocabulary->name,
642 '#description' => $help,
643 '#required' => $vocabulary->required,
644 '#default_value' => $typed_string,
645 '#autocomplete_path' => 'taxonomy/autocomplete/'. $vocabulary->vid,
646 '#weight' => $vocabulary->weight,
651 // Extract terms belonging to the vocabulary in question.
652 $default_terms = array();
653 foreach ($terms as $term) {
654 if ($term->vid == $vocabulary->vid) {
655 $default_terms[$term->tid] = $term;
658 $form['taxonomy'][$vocabulary->vid] = taxonomy_form($vocabulary->vid, array_keys($default_terms), $vocabulary->help);
659 $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
660 $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
663 if (isset($form['taxonomy'])) {
664 $form['taxonomy'] += array('#type' => 'fieldset', '#title' => t('Categories'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#tree' => TRUE, '#weight' => -3);
670 * Find all terms associated to the given node, within one vocabulary.
672 function taxonomy_node_get_terms_by_vocabulary($nid, $vid, $key = 'tid') {
673 $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);
675 while ($term = db_fetch_object($result)) {
676 $terms[$term->$key] = $term;
682 * Find all terms associated to the given node, ordered by vocabulary and term weight.
684 function taxonomy_node_get_terms($nid, $key = 'tid') {
687 if (!isset($terms[$nid])) {
688 $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);
689 $terms[$nid] = array();
690 while ($term = db_fetch_object($result)) {
691 $terms[$nid][$term->$key] = $term;
698 * Make sure incoming vids are free tagging enabled.
700 function taxonomy_node_validate(&$node) {
701 if ($node->taxonomy) {
702 $terms = $node->taxonomy;
703 if ($terms['tags']) {
704 foreach ($terms['tags'] as $vid => $vid_value) {
705 $vocabulary = taxonomy_get_vocabulary($vid);
706 if (!$vocabulary->tags) {
707 // see form_get_error $key = implode('][', $element['#parents']);
708 // on why this is the key
709 form_set_error("taxonomy][tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => theme('placeholder', $vocabulary->name))));
717 * Save term associations for a given node.
719 function taxonomy_node_save($nid, $terms) {
720 taxonomy_node_delete($nid);
722 // Free tagging vocabularies do not send their tids in the form,
723 // so we'll detect them here and process them independently.
724 if (isset($terms['tags'])) {
725 $typed_input = $terms['tags'];
726 unset($terms['tags']);
728 foreach ($typed_input as $vid => $vid_value) {
729 // This regexp allows the following types of user input:
730 // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
731 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
732 preg_match_all($regexp, $vid_value, $matches);
733 $typed_terms = array_unique($matches[1]);
736 foreach ($typed_terms as $typed_term) {
737 // If a user has escaped a term (to demonstrate that it is a group,
738 // or includes a comma or quote character), we remove the escape
739 // formatting so to save the term into the DB as the user intends.
740 $typed_term = str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term));
741 $typed_term = trim($typed_term);
742 if ($typed_term == "") { continue; }
744 // See if the term exists in the chosen vocabulary
745 // and return the tid, otherwise, add a new record.
746 $possibilities = taxonomy_get_term_by_name($typed_term);
747 $typed_term_tid = NULL; // tid match if any.
748 foreach ($possibilities as $possibility) {
749 if ($possibility->vid == $vid) {
750 $typed_term_tid = $possibility->tid;
754 if (!$typed_term_tid) {
755 $edit = array('vid' => $vid, 'name' => $typed_term);
756 $status = taxonomy_save_term($edit);
757 $typed_term_tid = $edit['tid'];
760 // defend against duplicate, different cased tags
761 if (!isset($inserted[$typed_term_tid])) {
762 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $typed_term_tid);
763 $inserted[$typed_term_tid] = TRUE;
769 if (is_array($terms)) {
770 foreach ($terms as $term) {
771 if (is_array($term)) {
772 foreach ($term as $tid) {
774 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $tid);
778 else if (is_object($term)) {
779 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term->tid);
782 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term);
789 * Remove associations of a node to its terms.
791 function taxonomy_node_delete($nid) {
792 db_query('DELETE FROM {term_node} WHERE nid = %d', $nid);
796 * Find all term objects related to a given term ID.
798 function taxonomy_get_related($tid, $key = 'tid') {
800 $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);
802 while ($term = db_fetch_object($result)) {
803 $related[$term->$key] = $term;
813 * Find all parents of a given term ID.
815 function taxonomy_get_parents($tid, $key = 'tid') {
817 $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);
819 while ($parent = db_fetch_object($result)) {
820 $parents[$parent->$key] = $parent;
830 * Find all ancestors of a given term ID.
832 function taxonomy_get_parents_all($tid) {
835 $parents[] = taxonomy_get_term($tid);
837 while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
838 $parents = array_merge($parents, $parent);
846 * Find all children of a term ID.
848 function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
850 $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);
853 $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);
856 while ($term = db_fetch_object($result)) {
857 $children[$term->$key] = $term;
863 * Create a hierarchical representation of a vocabulary.
866 * Which vocabulary to generate the tree for.
869 * The term ID under which to generate the tree. If 0, generate the tree
870 * for the entire vocabulary.
876 * The number of levels of the tree to return. Leave NULL to return all levels.
879 * An array of all term objects in the tree. Each term object is extended
880 * to have "depth" and "parents" attributes in addition to its normal ones.
882 function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
883 static $children, $parents, $terms;
887 // We cache trees, so it's not CPU-intensive to call get_tree() on a term
888 // and its children, too.
889 if (!isset($children[$vid])) {
890 $children[$vid] = array();
892 $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);
893 while ($term = db_fetch_object($result)) {
894 $children[$vid][$term->parent][] = $term->tid;
895 $parents[$vid][$term->tid][] = $term->parent;
896 $terms[$vid][$term->tid] = $term;
900 $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
901 if ($children[$vid][$parent]) {
902 foreach ($children[$vid][$parent] as $child) {
903 if ($max_depth > $depth) {
904 $terms[$vid][$child]->depth = $depth;
905 // The "parent" attribute is not useful, as it would show one parent only.
906 unset($terms[$vid][$child]->parent);
907 $terms[$vid][$child]->parents = $parents[$vid][$child];
908 $tree[] = $terms[$vid][$child];
910 if ($children[$vid][$child]) {
911 $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $depth, $max_depth));
917 return $tree ? $tree : array();
921 * Return an array of synonyms of the given term ID.
923 function taxonomy_get_synonyms($tid) {
925 $result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d', $tid);
926 while ($synonym = db_fetch_array($result)) {
927 $synonyms[] = $synonym['name'];
929 return $synonyms ? $synonyms : array();
937 * Return the term object that has the given string as a synonym.
939 function taxonomy_get_synonym_root($synonym) {
940 return db_fetch_object(db_query("SELECT * FROM {term_synonym} s, {term_data} t WHERE t.tid = s.tid AND s.name = '%s'", $synonym));
944 * Given a term id, count the number of published nodes in it.
946 function taxonomy_term_count_nodes($tid, $type = 0) {
949 if (!isset($count[$type])) {
950 // $type == 0 always evaluates true is $type is a string
951 if (is_numeric($type)) {
952 $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'));
955 $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);
957 while ($term = db_fetch_object($result)) {
958 $count[$type][$term->tid] = $term->c;
962 foreach (_taxonomy_term_children($tid) as $c) {
963 $children_count += taxonomy_term_count_nodes($c, $type);
965 return $count[$type][$tid] + $children_count;
969 * Helper for taxonomy_term_count_nodes().
971 function _taxonomy_term_children($tid) {
974 if (!isset($children)) {
975 $result = db_query('SELECT tid, parent FROM {term_hierarchy}');
976 while ($term = db_fetch_object($result)) {
977 $children[$term->parent][] = $term->tid;
980 return $children[$tid] ? $children[$tid] : array();
984 * Try to map a string to an existing term, as for glossary use.
986 * Provides a case-insensitive and trimmed mapping, to maximize the
987 * likelihood of a successful match.
990 * Name of the term to search for.
993 * An array of matching term objects.
995 function taxonomy_get_term_by_name($name) {
996 $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));
998 while ($term = db_fetch_object($db_result)) {
1006 * Return the vocabulary object matching a vocabulary ID.
1008 function taxonomy_get_vocabulary($vid) {
1009 static $vocabularies = array();
1011 if (!array_key_exists($vid, $vocabularies)) {
1012 $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);
1013 $node_types = array();
1014 while ($voc = db_fetch_object($result)) {
1015 $node_types[] = $voc->type;
1017 $voc->nodes = $node_types;
1018 $vocabularies[$vid] = $voc;
1022 return $vocabularies[$vid];
1026 * Return the term object matching a term ID.
1028 function taxonomy_get_term($tid) {
1029 // simple cache using a static var?
1030 return db_fetch_object(db_query('SELECT * FROM {term_data} WHERE tid = %d', $tid));
1033 function _taxonomy_term_select($title, $name, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) {
1034 $tree = taxonomy_get_tree($vocabulary_id);
1038 $options[0] = $blank;
1041 foreach ($tree as $term) {
1042 if (!in_array($term->tid, $exclude)) {
1043 $options[$term->tid] = _taxonomy_depth($term->depth, '-') . $term->name;
1046 if (!$blank && !$value) {
1047 // required but without a predefined value, so set first as predefined
1048 $value = $tree[0]->tid;
1052 return array('#type' => 'select',
1054 '#default_value' => $value,
1055 '#options' => $options,
1056 '#description' => $description,
1057 '#multiple' => $multiple,
1058 '#size' => $multiple ? min(9, count($options)) : 0,
1060 '#theme' => 'taxonomy_term_select',
1064 function theme_taxonomy_term_select($element) {
1065 return theme('select', $element);
1068 function _taxonomy_depth($depth, $graphic = '--') {
1069 for ($n = 0; $n < $depth; $n++) {
1070 $result .= $graphic;
1076 * Finds all nodes that match selected taxonomy conditions.
1079 * An array of term IDs to match.
1081 * How to interpret multiple IDs in the array. Can be "or" or "and".
1083 * How many levels deep to traverse the taxonomy tree. Can be a nonnegative
1086 * Whether the nodes are to be used with a pager (the case on most Drupal
1087 * pages) or not (in an XML feed, for example).
1089 * The order clause for the query that retrieve the nodes.
1091 * A resource identifier pointing to the query results.
1093 function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC') {
1094 if (count($tids) > 0) {
1095 // For each term ID, generate an array of descendant term IDs to the right depth.
1096 $descendant_tids = array();
1097 if ($depth === 'all') {
1100 foreach ($tids as $index => $tid) {
1101 $term = taxonomy_get_term($tid);
1102 $tree = taxonomy_get_tree($term->vid, $tid, -1, $depth);
1103 $descendant_tids[] = array_merge(array($tid), array_map('_taxonomy_get_tid_from_term', $tree));
1106 if ($operator == 'or') {
1107 $str_tids = implode(',', call_user_func_array('array_merge', $descendant_tids));
1108 $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 ('. $str_tids .') AND n.status = 1 ORDER BY '. $order;
1109 $sql_count = 'SELECT COUNT(DISTINCT(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';
1114 foreach ($descendant_tids as $index => $tids) {
1115 $joins .= ' INNER JOIN {term_node} tn'. $index .' ON n.nid = tn'. $index .'.nid';
1116 $wheres .= ' AND tn'. $index .'.tid IN ('. implode(',', $tids) .')';
1118 $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n '. $joins .' WHERE n.status = 1 '. $wheres .' ORDER BY '. $order;
1119 $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n '. $joins .' WHERE n.status = 1 ' . $wheres;
1121 $sql = db_rewrite_sql($sql);
1122 $sql_count = db_rewrite_sql($sql_count);
1124 $result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count);
1127 $result = db_query_range($sql, 0, variable_get('feed_default_items', 10));
1135 * Accepts the result of a pager_query() call, such as that performed by
1136 * taxonomy_select_nodes(), and formats each node along with a pager.
1138 function taxonomy_render_nodes($result) {
1139 if (db_num_rows($result) > 0) {
1140 while ($node = db_fetch_object($result)) {
1141 $output .= node_view(node_load($node->nid), 1);
1143 $output .= theme('pager', NULL, variable_get('default_nodes_main', 10), 0);
1146 $output .= t('There are currently no posts in this category.');
1152 * Implementation of hook_nodeapi().
1154 function taxonomy_nodeapi($node, $op, $arg = 0) {
1157 $output['taxonomy'] = taxonomy_node_get_terms($node->nid);
1160 taxonomy_node_save($node->nid, $node->taxonomy);
1163 taxonomy_node_save($node->nid, $node->taxonomy);
1166 taxonomy_node_delete($node->nid);
1169 taxonomy_node_validate($node);
1172 return taxonomy_rss_item($node);
1173 case 'update index':
1174 return taxonomy_node_update_index($node);
1179 * Implementation of hook_nodeapi('update_index').
1181 function taxonomy_node_update_index(&$node) {
1183 foreach ($node->taxonomy as $term) {
1184 $output[] = $term->name;
1186 if (count($output)) {
1187 return '<strong>('. implode(', ', $output) .')</strong>';
1192 * Menu callback; displays all nodes associated with a term.
1194 function taxonomy_term_page($str_tids = '', $depth = 0, $op = 'page') {
1195 if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
1197 // The '+' character in a query string may be parsed as ' '.
1198 $tids = preg_split('/[+ ]/', $str_tids);
1200 else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
1202 $tids = explode(',', $str_tids);
1209 $result = db_query(db_rewrite_sql('SELECT t.tid, t.name FROM {term_data} t WHERE t.tid IN (%s)', 't', 'tid'), implode(',', $tids));
1210 $tids = array(); // we rebuild the $tids-array so it only contains terms the user has access to.
1212 while ($term = db_fetch_object($result)) {
1213 $tids[] = $term->tid;
1214 $names[] = $term->name;
1218 drupal_set_title($title = check_plain(implode(', ', $names)));
1222 // Build breadcrumb based on first hierarchy of first term:
1223 $current->tid = $tids[0];
1224 $breadcrumbs = array(array('path' => $_GET['q']));
1225 while ($parents = taxonomy_get_parents($current->tid)) {
1226 $current = array_shift($parents);
1227 $breadcrumbs[] = array('path' => 'taxonomy/term/'. $current->tid, 'title' => $current->name);
1229 $breadcrumbs = array_reverse($breadcrumbs);
1230 menu_set_location($breadcrumbs);
1232 drupal_add_link(array('rel' => 'alternate',
1233 'type' => 'application/rss+xml',
1234 'title' => 'RSS - '. $title,
1235 'href' => url('taxonomy/term/'. $str_tids .'/'. $depth .'/feed')));
1237 $output = taxonomy_render_nodes(taxonomy_select_nodes($tids, $operator, $depth, TRUE));
1238 $output .= theme('feed_icon', url('taxonomy/term/'. $str_tids .'/'. $depth .'/feed'));
1243 $term = taxonomy_get_term($tids[0]);
1244 $channel['link'] = url('taxonomy/term/'. $str_tids .'/'. $depth, NULL, NULL, TRUE);
1245 $channel['title'] = variable_get('site_name', 'drupal') .' - '. $title;
1246 $channel['description'] = $term->description;
1248 $result = taxonomy_select_nodes($tids, $operator, $depth, FALSE);
1249 node_feed($result, $channel);
1262 * Page to add or edit a vocabulary
1264 function taxonomy_admin_vocabulary_edit($vid = NULL) {
1265 if ($_POST['op'] == t('Delete') || $_POST['edit']['confirm']) {
1266 return _taxonomy_confirm_del_vocabulary($vid);
1269 $vocabulary = (array)taxonomy_get_vocabulary($vid);
1271 return taxonomy_form_vocabulary($vocabulary);
1275 * Page to list terms for a vocabulary
1277 function taxonomy_admin_term_edit($tid = NULL) {
1278 if ($_POST['op'] == t('Delete') || $_POST['edit']['confirm']) {
1279 return _taxonomy_confirm_del_term($tid);
1282 $term = (array)taxonomy_get_term($tid);
1284 return taxonomy_form_term($term);
1288 * Provides category information for rss feeds
1290 function taxonomy_rss_item($node) {
1292 foreach ($node->taxonomy as $term) {
1293 $output[] = array('key' => 'category',
1294 'value' => check_plain($term->name),
1295 'attributes' => array('domain' => url('taxonomy/term/'. $term->tid, NULL, NULL, TRUE)));
1301 * Implementation of hook_help().
1303 function taxonomy_help($section) {
1305 case 'admin/help#taxonomy':
1306 $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. It can automatically classify new content, which is very useful for organizing content on-the-fly. A simple example would be organizing a list of music reviews by musical genre.') .'</p>';
1307 $output .= '<p>'. t('Taxonomy is also 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>';
1308 $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>';
1309 $output .= t('<p>You can</p>
1311 <li>add a vocabulary at <a href="%admin-taxonomy-add-vocabulary">administer >> categories >> add vocabulary</a>.</li>
1312 <li>administer taxonomy at <a href="%admin-taxonomy">administer >> categories</a>.</li>
1313 <li>restrict content access by category for specific users roles using the <a href="%external-http-drupal-org-project-taxonomy_access">taxonomy access module</a>.</li>
1314 <li>build a custom view of your categories using the <a href="%external-http-drupal-org-project-taxonomy_browser">taxonomy browser</a>.</li>
1316 ', array('%admin-taxonomy-add-vocabulary' => url('admin/taxonomy/add/vocabulary'), '%admin-taxonomy' => url('admin/taxonomy'), '%external-http-drupal-org-project-taxonomy_access' => 'http://drupal.org/project/taxonomy_access', '%external-http-drupal-org-project-taxonomy_browser' => 'http://drupal.org/project/taxonomy_browser'));
1317 $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>';
1319 case 'admin/modules#description':
1320 return t('Enables the categorization of content.');
1321 case 'admin/taxonomy':
1322 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 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>');
1323 case 'admin/taxonomy/add/vocabulary':
1324 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>");
1329 * Helper function for array_map purposes.
1331 function _taxonomy_get_tid_from_term($term) {
1336 * Helper function for autocompletion
1338 function taxonomy_autocomplete($vid, $string = '') {
1339 // The user enters a comma-separated list of tags. We only autocomplete the last tag.
1340 // This regexp allows the following types of user input:
1341 // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
1342 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
1343 preg_match_all($regexp, $string, $matches);
1344 $array = $matches[1];
1347 $last_string = trim(array_pop($array));
1348 if ($last_string != '') {
1349 $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);
1351 $prefix = count($array) ? implode(', ', $array) .', ' : '';
1354 while ($tag = db_fetch_object($result)) {
1356 // Commas and quotes in terms are special cases, so encode 'em.
1357 if (preg_match('/,/', $tag->name) || preg_match('/"/', $tag->name)) {
1358 $n = '"'. preg_replace('/"/', '""', $tag->name) .'"';
1360 $matches[$prefix . $n] = check_plain($tag->name);
1362 print drupal_to_js($matches);