taxonomy.module 51.5 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
33
34
35
36
        $links['taxonomy_term_'. $term->tid] = array(
          '#title' => $term->name,
          '#href' => 'taxonomy/term/'. $term->tid,
          '#attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
        );
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
  }
}

Dries's avatar
   
Dries committed
50
51
52
/**
 * Implementation of hook_menu().
 */
Dries's avatar
   
Dries committed
53
function taxonomy_menu($may_cache) {
Dries's avatar
   
Dries committed
54
  $items = array();
Dries's avatar
   
Dries committed
55

Dries's avatar
   
Dries committed
56
  if ($may_cache) {
57
58
59
    $items[] = array('path' => 'admin/taxonomy',
      'title' => t('categories'),
      'callback' => 'taxonomy_overview_vocabularies',
Dries's avatar
   
Dries committed
60
      'access' => user_access('administer taxonomy'));
61

62
63
64
65
    $items[] = array('path' => 'admin/taxonomy/list',
      'title' => t('list'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10);
66

67
68
69
    $items[] = array('path' => 'admin/taxonomy/add/vocabulary',
      'title' => t('add vocabulary'),
      'callback' => 'taxonomy_admin_vocabulary_edit',
Dries's avatar
   
Dries committed
70
71
72
      'access' => user_access('administer taxonomy'),
      'type' => MENU_LOCAL_TASK);

73
74
75
    $items[] = array('path' => 'admin/taxonomy/edit/vocabulary',
      'title' => t('edit vocabulary'),
      'callback' => 'taxonomy_admin_vocabulary_edit',
76
77
78
      'access' => user_access('administer taxonomy'),
      'type' => MENU_CALLBACK);

79
80
81
    $items[] = array('path' => 'admin/taxonomy/edit/term',
      'title' => t('edit term'),
      'callback' => 'taxonomy_admin_term_edit',
82
83
84
      'access' => user_access('administer taxonomy'),
      'type' => MENU_CALLBACK);

85
86
    $items[] = array('path' => 'taxonomy/term',
      'title' => t('taxonomy term'),
Dries's avatar
   
Dries committed
87
88
89
      'callback' => 'taxonomy_term_page',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK);
Steven Wittens's avatar
Steven Wittens committed
90

91
92
    $items[] = array('path' => 'taxonomy/autocomplete',
      'title' => t('autocomplete taxonomy'),
Steven Wittens's avatar
Steven Wittens committed
93
94
95
      'callback' => 'taxonomy_autocomplete',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK);
Dries's avatar
   
Dries committed
96
  }
Dries's avatar
Dries committed
97
98
  else {
    if (is_numeric(arg(2))) {
99
100
101
102
      $items[] = array('path' => 'admin/taxonomy/' . arg(2),
        'title' => t('list terms'),
        'callback' => 'taxonomy_overview_terms',
        'callback arguments' => array(arg(2)),
Dries's avatar
Dries committed
103
104
105
        'access' => user_access('administer taxonomy'),
        'type' => MENU_CALLBACK);

106
107
108
109
      $items[] = array('path' => 'admin/taxonomy/' . arg(2) . '/list',
        'title' => t('list'),
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => -10);
Dries's avatar
Dries committed
110

111
112
      $items[] = array('path' => 'admin/taxonomy/' . arg(2) . '/add/term',
        'title' => t('add term'),
113
114
        'callback' => 'taxonomy_form_term',
        'callback arguments' => array(array('vid' => arg(2))),
Dries's avatar
Dries committed
115
116
117
118
        'access' => user_access('administer taxonomy'),
        'type' => MENU_LOCAL_TASK);
    }
  }
Dries's avatar
   
Dries committed
119

Dries's avatar
   
Dries committed
120
121
  return $items;
}
Dries's avatar
 
Dries committed
122

123
124
125
126
/**
 * List and manage vocabularies.
 */
function taxonomy_overview_vocabularies() {
127
  $vocabularies = taxonomy_get_vocabularies();
128
  $rows = array();
129
130
131
132
133
134
  foreach ($vocabularies as $vocabulary) {
    $types = array();
    foreach ($vocabulary->nodes as $type) {
      $node_type = node_get_name($type);
      $types[] = $node_type ? $node_type : $type;
    }
135
136
137
138
139
140
141
142
    $rows[] = array('name' => check_plain($vocabulary->name),
      'type' => implode(', ', $types),
      'edit' => l(t('edit vocabulary'), "admin/taxonomy/edit/vocabulary/$vocabulary->vid"),
      'list' => l(t('list terms'), "admin/taxonomy/$vocabulary->vid"),
      'add' => l(t('add terms'), "admin/taxonomy/$vocabulary->vid/add/term")
    );
  }
  if (empty($rows)) {
drumm's avatar
drumm committed
143
    $rows[] = array(array('data' => t('No categories available.'), 'colspan' => '5', 'class' => 'message'));
144
  }
drumm's avatar
drumm committed
145
  $header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3'));
146

147
  return theme('table', $header, $rows, array('id' => 'taxonomy'));
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
}

/**
 * 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; }
    $rows[] = array(_taxonomy_depth($term->depth) . ' ' . l($term->name, "taxonomy/term/$term->tid"), l(t('edit'), "admin/taxonomy/edit/term/$term->tid", array(), $destination));
    $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
191
function taxonomy_form_vocabulary($edit = array()) {
192
193
194
195
  $form['name'] = array('#type' => 'textfield',
    '#title' => t('Vocabulary name'),
    '#default_value' => $edit['name'],
    '#maxlength' => 64,
196
    '#description' => t('The name for this vocabulary. Example: "Topic".'),
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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
    '#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'],
    '#maxlength' => 255,
    '#description' => t('Instructions to present to the user when choosing a term.'),
  );
  $form['nodes'] = array('#type' => 'checkboxes',
    '#title' => t('Types'),
    '#default_value' => $edit['nodes'],
    '#options' => node_get_types(),
    '#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.'),
  );
248

249
250
251
252
  // Add extra vocabulary form elements.
  $extra = module_invoke_all('taxonomy', 'form', 'vocabulary');
  if (is_array($extra)) {
    foreach ($extra as $key => $element) {
253
      $extra[$key]['#weight'] = isset($extra[$key]['#weight']) ? $extra[$key]['#weight'] : -18;
254
255
256
    }
    $form = array_merge($form, $extra);
  }
Dries's avatar
 
Dries committed
257

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

267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/**
 * 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;
  }
  return 'admin/taxonomy';
}

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

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

  cache_clear_all();
Dries's avatar
   
Dries committed
310

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

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

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

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

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

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

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

334
335
336
337
  $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,
338
339
340
                  t('Are you sure you want to delete the vocabulary %title?',
                  array('%title' => theme('placeholder', $vocabulary->name))),
                  'admin/taxonomy', t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
341
                  t('Delete'),
342
                  t('Cancel'));
Kjartan's avatar
Kjartan committed
343
}
Dries's avatar
 
Dries committed
344

345
346
347
348
349
350
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']))));
  return 'admin/taxonomy';
}

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

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

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

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

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

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

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

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

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


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

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

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

408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/**
 * 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;
  }
  return 'admin/taxonomy';
}

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

Dries's avatar
   
Dries committed
439
440
441
  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
442
      if ($related_id != 0) {
Dries's avatar
   
Dries committed
443
        db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $edit['tid'], $related_id);
Dries's avatar
 
Dries committed
444
      }
Kjartan's avatar
Kjartan committed
445
    }
Kjartan's avatar
Kjartan committed
446
  }
Dries's avatar
 
Dries committed
447

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

Dries's avatar
   
Dries committed
468
469
470
  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
471
      if ($synonym) {
Dries's avatar
   
Dries committed
472
        db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $edit['tid'], chop($synonym));
Dries's avatar
   
Dries committed
473
      }
Kjartan's avatar
Kjartan committed
474
    }
Dries's avatar
 
Dries committed
475
  }
Dries's avatar
   
Dries committed
476

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

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

Kjartan's avatar
Kjartan committed
482
function taxonomy_del_term($tid) {
483
484
485
486
487
488
489
490
491
492
493
494
495
496
  $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
497

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

500
501
502
503
504
      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
505

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

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

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

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

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

520
521
522
523
  $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,
524
525
                  t('Are you sure you want to delete the term %title?',
                  array('%title' => theme('placeholder', $term->name))),
526
527
528
                  'admin/taxonomy',
                  t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
                  t('Delete'),
529
                  t('Cancel'));
Kjartan's avatar
Kjartan committed
530
}
Dries's avatar
 
Dries committed
531

532
533
534
535
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']))));
  return 'admin/taxonomy';
Kjartan's avatar
Kjartan committed
536
537
}

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

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

554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
/**
 * 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
574
575
576
577
578
579
/**
 * 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
580
function taxonomy_get_vocabularies($type = NULL) {
Kjartan's avatar
Kjartan committed
581
  if ($type) {
582
    $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
583
584
  }
  else {
585
    $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
586
  }
Dries's avatar
   
Dries committed
587

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

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

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

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

619
    $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
620

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

628
629
630
631
632
633
634
            // 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
635
        }
636
        $typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
637

638
639
640
641
        if ($vocabulary->help) {
          $help = $vocabulary->help;
        }
        else {
642
          $help = t('A comma-separated list of terms describing this content. Example: funny, bungie jumping, "Company, Inc.".');
643
        }
644
645
646
647
648
649
650
        $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,
651
          '#maxlength' => 255,
652
        );
653
654
      }
      else {
655
656
657
658
659
660
661
662
        // 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);
663
        $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
664
        $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
665
      }
Dries's avatar
Dries committed
666
    }
667
    if (isset($form['taxonomy'])) {
668
      $form['taxonomy'] += array('#type' => 'fieldset', '#title' => t('Categories'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#tree' => TRUE, '#weight' => -3);
Dries's avatar
Dries committed
669
    }
Dries's avatar
 
Dries committed
670
  }
Kjartan's avatar
Kjartan committed
671
}
Dries's avatar
 
Dries committed
672

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

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

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

Dries's avatar
Dries committed
701
702
703
704
705
706
707
708
709
710
/**
 * 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) {
711
712
713
          // 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
714
715
716
717
718
719
        }
      }
    }
  }
}

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

  // Free tagging vocabularies do not send their tids in the form,
  // so we'll detect them here and process them independently.
728
  if (isset($terms['tags'])) {
Dries's avatar
Dries committed
729
730
731
732
733
734
735
736
    $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);
737
      $typed_terms = array_unique($matches[1]);
Dries's avatar
Dries committed
738

739
      $inserted = array();
Dries's avatar
Dries committed
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
      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) {
759
760
761
          $edit = array('vid' => $vid, 'name' => $typed_term);
          $status = taxonomy_save_term($edit);
          $typed_term_tid = $edit['tid'];
Dries's avatar
Dries committed
762
763
        }

764
765
766
767
768
        // 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
769
770
771
      }
    }
  }
Dries's avatar
 
Dries committed
772

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

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

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

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

Dries's avatar
   
Dries committed
833
834
835
836
/**
 * Find all ancestors of a given term ID.
 */
function taxonomy_get_parents_all($tid) {
Dries's avatar
   
Dries committed
837
838
839
840
841
842
843
844
845
846
847
848
  $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
849
850
851
852
/**
 * Find all children of a term ID.
 */
function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
Kjartan's avatar
Kjartan committed
853
  if ($vid) {
854
    $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
855
  }
Kjartan's avatar
Kjartan committed
856
  else {
857
    $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
858
859
860
861
862
863
864
  }
  $children = array();
  while ($term = db_fetch_object($result)) {
    $children[$term->$key] = $term;
  }
  return $children;
}
Dries's avatar
 
Dries committed
865

Dries's avatar
   
Dries committed
866
867
868
869
870
871
872
873
874
875
876
877
878
/**
 * 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
879
880
881
 * @param $max_depth
 *   The number of levels of the tree to return. Leave NULL to return all levels.
 *
Dries's avatar
   
Dries committed
882
883
884
885
886
 * @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
887
  static $children, $parents, $terms;
Dries's avatar
   
Dries committed
888

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

Dries's avatar
   
Dries committed
891
892
893
894
  // 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
895

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

Dries's avatar
   
Dries committed
904
  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
Dries's avatar
Dries committed
905
906
907
908
909
910
911
912
913
914
915
916
  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
917
      }
Dries's avatar
   
Dries committed
918
    }
Kjartan's avatar
Kjartan committed
919
  }
Dries's avatar
   
Dries committed
920

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

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

Dries's avatar
   
Dries committed
940
941
942
943
944
/**
 * Return the term object that has the given string as a synonym.
 */
function taxonomy_get_synonym_root($synonym) {
  return db_fetch_object(db_query("SELECT * FROM {term_synonym} s, {term_data} t WHERE t.tid = s.tid AND s.name = '%s'", $synonym));
Kjartan's avatar
Kjartan committed
945
}
Dries's avatar
   
Dries committed
946

Dries's avatar
   
Dries committed
947
948
949
/**
 * Given a term id, count the number of published nodes in it.
 */
Dries's avatar
   
Dries committed
950
function taxonomy_term_count_nodes($tid, $type = 0) {
Kjartan's avatar
Kjartan committed
951
  static $count;
Dries's avatar
   
Dries committed
952

Dries's avatar
   
Dries committed
953
954
955
  if (!isset($count[$type])) {
    // $type == 0 always evaluates true is $type is a string
    if (is_numeric($type)) {
956
      $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'));
Dries's avatar
   
Dries committed
957
958
    }
    else {
959
      $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);
Dries's avatar
   
Dries committed
960
    }
Kjartan's avatar
Kjartan committed
961
    while ($term = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
962
      $count[$type][$term->tid] = $term->c;
Dries's avatar
   
Dries committed
963
964
965
    }
  }

Kjartan's avatar
Kjartan committed
966
  foreach (_taxonomy_term_children($tid) as $c) {
Dries's avatar
   
Dries committed
967
    $children_count += taxonomy_term_count_nodes($c, $type);
Kjartan's avatar
Kjartan committed
968
  }
Dries's avatar
   
Dries committed
969
  return $count[$type][$tid] + $children_count;
Kjartan's avatar
Kjartan committed
970
971
}

Dries's avatar
   
Dries committed
972
973
974
/**
 * Helper for taxonomy_term_count_nodes().
 */
Kjartan's avatar
Kjartan committed
975
976
function _taxonomy_term_children($tid) {
  static $children;
Dries's avatar
   
Dries committed
977