taxonomy.module 51.9 KB
Newer Older
Dries's avatar
 
Dries committed
1
<?php
Kjartan's avatar
Kjartan committed
2
// $Id$
Dries's avatar
   
Dries committed
3

Dries's avatar
   
Dries committed
4
5
6
7
8
/**
 * @file
 * Enables the organization of content into categories.
 */

Dries's avatar
   
Dries committed
9
10
11
/**
 * Implementation of hook_perm().
 */
Kjartan's avatar
Kjartan committed
12
function taxonomy_perm() {
Dries's avatar
   
Dries committed
13
  return array('administer taxonomy');
Kjartan's avatar
Kjartan committed
14
}
Dries's avatar
 
Dries committed
15

Dries's avatar
   
Dries committed
16
17
18
19
20
21
22
23
24
25
26
/**
 * Implementation of hook_link().
 *
 * This hook is extended with $type = 'taxonomy terms' to allow themes to
 * print lists of terms associated with a node. Themes can print taxonomy
 * links with:
 *
 * if (module_exist('taxonomy')) {
 *   $this->links(taxonomy_link('taxonomy terms', $node));
 * }
 */
Dries's avatar
   
Dries committed
27
function taxonomy_link($type, $node = NULL) {
Dries's avatar
   
Dries committed
28
  if ($type == 'taxonomy terms' && $node != NULL) {
Kjartan's avatar
   
Kjartan committed
29
    $links = array();
Dries's avatar
   
Dries committed
30
    if (array_key_exists('taxonomy', $node)) {
31
      foreach ($node->taxonomy as $term) {
32
        $links['taxonomy_term_'. $term->tid] = array(
33
34
35
          'title' => $term->name,
          'href' => taxonomy_term_path($term),
          'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
36
        );
Dries's avatar
   
Dries committed
37
      }
Dries's avatar
   
Dries committed
38
    }
Kjartan's avatar
Kjartan committed
39

40
41
42
43
44
45
46
    // We call this hook again because some modules and themes call taxonomy_link('taxonomy terms') directly
    foreach (module_implements('link_alter') AS $module) {
      $function = $module .'_link_alter';
      $function($node, $links);
    }

    return $links;
47
48
49
  }
}

50
51
52
53
54
55
56
57
function taxonomy_term_path($term) {
  $vocabulary = taxonomy_get_vocabulary($term->vid);
  if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
    return $path;
  }
  return 'taxonomy/term/'. $term->tid;
}

Dries's avatar
   
Dries committed
58
59
60
/**
 * Implementation of hook_menu().
 */
Dries's avatar
   
Dries committed
61
function taxonomy_menu($may_cache) {
Dries's avatar
   
Dries committed
62
  $items = array();
Dries's avatar
   
Dries committed
63

Dries's avatar
   
Dries committed
64
  if ($may_cache) {
65
    $items[] = array('path' => 'admin/content/taxonomy',
66
      'title' => t('categories'),
67
      'description' => t('Create vocabularies and terms to categorize your content.'),
68
      'callback' => 'taxonomy_overview_vocabularies',
Dries's avatar
   
Dries committed
69
      'access' => user_access('administer taxonomy'));
70

71
    $items[] = array('path' => 'admin/content/taxonomy/list',
72
73
74
      'title' => t('list'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10);
75

76
    $items[] = array('path' => 'admin/content/taxonomy/add/vocabulary',
77
78
      'title' => t('add vocabulary'),
      'callback' => 'taxonomy_admin_vocabulary_edit',
Dries's avatar
   
Dries committed
79
80
81
      'access' => user_access('administer taxonomy'),
      'type' => MENU_LOCAL_TASK);

82
    $items[] = array('path' => 'admin/content/taxonomy/edit/vocabulary',
83
84
      'title' => t('edit vocabulary'),
      'callback' => 'taxonomy_admin_vocabulary_edit',
85
86
87
      'access' => user_access('administer taxonomy'),
      'type' => MENU_CALLBACK);

88
    $items[] = array('path' => 'admin/content/taxonomy/edit/term',
89
90
      'title' => t('edit term'),
      'callback' => 'taxonomy_admin_term_edit',
91
92
93
      'access' => user_access('administer taxonomy'),
      'type' => MENU_CALLBACK);

94
95
    $items[] = array('path' => 'taxonomy/term',
      'title' => t('taxonomy term'),
Dries's avatar
   
Dries committed
96
97
98
      'callback' => 'taxonomy_term_page',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK);
Steven Wittens's avatar
Steven Wittens committed
99

100
101
    $items[] = array('path' => 'taxonomy/autocomplete',
      'title' => t('autocomplete taxonomy'),
Steven Wittens's avatar
Steven Wittens committed
102
103
104
      'callback' => 'taxonomy_autocomplete',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK);
Dries's avatar
   
Dries committed
105
  }
Dries's avatar
Dries committed
106
  else {
107
108
    if (is_numeric(arg(3))) {
      $items[] = array('path' => 'admin/content/taxonomy/' . arg(3),
109
110
        'title' => t('list terms'),
        'callback' => 'taxonomy_overview_terms',
111
        'callback arguments' => array(arg(3)),
Dries's avatar
Dries committed
112
113
114
        'access' => user_access('administer taxonomy'),
        'type' => MENU_CALLBACK);

115
      $items[] = array('path' => 'admin/content/taxonomy/' . arg(3) . '/list',
116
117
118
        'title' => t('list'),
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => -10);
Dries's avatar
Dries committed
119

120
      $items[] = array('path' => 'admin/content/taxonomy/' . arg(3) . '/add/term',
121
        'title' => t('add term'),
122
        'callback' => 'taxonomy_form_term',
123
        'callback arguments' => array(array('vid' => arg(3))),
Dries's avatar
Dries committed
124
125
126
127
        'access' => user_access('administer taxonomy'),
        'type' => MENU_LOCAL_TASK);
    }
  }
Dries's avatar
   
Dries committed
128

Dries's avatar
   
Dries committed
129
130
  return $items;
}
Dries's avatar
 
Dries committed
131

132
133
134
135
/**
 * List and manage vocabularies.
 */
function taxonomy_overview_vocabularies() {
136
  $vocabularies = taxonomy_get_vocabularies();
137
  $rows = array();
138
139
140
  foreach ($vocabularies as $vocabulary) {
    $types = array();
    foreach ($vocabulary->nodes as $type) {
141
      $node_type = node_get_types('name', $type);
142
143
      $types[] = $node_type ? $node_type : $type;
    }
144
145
    $rows[] = array('name' => check_plain($vocabulary->name),
      'type' => implode(', ', $types),
146
147
148
      'edit' => l(t('edit vocabulary'), "admin/content/taxonomy/edit/vocabulary/$vocabulary->vid"),
      'list' => l(t('list terms'), "admin/content/taxonomy/$vocabulary->vid"),
      'add' => l(t('add terms'), "admin/content/taxonomy/$vocabulary->vid/add/term")
149
150
151
    );
  }
  if (empty($rows)) {
drumm's avatar
drumm committed
152
    $rows[] = array(array('data' => t('No categories available.'), 'colspan' => '5', 'class' => 'message'));
153
  }
drumm's avatar
drumm committed
154
  $header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3'));
155

156
  return theme('table', $header, $rows, array('id' => 'taxonomy'));
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
}

/**
 * Display a tree of all the terms in a vocabulary, with options to edit
 * each one.
 */
function taxonomy_overview_terms($vid) {
  $destination = drupal_get_destination();

  $header = array(t('Name'), t('Operations'));
  $vocabulary = taxonomy_get_vocabulary($vid);

  drupal_set_title(check_plain($vocabulary->name));
  $start_from      = $_GET['page'] ? $_GET['page'] : 0;
  $total_entries   = 0;  // total count for pager
  $page_increment  = 25; // number of tids per page
  $displayed_count = 0;  // number of tids shown

  $tree = taxonomy_get_tree($vocabulary->vid);
  foreach ($tree as $term) {
    $total_entries++; // we're counting all-totals, not displayed
    if (($start_from && ($start_from * $page_increment) >= $total_entries) || ($displayed_count == $page_increment)) { continue; }
179
    $rows[] = array(_taxonomy_depth($term->depth) . ' ' . l($term->name, "taxonomy/term/$term->tid"), l(t('edit'), "admin/content/taxonomy/edit/term/$term->tid", array(), $destination));
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    $displayed_count++; // we're counting tids displayed
  }

  if (!$rows) {
    $rows[] = array(array('data' => t('No terms available.'), 'colspan' => '2'));
  }

  $GLOBALS['pager_page_array'][] = $start_from;  // FIXME
  $GLOBALS['pager_total'][] = intval($total_entries / $page_increment) + 1; // FIXME

  if ($total_entries >= $page_increment) {
    $rows[] = array(array('data' => theme('pager', NULL, $page_increment), 'colspan' => '2'));
  }

  return theme('table', $header, $rows, array('id' => 'taxonomy'));
}

/**
 * Display form for adding and editing vocabularies.
 */
Kjartan's avatar
Kjartan committed
200
function taxonomy_form_vocabulary($edit = array()) {
201
202
203
204
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Vocabulary name'),
    '#default_value' => $edit['name'],
    '#maxlength' => 64,
205
    '#description' => t('The name for this vocabulary. Example: "Topic".'),
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
    '#required' => TRUE,
  );
  $form['description'] = array('#type' => 'textarea',
    '#title' => t('Description'),
    '#default_value' => $edit['description'],
    '#description' => t('Description of the vocabulary; can be used by modules.'),
  );
  $form['help'] = array('#type' => 'textfield',
    '#title' => t('Help text'),
    '#default_value' => $edit['help'],
    '#description' => t('Instructions to present to the user when choosing a term.'),
  );
  $form['nodes'] = array('#type' => 'checkboxes',
    '#title' => t('Types'),
    '#default_value' => $edit['nodes'],
221
    '#options' => node_get_types('names'),
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
    '#description' => t('A list of node types you want to associate with this vocabulary.'),
    '#required' => TRUE,
  );
  $form['hierarchy'] = array('#type' => 'radios',
    '#title' => t('Hierarchy'),
    '#default_value' => $edit['hierarchy'],
    '#options' => array(t('Disabled'), t('Single'), t('Multiple')),
    '#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'))),
  );
  $form['relations'] = array('#type' => 'checkbox',
    '#title' => t('Related terms'),
    '#default_value' => $edit['relations'],
    '#description' => t('Allows <a href="%help-url">related terms</a> in this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'related-terms'))),
  );
  $form['tags'] = array('#type' => 'checkbox',
    '#title' => t('Free tagging'),
    '#default_value' => $edit['tags'],
    '#description' => t('Content is categorized by typing terms instead of choosing from a list.'),
  );
  $form['multiple'] = array('#type' => 'checkbox',
    '#title' => t('Multiple select'),
    '#default_value' => $edit['multiple'],
    '#description' => t('Allows nodes to have more than one term from this vocabulary (always true for free tagging).'),
  );
  $form['required'] = array('#type' => 'checkbox',
    '#title' => t('Required'),
    '#default_value' => $edit['required'],
    '#description' => t('If enabled, every node <strong>must</strong> have at least one term in this vocabulary.'),
  );
  $form['weight'] = array('#type' => 'weight',
    '#title' => t('Weight'),
    '#default_value' => $edit['weight'],
    '#description' => t('In listings, the heavier vocabularies will sink and the lighter vocabularies will be positioned nearer the top.'),
  );
256

257
  $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
Dries's avatar
   
Dries committed
258
  if ($edit['vid']) {
259
    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
260
    $form['vid'] = array('#type' => 'value', '#value' => $edit['vid']);
261
    $form['module'] = array('#type' => 'value', '#value' => $edit['module']);
Dries's avatar
 
Dries committed
262
  }
263
  return drupal_get_form('taxonomy_form_vocabulary', $form);
Kjartan's avatar
Kjartan committed
264
}
Kjartan's avatar
Kjartan committed
265

266
267
268
269
270
271
272
273
274
275
276
277
278
279
/**
 * Accept the form submission for a vocabulary and save the results.
 */
function taxonomy_form_vocabulary_submit($form_id, $form_values) {
  // Fix up the nodes array to remove unchecked nodes.
  $form_values['nodes'] = array_filter($form_values['nodes']);
  switch (taxonomy_save_vocabulary($form_values)) {
  case SAVED_NEW:
    drupal_set_message(t('Created new vocabulary %name.', array('%name' => theme('placeholder', $form_values['name']))));
    break;
  case SAVED_UPDATED:
    drupal_set_message(t('Updated vocabulary %name.', array('%name' => theme('placeholder', $form_values['name']))));
    break;
  }
280
  return 'admin/content/taxonomy';
281
282
}

283
function taxonomy_save_vocabulary(&$edit) {
284
  $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
Dries's avatar
   
Dries committed
285

Dries's avatar
   
Dries committed
286
  if ($edit['vid'] && $edit['name']) {
287
    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']);
Dries's avatar
   
Dries committed
288
    db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d", $edit['vid']);
289
    foreach ($edit['nodes'] as $type => $selected) {
Dries's avatar
   
Dries committed
290
291
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
    }
Dries's avatar
   
Dries committed
292
    module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
293
    $status = SAVED_UPDATED;
Dries's avatar
 
Dries committed
294
  }
Dries's avatar
   
Dries committed
295
  else if ($edit['vid']) {
296
    $status = taxonomy_del_vocabulary($edit['vid']);
Kjartan's avatar
Kjartan committed
297
298
  }
  else {
299
300
    $edit['vid'] = db_next_id('{vocabulary}_vid');
    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');
301
    foreach ($edit['nodes'] as $type => $selected) {
Dries's avatar
   
Dries committed
302
303
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
    }
Dries's avatar
   
Dries committed
304
    module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
305
    $status = SAVED_NEW;
Kjartan's avatar
Kjartan committed
306
  }
Dries's avatar
   
Dries committed
307
308

  cache_clear_all();
Dries's avatar
   
Dries committed
309

310
  return $status;
Kjartan's avatar
Kjartan committed
311
}
Dries's avatar
 
Dries committed
312

Kjartan's avatar
Kjartan committed
313
function taxonomy_del_vocabulary($vid) {
314
  $vocabulary = (array) taxonomy_get_vocabulary($vid);
Dries's avatar
   
Dries committed
315

Dries's avatar
   
Dries committed
316
  db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
Dries's avatar
   
Dries committed
317
  db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
Dries's avatar
   
Dries committed
318
  $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
Kjartan's avatar
Kjartan committed
319
320
  while ($term = db_fetch_object($result)) {
    taxonomy_del_term($term->tid);
Dries's avatar
 
Dries committed
321
  }
Dries's avatar
   
Dries committed
322

Dries's avatar
   
Dries committed
323
  module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
Dries's avatar
   
Dries committed
324

Dries's avatar
   
Dries committed
325
326
  cache_clear_all();

327
  return SAVED_DELETED;
Dries's avatar
   
Dries committed
328
329
330
331
332
}

function _taxonomy_confirm_del_vocabulary($vid) {
  $vocabulary = taxonomy_get_vocabulary($vid);

333
334
335
336
  $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
  $form['vid'] = array('#type' => 'value', '#value' => $vid);
  $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
  return confirm_form('taxonomy_vocabulary_confirm_delete', $form,
337
338
                  t('Are you sure you want to delete the vocabulary %title?',
                  array('%title' => theme('placeholder', $vocabulary->name))),
339
                  'admin/content/taxonomy', t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
340
                  t('Delete'),
341
                  t('Cancel'));
Kjartan's avatar
Kjartan committed
342
}
Dries's avatar
 
Dries committed
343

344
345
346
function taxonomy_vocabulary_confirm_delete_submit($form_id, $form_values) {
  $status = taxonomy_del_vocabulary($form_values['vid']);
  drupal_set_message(t('Deleted vocabulary %name.', array('%name' => theme('placeholder', $form_values['name']))));
347
  return 'admin/content/taxonomy';
348
349
}

Kjartan's avatar
Kjartan committed
350
function taxonomy_form_term($edit = array()) {
Dries's avatar
   
Dries committed
351
  $vocabulary_id = isset($edit['vid']) ? $edit['vid'] : arg(4);
Kjartan's avatar
Kjartan committed
352
  $vocabulary = taxonomy_get_vocabulary($vocabulary_id);
Dries's avatar
   
Dries committed
353

354
  $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);
355

356
  $form['description'] = array('#type' => 'textarea', '#title' => t('Description'), '#default_value' => $edit['description'], '#description' => t('A description of the term.'));
Dries's avatar
 
Dries committed
357

Kjartan's avatar
Kjartan committed
358
  if ($vocabulary->hierarchy) {
Dries's avatar
   
Dries committed
359
360
    $parent = array_keys(taxonomy_get_parents($edit['tid']));
    $children = taxonomy_get_tree($vocabulary_id, $edit['tid']);
Dries's avatar
   
Dries committed
361

Dries's avatar
   
Dries committed
362
    // A term can't be the child of itself, nor of its children.
Dries's avatar
   
Dries committed
363
364
365
    foreach ($children as $child) {
      $exclude[] = $child->tid;
    }
Dries's avatar
   
Dries committed
366
    $exclude[] = $edit['tid'];
Dries's avatar
   
Dries committed
367

Kjartan's avatar
Kjartan committed
368
    if ($vocabulary->hierarchy == 1) {
369
      $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);
Dries's avatar
 
Dries committed
370
    }
Kjartan's avatar
Kjartan committed
371
    elseif ($vocabulary->hierarchy == 2) {
372
      $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);
Dries's avatar
 
Dries committed
373
    }
Kjartan's avatar
Kjartan committed
374
  }
Dries's avatar
 
Dries committed
375

376
  if ($vocabulary->relations) {
377
    $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']));
378
379
  }

380
381
  $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'))));
  $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.'));
382
383
384
385
386

  // Add extra term form elements.
  $extra = module_invoke_all('taxonomy', 'term', 'vocabulary');
  if (is_array($extra)) {
    foreach ($extra as $key => $element) {
387
      $extra[$key]['#weight'] = isset($extra[$key]['#weight']) ? $extra[$key]['#weight'] : -18;
388
389
390
391
392
    }
    $form = array_merge($form, $extra);
  }


393
  $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid);
394
  $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
Kjartan's avatar
Kjartan committed
395

Dries's avatar
   
Dries committed
396
  if ($edit['tid']) {
397
    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
398
    $form['tid'] = array('#type' => 'value', '#value' => $edit['tid']);
Dries's avatar
 
Dries committed
399
  }
400
  else {
401
    $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
402
  }
Dries's avatar
 
Dries committed
403

404
  return drupal_get_form('taxonomy_form_term', $form);
Kjartan's avatar
Kjartan committed
405
}
Dries's avatar
 
Dries committed
406

407
408
409
410
411
412
413
414
415
416
417
418
/**
 * Accept the form submission for a taxonomy term and save the result.
 */
function taxonomy_form_term_submit($form_id, $form_values) {
  switch (taxonomy_save_term($form_values)) {
    case SAVED_NEW:
      drupal_set_message(t('Created new term %term.', array('%term' => theme('placeholder', $form_values['name']))));
      break;
    case SAVED_UPDATED:
      drupal_set_message(t('The term %term has been updated.', array('%term' => theme('placeholder', $form_values['name']))));
      break;
  }
419
  return 'admin/content/taxonomy';
420
421
}

422
function taxonomy_save_term(&$edit) {
Dries's avatar
   
Dries committed
423
  if ($edit['tid'] && $edit['name']) {
424
    db_query("UPDATE {term_data} SET name = '%s', description = '%s', weight = %d WHERE tid = %d", $edit['name'], $edit['description'], $edit['weight'], $edit['tid']);
Dries's avatar
   
Dries committed
425
    module_invoke_all('taxonomy', 'update', 'term', $edit);
426
    $status = SAVED_UPDATED;
Kjartan's avatar
Kjartan committed
427
  }
Dries's avatar
   
Dries committed
428
429
  else if ($edit['tid']) {
    return taxonomy_del_term($edit['tid']);
Kjartan's avatar
Kjartan committed
430
431
  }
  else {
Dries's avatar
   
Dries committed
432
    $edit['tid'] = db_next_id('{term_data}_tid');
433
    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']);
Dries's avatar
   
Dries committed
434
    module_invoke_all('taxonomy', 'insert', 'term', $edit);
435
    $status = SAVED_NEW;
Kjartan's avatar
Kjartan committed
436
  }
Dries's avatar
 
Dries committed
437

Dries's avatar
   
Dries committed
438
439
440
  db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $edit['tid'], $edit['tid']);
  if ($edit['relations']) {
    foreach ($edit['relations'] as $related_id) {
Kjartan's avatar
Kjartan committed
441
      if ($related_id != 0) {
Dries's avatar
   
Dries committed
442
        db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $edit['tid'], $related_id);
Dries's avatar
 
Dries committed
443
      }
Kjartan's avatar
Kjartan committed
444
    }
Kjartan's avatar
Kjartan committed
445
  }
Dries's avatar
 
Dries committed
446

Dries's avatar
   
Dries committed
447
  db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $edit['tid']);
448
  if (!isset($edit['parent']) || empty($edit['parent'])) {
449
    $edit['parent'] = array(0);
Kjartan's avatar
Kjartan committed
450
  }
Dries's avatar
   
Dries committed
451
452
  if (is_array($edit['parent'])) {
    foreach ($edit['parent'] as $parent) {
453
454
455
456
457
458
459
460
      if (is_array($parent)) {
        foreach ($parent as $tid) {
          db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $edit['tid'], $tid);
        }
      }
      else {
        db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $edit['tid'], $parent);
      }
Dries's avatar
 
Dries committed
461
    }
Kjartan's avatar
Kjartan committed
462
  }
463
464
465
  else {
    db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $edit['tid'], $edit['parent']);
  }
Dries's avatar
 
Dries committed
466

Dries's avatar
   
Dries committed
467
468
469
  db_query('DELETE FROM {term_synonym} WHERE tid = %d', $edit['tid']);
  if ($edit['synonyms']) {
    foreach (explode ("\n", str_replace("\r", '', $edit['synonyms'])) as $synonym) {
Dries's avatar
   
Dries committed
470
      if ($synonym) {
Dries's avatar
   
Dries committed
471
        db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $edit['tid'], chop($synonym));
Dries's avatar
   
Dries committed
472
      }
Kjartan's avatar
Kjartan committed
473
    }
Dries's avatar
 
Dries committed
474
  }
Dries's avatar
   
Dries committed
475

Dries's avatar
   
Dries committed
476
477
  cache_clear_all();

478
  return $status;
Kjartan's avatar
Kjartan committed
479
}
Dries's avatar
 
Dries committed
480

Kjartan's avatar
Kjartan committed
481
function taxonomy_del_term($tid) {
482
483
484
485
486
487
488
489
490
491
492
493
494
495
  $tids = array($tid);
  while ($tids) {
    $children_tids = $orphans = array();
    foreach ($tids as $tid) {
      // See if any of the term's children are about to be become orphans:
      if ($children = taxonomy_get_children($tid)) {
        foreach ($children as $child) {
          // If the term has multiple parents, we don't delete it.
          $parents = taxonomy_get_parents($child->tid);
          if (count($parents) == 1) {
            $orphans[] = $child->tid;
          }
        }
      }
Dries's avatar
   
Dries committed
496

497
      $term = (array) taxonomy_get_term($tid);
Dries's avatar
   
Dries committed
498

499
500
501
502
503
      db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
      db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
      db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
      db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
      db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
Dries's avatar
   
Dries committed
504

505
506
      module_invoke_all('taxonomy', 'delete', 'term', $term);
    }
Dries's avatar
   
Dries committed
507

508
509
    $tids = $orphans;
  }
Dries's avatar
   
Dries committed
510

Dries's avatar
   
Dries committed
511
  cache_clear_all();
512
513

  return SAVED_DELETED;
Dries's avatar
   
Dries committed
514
515
516
517
518
}

function _taxonomy_confirm_del_term($tid) {
  $term = taxonomy_get_term($tid);

519
520
521
522
  $form['type'] = array('#type' => 'value', '#value' => 'term');
  $form['name'] = array('#type' => 'value', '#value' => $term->name);
  $form['tid'] = array('#type' => 'value', '#value' => $tid);
  return confirm_form('taxonomy_term_confirm_delete', $form,
523
524
                  t('Are you sure you want to delete the term %title?',
                  array('%title' => theme('placeholder', $term->name))),
525
                  'admin/content/taxonomy',
526
527
                  t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
                  t('Delete'),
528
                  t('Cancel'));
Kjartan's avatar
Kjartan committed
529
}
Dries's avatar
 
Dries committed
530

531
532
533
function taxonomy_term_confirm_delete_submit($form_id, $form_values) {
  taxonomy_del_term($form_values['tid']);
  drupal_set_message(t('Deleted term %name.', array('%name' => theme('placeholder', $form_values['name']))));
534
  return 'admin/content/taxonomy';
Kjartan's avatar
Kjartan committed
535
536
}

Dries's avatar
   
Dries committed
537
538
539
/**
 * Generate a form element for selecting terms from a vocabulary.
 */
Dries's avatar
   
Dries committed
540
function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') {
Dries's avatar
   
Dries committed
541
  $vocabulary = taxonomy_get_vocabulary($vid);
542
  $help = ($help) ? $help : $vocabulary->help;
Kjartan's avatar
Kjartan committed
543
544
545
546
  if ($vocabulary->required) {
    $blank = 0;
  }
  else {
Dries's avatar
   
Dries committed
547
    $blank = '<'. t('none') .'>';
Kjartan's avatar
Kjartan committed
548
  }
Dries's avatar
   
Dries committed
549

550
  return _taxonomy_term_select(check_plain($vocabulary->name), $name, $value, $vid, $help, intval($vocabulary->multiple), $blank);
Kjartan's avatar
Kjartan committed
551
}
Dries's avatar
 
Dries committed
552

553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
/**
 * Generate a set of options for selecting a term from all vocabularies. Can be
 * passed to form_select.
 */
function taxonomy_form_all($free_tags = 0) {
  $vocabularies = taxonomy_get_vocabularies();
  $options = array();
  foreach ($vocabularies as $vid => $vocabulary) {
    if ($vocabulary->tags && !$free_tags) { continue; }
    $tree = taxonomy_get_tree($vid);
    $options[$vocabulary->name] = array();
    if ($tree) {
      foreach ($tree as $term) {
        $options[$vocabulary->name][$term->tid] = _taxonomy_depth($term->depth, '-') . $term->name;
      }
    }
  }
  return $options;
}

Dries's avatar
   
Dries committed
573
574
575
576
577
578
/**
 * Return an array of all vocabulary objects.
 *
 * @param $type
 *   If set, return only those vocabularies associated with this node type.
 */
Dries's avatar
   
Dries committed
579
function taxonomy_get_vocabularies($type = NULL) {
Kjartan's avatar
Kjartan committed
580
  if ($type) {
581
    $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);
Kjartan's avatar
Kjartan committed
582
583
  }
  else {
584
    $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'));
Kjartan's avatar
Kjartan committed
585
  }
Dries's avatar
   
Dries committed
586

Kjartan's avatar
Kjartan committed
587
  $vocabularies = array();
Dries's avatar
   
Dries committed
588
  $node_types = array();
Kjartan's avatar
Kjartan committed
589
  while ($voc = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
590
591
592
593
    $node_types[$voc->vid][] = $voc->type;
    unset($voc->type);
    $voc->nodes = $node_types[$voc->vid];
    $vocabularies[$voc->vid] = $voc;
Dries's avatar
 
Dries committed
594
  }
Dries's avatar
   
Dries committed
595

Kjartan's avatar
Kjartan committed
596
597
  return $vocabularies;
}
Dries's avatar
 
Dries committed
598

Dries's avatar
   
Dries committed
599
600
601
/**
 * Generate a form for selecting terms to associate with a node.
 */
602
603
604
605
function taxonomy_form_alter($form_id, &$form) {
  if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
    $node = $form['#node'];

606
    if (!isset($node->taxonomy)) {
607
608
609
610
611
612
      if ($node->nid) {
        $terms = taxonomy_node_get_terms($node->nid);
      }
      else {
        $terms = array();
      }
Kjartan's avatar
Kjartan committed
613
614
    }
    else {
615
      $terms = $node->taxonomy;
Dries's avatar
 
Dries committed
616
617
    }

618
    $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);
Dries's avatar
Dries committed
619

620
621
622
623
    while ($vocabulary = db_fetch_object($c)) {
      if ($vocabulary->tags) {
        $typed_terms = array();
        foreach ($terms as $term) {
624
          // Extract terms belonging to the vocabulary in question.
625
          if ($term->vid == $vocabulary->vid) {
Dries's avatar
Dries committed
626

627
628
629
630
631
632
633
            // Commas and quotes in terms are special cases, so encode 'em.
            if (preg_match('/,/', $term->name) || preg_match('/"/', $term->name)) {
              $term->name = '"'.preg_replace('/"/', '""', $term->name).'"';
            }

            $typed_terms[] = $term->name;
          }
Dries's avatar
Dries committed
634
        }
635
        $typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
636

637
638
639
640
        if ($vocabulary->help) {
          $help = $vocabulary->help;
        }
        else {
641
          $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
642
        }
643
644
645
646
647
648
649
        $form['taxonomy']['tags'][$vocabulary->vid] = array('#type' => 'textfield',
          '#title' => $vocabulary->name,
          '#description' => $help,
          '#required' => $vocabulary->required,
          '#default_value' => $typed_string,
          '#autocomplete_path' => 'taxonomy/autocomplete/'. $vocabulary->vid,
          '#weight' => $vocabulary->weight,
650
          '#maxlength' => 255,
651
        );
652
653
      }
      else {
654
655
656
657
658
659
660
661
        // Extract terms belonging to the vocabulary in question.
        $default_terms = array();
        foreach ($terms as $term) {
          if ($term->vid == $vocabulary->vid) {
            $default_terms[$term->tid] = $term;
          }
        }
        $form['taxonomy'][$vocabulary->vid] = taxonomy_form($vocabulary->vid, array_keys($default_terms), $vocabulary->help);
662
        $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
663
        $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
664
      }
Dries's avatar
Dries committed
665
    }
666
    if (isset($form['taxonomy'])) {
667
      $form['taxonomy'] += array('#type' => 'fieldset', '#title' => t('Categories'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#tree' => TRUE, '#weight' => -3);
Dries's avatar
Dries committed
668
    }
Dries's avatar
 
Dries committed
669
  }
Kjartan's avatar
Kjartan committed
670
}
Dries's avatar
 
Dries committed
671

Dries's avatar
   
Dries committed
672
673
674
675
/**
 * Find all terms associated to the given node, within one vocabulary.
 */
function taxonomy_node_get_terms_by_vocabulary($nid, $vid, $key = 'tid') {
676
  $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);
Kjartan's avatar
Kjartan committed
677
678
679
  $terms = array();
  while ($term = db_fetch_object($result)) {
    $terms[$term->$key] = $term;
Dries's avatar
 
Dries committed
680
  }
Kjartan's avatar
Kjartan committed
681
682
683
  return $terms;
}

Dries's avatar
   
Dries committed
684
/**
685
 * Find all terms associated to the given node, ordered by vocabulary and term weight.
Dries's avatar
   
Dries committed
686
687
 */
function taxonomy_node_get_terms($nid, $key = 'tid') {
Kjartan's avatar
Kjartan committed
688
  static $terms;
Dries's avatar
 
Dries committed
689

Dries's avatar
   
Dries committed
690
  if (!isset($terms[$nid])) {
691
    $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);
Kjartan's avatar
Kjartan committed
692
    $terms[$nid] = array();
Dries's avatar
 
Dries committed
693
    while ($term = db_fetch_object($result)) {
Kjartan's avatar
Kjartan committed
694
      $terms[$nid][$term->$key] = $term;
Dries's avatar
 
Dries committed
695
696
    }
  }
Kjartan's avatar
Kjartan committed
697
698
  return $terms[$nid];
}
Dries's avatar
 
Dries committed
699

Dries's avatar
Dries committed
700
701
702
703
704
705
706
707
708
709
/**
 * Make sure incoming vids are free tagging enabled.
 */
function taxonomy_node_validate(&$node) {
  if ($node->taxonomy) {
    $terms = $node->taxonomy;
    if ($terms['tags']) {
      foreach ($terms['tags'] as $vid => $vid_value) {
        $vocabulary = taxonomy_get_vocabulary($vid);
        if (!$vocabulary->tags) {
710
711
712
          // see form_get_error $key = implode('][', $element['#parents']);
          // on why this is the key
          form_set_error("taxonomy][tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => theme('placeholder', $vocabulary->name))));
Dries's avatar
Dries committed
713
714
715
716
717
718
        }
      }
    }
  }
}

Dries's avatar
   
Dries committed
719
720
721
/**
 * Save term associations for a given node.
 */
Kjartan's avatar
Kjartan committed
722
function taxonomy_node_save($nid, $terms) {
723
  taxonomy_node_delete($nid);
Dries's avatar
Dries committed
724
725
726

  // Free tagging vocabularies do not send their tids in the form,
  // so we'll detect them here and process them independently.
727
  if (isset($terms['tags'])) {
Dries's avatar
Dries committed
728
729
730
731
732
733
734
735
    $typed_input = $terms['tags'];
    unset($terms['tags']);

    foreach ($typed_input as $vid => $vid_value) {
      // This regexp allows the following types of user input:
      // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
      $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
      preg_match_all($regexp, $vid_value, $matches);
736
      $typed_terms = array_unique($matches[1]);
Dries's avatar
Dries committed
737

738
      $inserted = array();
Dries's avatar
Dries committed
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
      foreach ($typed_terms as $typed_term) {
        // If a user has escaped a term (to demonstrate that it is a group,
        // or includes a comma or quote character), we remove the escape
        // formatting so to save the term into the DB as the user intends.
        $typed_term = str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term));
        $typed_term = trim($typed_term);
        if ($typed_term == "") { continue; }

        // See if the term exists in the chosen vocabulary
        // and return the tid, otherwise, add a new record.
        $possibilities = taxonomy_get_term_by_name($typed_term);
        $typed_term_tid = NULL; // tid match if any.
        foreach ($possibilities as $possibility) {
          if ($possibility->vid == $vid) {
            $typed_term_tid = $possibility->tid;
          }
        }

        if (!$typed_term_tid) {
758
759
760
          $edit = array('vid' => $vid, 'name' => $typed_term);
          $status = taxonomy_save_term($edit);
          $typed_term_tid = $edit['tid'];
Dries's avatar
Dries committed
761
762
        }

763
764
765
766
767
        // Defend against duplicate, different cased tags
        if (!isset($inserted[$typed_term_tid])) {
          db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $typed_term_tid);
          $inserted[$typed_term_tid] = TRUE;
        }
Dries's avatar
Dries committed
768
769
770
      }
    }
  }
Dries's avatar
 
Dries committed
771

772
  if (is_array($terms)) {
Dries's avatar
   
Dries committed
773
    foreach ($terms as $term) {
774
775
776
777
778
779
780
      if (is_array($term)) {
        foreach ($term as $tid) {
          if ($tid) {
            db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $tid);
          }
        }
      }
781
782
783
      else if (is_object($term)) {
        db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term->tid);
      }
784
      else if ($term) {
Dries's avatar
   
Dries committed
785
        db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term);
786
      }
Dries's avatar
 
Dries committed
787
788
    }
  }
Kjartan's avatar
Kjartan committed
789
}
Dries's avatar
 
Dries committed
790

Dries's avatar
   
Dries committed
791
792
793
/**
 * Remove associations of a node to its terms.
 */
Kjartan's avatar
Kjartan committed
794
function taxonomy_node_delete($nid) {
Dries's avatar
   
Dries committed
795
  db_query('DELETE FROM {term_node} WHERE nid = %d', $nid);
Kjartan's avatar
Kjartan committed
796
}
Dries's avatar
 
Dries committed
797

Dries's avatar
   
Dries committed
798
799
800
801
/**
 * Find all term objects related to a given term ID.
 */
function taxonomy_get_related($tid, $key = 'tid') {
Kjartan's avatar
Kjartan committed
802
  if ($tid) {
Dries's avatar
   
Dries committed
803
    $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);
Kjartan's avatar
Kjartan committed
804
805
806
    $related = array();
    while ($term = db_fetch_object($result)) {
      $related[$term->$key] = $term;
Dries's avatar
 
Dries committed
807
    }
Kjartan's avatar
Kjartan committed
808
    return $related;
Dries's avatar
 
Dries committed
809
  }
Kjartan's avatar
Kjartan committed
810
811
  else {
    return array();
Dries's avatar
 
Dries committed
812
  }
Kjartan's avatar
Kjartan committed
813
}
Dries's avatar
 
Dries committed
814

Dries's avatar
   
Dries committed
815
816
817
818
/**
 * Find all parents of a given term ID.
 */
function taxonomy_get_parents($tid, $key = 'tid') {
Kjartan's avatar
Kjartan committed
819
  if ($tid) {
820
    $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);
Kjartan's avatar
Kjartan committed
821
822
823
    $parents = array();
    while ($parent = db_fetch_object($result)) {
      $parents[$parent->$key] = $parent;
Dries's avatar
   
Dries committed
824
    }
Kjartan's avatar
Kjartan committed
825
    return $parents;
Dries's avatar
 
Dries committed
826
  }
Kjartan's avatar
Kjartan committed
827
828
829
830
  else {
    return array();
  }
}
Dries's avatar
 
Dries committed
831

Dries's avatar
   
Dries committed
832
833
834
835
/**
 * Find all ancestors of a given term ID.
 */
function taxonomy_get_parents_all($tid) {
Dries's avatar
   
Dries committed
836
837
838
839
840
841
842
843
844
845
846
847
  $parents = array();
  if ($tid) {
    $parents[] = taxonomy_get_term($tid);
    $n = 0;
    while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
      $parents = array_merge($parents, $parent);
      $n++;
    }
  }
  return $parents;
}

Dries's avatar
   
Dries committed
848
849
850
851
/**
 * Find all children of a term ID.
 */
function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
Kjartan's avatar
Kjartan committed
852
  if ($vid) {
853
    $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);
Dries's avatar
 
Dries committed
854
  }
Kjartan's avatar
Kjartan committed
855
  else {
856
    $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);
Kjartan's avatar
Kjartan committed
857
858
859
860
861
862
863
  }
  $children = array();
  while ($term = db_fetch_object($result)) {
    $children[$term->$key] = $term;
  }
  return $children;
}
Dries's avatar
 
Dries committed
864

Dries's avatar
   
Dries committed
865
866
867
868
869
870
871
872
873
874
875
876
877
/**
 * Create a hierarchical representation of a vocabulary.
 *
 * @param $vid
 *   Which vocabulary to generate the tree for.
 *
 * @param $parent
 *   The term ID under which to generate the tree. If 0, generate the tree
 *   for the entire vocabulary.
 *
 * @param $depth
 *   Internal use only.
 *
Dries's avatar
   
Dries committed
878
879
880
 * @param $max_depth
 *   The number of levels of the tree to return. Leave NULL to return all levels.
 *
Dries's avatar
   
Dries committed
881
882
883
884
885
 * @return
 *   An array of all term objects in the tree. Each term object is extended
 *   to have "depth" and "parents" attributes in addition to its normal ones.
 */
function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
Dries's avatar
   
Dries committed
886
  static $children, $parents, $terms;
Dries's avatar
   
Dries committed
887

Kjartan's avatar
Kjartan committed
888
  $depth++;
Dries's avatar
   
Dries committed
889

Dries's avatar
   
Dries committed
890
891
892
893
  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
  // and its children, too.
  if (!isset($children[$vid])) {
    $children[$vid] = array();
Dries's avatar
   
Dries committed
894

895
    $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);
Dries's avatar
   
Dries committed
896
    while ($term = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
897
898
899
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
Dries's avatar
 
Dries committed
900
901
    }
  }
Dries's avatar
   
Dries committed
902

Dries's avatar
   
Dries committed
903
  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
Dries's avatar
Dries committed
904
905
906
907
908
909
910
911
912
913
914
915
  if ($children[$vid][$parent]) {
    foreach ($children[$vid][$parent] as $child) {
      if ($max_depth > $depth) {
        $terms[$vid][$child]->depth = $depth;
        // The "parent" attribute is not useful, as it would show one parent only.
        unset($terms[$vid][$child]->parent);
        $terms[$vid][$child]->parents = $parents[$vid][$child];
        $tree[] = $terms[$vid][$child];

        if ($children[$vid][$child]) {
          $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $depth, $max_depth));
        }
Dries's avatar
   
Dries committed
916
      }
Dries's avatar
   
Dries committed
917
    }
Kjartan's avatar
Kjartan committed
918
  }
Dries's avatar
   
Dries committed
919

Dries's avatar
   
Dries committed
920
  return $tree ? $tree : array();
Kjartan's avatar
Kjartan committed
921
}
Dries's avatar
 
Dries committed
922

Dries's avatar
   
Dries committed
923
924
925
/**
 * Return an array of synonyms of the given term ID.
 */
Kjartan's avatar
Kjartan committed
926
927
function taxonomy_get_synonyms($tid) {
  if ($tid) {
Dries's avatar
   
Dries committed
928
    $result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d', $tid);
Kjartan's avatar
Kjartan committed
929
    while ($synonym = db_fetch_array($result)) {
Dries's avatar
   
Dries committed
930
      $synonyms[] = $synonym['name'];
Dries's avatar
 
Dries committed
931
    }
Kjartan's avatar
Kjartan committed
932
    return $synonyms ? $synonyms : array();
Dries's avatar
 
Dries committed
933
  }
Kjartan's avatar
Kjartan committed
934
935
  else {
    return array();
Dries's avatar
   
Dries committed
936
  }
Kjartan's avatar
Kjartan committed
937
}
Dries's avatar
   
Dries committed
938

Dries's avatar
   
Dries committed
939
940
941
942
943
/**
 * Return the term object that has the given string as a synonym.
 */
function taxonomy_get_synonym_root($synonym