taxonomy.module 38.4 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

16
17
18
19
20
21
22
23
24
25
26
/**
 * Implementation of hook_theme()
 */
function taxonomy_theme() {
  return array(
    'taxonomy_term_select' => array(
      'arguments' => array('element' => NULL),
    ),
  );
}

Dries's avatar
   
Dries committed
27
28
29
30
31
32
33
/**
 * 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:
 *
34
 * if (module_exists('taxonomy')) {
35
36
 *   $terms = taxonomy_link('taxonomy terms', $node);
 *   print theme('links', $terms);
Dries's avatar
   
Dries committed
37
38
 * }
 */
Dries's avatar
   
Dries committed
39
function taxonomy_link($type, $node = NULL) {
Dries's avatar
   
Dries committed
40
  if ($type == 'taxonomy terms' && $node != NULL) {
Kjartan's avatar
   
Kjartan committed
41
    $links = array();
42
    if (!empty($node->taxonomy)) {
43
      foreach ($node->taxonomy as $term) {
44
45
46
47
        // On preview, we get tids.
        if (is_numeric($term)) {
          $term = taxonomy_get_term($term);
        }
48
        $links['taxonomy_term_'. $term->tid] = array(
49
50
51
          'title' => $term->name,
          'href' => taxonomy_term_path($term),
          'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
52
        );
Dries's avatar
   
Dries committed
53
      }
Dries's avatar
   
Dries committed
54
    }
Kjartan's avatar
Kjartan committed
55

56
    // We call this hook again because some modules and themes call taxonomy_link('taxonomy terms') directly
57
    drupal_alter('link', $links, $node);
58
59

    return $links;
60
61
62
  }
}

63
64
65
66
67
68
69
70
71
72
/**
 * For vocabularies not maintained by taxonomy.module, give the maintaining
 * module a chance to provide a path for terms in that vocabulary.
 *
 * @param $term
 *   A term object.
 * @return
 *   An internal Drupal path.
 */

73
function taxonomy_term_path($term) {
74
  $vocabulary = taxonomy_vocabulary_load($term->vid);
75
76
77
78
79
80
  if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
    return $path;
  }
  return 'taxonomy/term/'. $term->tid;
}

Dries's avatar
   
Dries committed
81
82
83
/**
 * Implementation of hook_menu().
 */
84
85
function taxonomy_menu() {
  $items['admin/content/taxonomy'] = array(
86
87
    'title' => 'Categories',
    'description' => 'Create vocabularies and terms to categorize your content.',
88
89
    'page callback' => 'taxonomy_overview_vocabularies',
    'access arguments' => array('administer taxonomy'),
90
    'file' => 'taxonomy.admin.inc',
91
92
93
  );

  $items['admin/content/taxonomy/list'] = array(
94
    'title' => 'List',
95
96
97
98
99
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

  $items['admin/content/taxonomy/add/vocabulary'] = array(
100
    'title' => 'Add vocabulary',
101
102
103
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_form_vocabulary'),
    'type' => MENU_LOCAL_TASK,
104
    'parent' => 'admin/content/taxonomy',
105
    'file' => 'taxonomy.admin.inc',
106
107
  );

108
  $items['admin/content/taxonomy/edit/vocabulary/%taxonomy_vocabulary'] = array(
109
    'title' => 'Edit vocabulary',
110
111
112
    'page callback' => 'taxonomy_admin_vocabulary_edit',
    'page arguments' => array(5),
    'type' => MENU_CALLBACK,
113
    'file' => 'taxonomy.admin.inc',
114
115
116
  );

  $items['admin/content/taxonomy/edit/term'] = array(
117
    'title' => 'Edit term',
118
119
    'page callback' => 'taxonomy_admin_term_edit',
    'type' => MENU_CALLBACK,
120
    'file' => 'taxonomy.admin.inc',
121
122
123
  );

  $items['taxonomy/term'] = array(
124
    'title' => 'Taxonomy term',
125
126
127
    'page callback' => 'taxonomy_term_page',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
128
    'file' => 'taxonomy.pages.inc',
129
130
131
  );

  $items['taxonomy/autocomplete'] = array(
132
    'title' => 'Autocomplete taxonomy',
133
134
135
    'page callback' => 'taxonomy_autocomplete',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
136
    'file' => 'taxonomy.pages.inc',
137
  );
138
  $items['admin/content/taxonomy/%taxonomy_vocabulary'] = array(
139
    'title' => 'List terms',
140
141
142
143
    'page callback' => 'taxonomy_overview_terms',
    'page arguments' => array(3),
    'access arguments' => array('administer taxonomy'),
    'type' => MENU_CALLBACK,
144
    'file' => 'taxonomy.admin.inc',
145
146
  );

147
  $items['admin/content/taxonomy/%taxonomy_vocabulary/list'] = array(
148
    'title' => 'List',
149
150
151
152
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

153
  $items['admin/content/taxonomy/%taxonomy_vocabulary/add/term'] = array(
154
    'title' => 'Add term',
155
156
157
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_form_term', 3),
    'type' => MENU_LOCAL_TASK,
158
    'parent' => 'admin/content/taxonomy/%taxonomy_vocabulary',
159
    'file' => 'taxonomy.admin.inc',
160
  );
Dries's avatar
   
Dries committed
161

Dries's avatar
   
Dries committed
162
163
  return $items;
}
Dries's avatar
   
Dries committed
164

165
function taxonomy_save_vocabulary(&$edit) {
166
  $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
Dries's avatar
   
Dries committed
167

168
  if (!empty($edit['vid']) && !empty($edit['name'])) {
169
    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
170
    db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d", $edit['vid']);
171
    foreach ($edit['nodes'] as $type => $selected) {
Dries's avatar
   
Dries committed
172
173
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
    }
Dries's avatar
   
Dries committed
174
    module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
175
    $status = SAVED_UPDATED;
Dries's avatar
   
Dries committed
176
  }
177
  else if (!empty($edit['vid'])) {
178
    $status = taxonomy_del_vocabulary($edit['vid']);
Kjartan's avatar
Kjartan committed
179
180
  }
  else {
181
182
    db_query("INSERT INTO {vocabulary} (name, description, help, multiple, required, hierarchy, relations, tags, weight, module) VALUES ('%s', '%s', '%s', %d, %d, %d, %d, %d, %d, '%s')", $edit['name'], isset($edit['description']) ? $edit['description'] : NULL, isset($edit['help']) ? $edit['help'] : NULL, $edit['multiple'], $edit['required'], $edit['hierarchy'], $edit['relations'], isset($edit['tags']) ? $edit['tags'] : NULL, $edit['weight'], isset($edit['module']) ? $edit['module'] : 'taxonomy');
    $edit['vid'] = db_last_insert_id('vocabulary', 'vid');
183
    foreach ($edit['nodes'] as $type => $selected) {
Dries's avatar
   
Dries committed
184
185
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
    }
Dries's avatar
   
Dries committed
186
    module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
187
    $status = SAVED_NEW;
Kjartan's avatar
Kjartan committed
188
  }
Dries's avatar
   
Dries committed
189
190

  cache_clear_all();
Dries's avatar
   
Dries committed
191

192
  return $status;
Kjartan's avatar
Kjartan committed
193
}
Dries's avatar
   
Dries committed
194

195
196
197
198
199
200
201
202
/**
 * Delete a vocabulary.
 *
 * @param $vid
 *   A vocabulary ID.
 * @return
 *   Constant indicating items were deleted.
 */
Kjartan's avatar
Kjartan committed
203
function taxonomy_del_vocabulary($vid) {
204
  $vocabulary = (array) taxonomy_vocabulary_load($vid);
Dries's avatar
   
Dries committed
205

Dries's avatar
   
Dries committed
206
  db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
Dries's avatar
   
Dries committed
207
  db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
Dries's avatar
   
Dries committed
208
  $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
Kjartan's avatar
Kjartan committed
209
210
  while ($term = db_fetch_object($result)) {
    taxonomy_del_term($term->tid);
Dries's avatar
   
Dries committed
211
  }
Dries's avatar
   
Dries committed
212

Dries's avatar
   
Dries committed
213
  module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
Dries's avatar
   
Dries committed
214

Dries's avatar
   
Dries committed
215
216
  cache_clear_all();

217
  return SAVED_DELETED;
Dries's avatar
   
Dries committed
218
219
}

220
221
222
/**
 * Helper function for taxonomy_form_term_submit().
 *
223
 * @param $form_state['values']
224
225
226
227
 * @return
 *   Status constant indicating if term was inserted or updated.
 */
function taxonomy_save_term(&$form_values) {
228
229
230
231
232
  $form_values += array(
    'description' => '',
    'weight' => 0
  );

233
  if (!empty($form_values['tid']) && $form_values['name']) {
234
    db_query("UPDATE {term_data} SET name = '%s', description = '%s', weight = %d WHERE tid = %d", $form_values['name'], $form_values['description'], $form_values['weight'], $form_values['tid']);
235
    $hook = 'update';
236
    $status = SAVED_UPDATED;
Kjartan's avatar
Kjartan committed
237
  }
238
  else if (!empty($form_values['tid'])) {
239
    return taxonomy_del_term($form_values['tid']);
Kjartan's avatar
Kjartan committed
240
241
  }
  else {
242
243
    db_query("INSERT INTO {term_data} (name, description, vid, weight) VALUES ('%s', '%s', %d, %d)", $form_values['name'], $form_values['description'], $form_values['vid'], $form_values['weight']);
    $form_values['tid'] = db_last_insert_id('term_data', 'tid');
244
    $hook = 'insert';
245
    $status = SAVED_NEW;
Kjartan's avatar
Kjartan committed
246
  }
Dries's avatar
   
Dries committed
247

248
  db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $form_values['tid'], $form_values['tid']);
249
  if (!empty($form_values['relations'])) {
250
    foreach ($form_values['relations'] as $related_id) {
Kjartan's avatar
Kjartan committed
251
      if ($related_id != 0) {
252
        db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $form_values['tid'], $related_id);
Dries's avatar
   
Dries committed
253
      }
Kjartan's avatar
Kjartan committed
254
    }
Kjartan's avatar
Kjartan committed
255
  }
Dries's avatar
   
Dries committed
256

257
258
259
  db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $form_values['tid']);
  if (!isset($form_values['parent']) || empty($form_values['parent'])) {
    $form_values['parent'] = array(0);
Kjartan's avatar
Kjartan committed
260
  }
261
262
  if (is_array($form_values['parent'])) {
    foreach ($form_values['parent'] as $parent) {
263
264
      if (is_array($parent)) {
        foreach ($parent as $tid) {
265
          db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $tid);
266
267
268
        }
      }
      else {
269
        db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $parent);
270
      }
Dries's avatar
   
Dries committed
271
    }
Kjartan's avatar
Kjartan committed
272
  }
273
  else {
274
    db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $form_values['parent']);
275
  }
Dries's avatar
   
Dries committed
276

277
  db_query('DELETE FROM {term_synonym} WHERE tid = %d', $form_values['tid']);
278
  if (!empty($form_values['synonyms'])) {
279
    foreach (explode ("\n", str_replace("\r", '', $form_values['synonyms'])) as $synonym) {
Dries's avatar
   
Dries committed
280
      if ($synonym) {
281
        db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $form_values['tid'], chop($synonym));
Dries's avatar
   
Dries committed
282
      }
Kjartan's avatar
Kjartan committed
283
    }
Dries's avatar
   
Dries committed
284
  }
Dries's avatar
   
Dries committed
285

286
  if (isset($hook)) {
287
    module_invoke_all('taxonomy', $hook, 'term', $form_values);
288
289
  }

Dries's avatar
   
Dries committed
290
291
  cache_clear_all();

292
  return $status;
Kjartan's avatar
Kjartan committed
293
}
Dries's avatar
   
Dries committed
294

295
296
297
298
299
300
301
302
/**
 * Delete a term.
 *
 * @param $tid
 *   The term ID.
 * @return
 *   Status constant indicating deletion.
 */
Kjartan's avatar
Kjartan committed
303
function taxonomy_del_term($tid) {
304
305
306
307
308
309
310
311
312
313
314
315
316
317
  $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
318

319
      $term = (array) taxonomy_get_term($tid);
Dries's avatar
   
Dries committed
320

321
322
323
324
325
      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
326

327
328
      module_invoke_all('taxonomy', 'delete', 'term', $term);
    }
Dries's avatar
   
Dries committed
329

330
331
    $tids = $orphans;
  }
Dries's avatar
   
Dries committed
332

Dries's avatar
   
Dries committed
333
  cache_clear_all();
334
335

  return SAVED_DELETED;
Dries's avatar
   
Dries committed
336
337
}

Dries's avatar
   
Dries committed
338
339
340
/**
 * Generate a form element for selecting terms from a vocabulary.
 */
341
function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') {
342
  $vocabulary = taxonomy_vocabulary_load($vid);
343
  $help = ($help) ? $help : $vocabulary->help;
Kjartan's avatar
Kjartan committed
344
345
346
347
  if ($vocabulary->required) {
    $blank = 0;
  }
  else {
Dries's avatar
   
Dries committed
348
    $blank = '<'. t('none') .'>';
Kjartan's avatar
Kjartan committed
349
  }
Dries's avatar
   
Dries committed
350

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

354
/**
355
 * Generate a set of options for selecting a term from all vocabularies.
356
357
358
359
360
361
362
 */
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);
363
    if ($tree && (count($tree) > 0)) {
364
      $options[$vocabulary->name] = array();
365
      foreach ($tree as $term) {
366
        $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name;
367
368
369
370
371
372
      }
    }
  }
  return $options;
}

Dries's avatar
   
Dries committed
373
374
375
376
377
378
/**
 * 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
379
function taxonomy_get_vocabularies($type = NULL) {
Kjartan's avatar
Kjartan committed
380
  if ($type) {
381
    $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
382
383
  }
  else {
384
    $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
385
  }
Dries's avatar
   
Dries committed
386

Kjartan's avatar
Kjartan committed
387
  $vocabularies = array();
Dries's avatar
   
Dries committed
388
  $node_types = array();
Kjartan's avatar
Kjartan committed
389
  while ($voc = db_fetch_object($result)) {
390
391
392
393
394
395
396
397
398
399
    // If no node types are associated with a vocabulary, the LEFT JOIN will
    // return a NULL value for type.
    if (isset($voc->type)) {
      $node_types[$voc->vid][] = $voc->type;
      unset($voc->type);
      $voc->nodes = $node_types[$voc->vid];
    }
    elseif (!isset($voc->nodes)) {
      $voc->nodes = array();
    }
Dries's avatar
   
Dries committed
400
    $vocabularies[$voc->vid] = $voc;
Dries's avatar
   
Dries committed
401
  }
Dries's avatar
   
Dries committed
402

Kjartan's avatar
Kjartan committed
403
404
  return $vocabularies;
}
Dries's avatar
   
Dries committed
405

Dries's avatar
   
Dries committed
406
/**
407
 * Implementation of hook_form_alter().
Dries's avatar
   
Dries committed
408
409
 * Generate a form for selecting terms to associate with a node.
 */
410
function taxonomy_form_alter(&$form, $form_state, $form_id) {
411
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
412
413
    $node = $form['#node'];

414
    if (!isset($node->taxonomy)) {
415
      $terms = empty($node->nid) ? array() : taxonomy_node_get_terms($node);
Kjartan's avatar
Kjartan committed
416
417
    }
    else {
418
      $terms = $node->taxonomy;
Dries's avatar
   
Dries committed
419
420
    }

421
    $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
422

423
424
    while ($vocabulary = db_fetch_object($c)) {
      if ($vocabulary->tags) {
425
        $typed_string = taxonomy_implode_tags($terms, $vocabulary->vid) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
426

427
428
429
430
        if ($vocabulary->help) {
          $help = $vocabulary->help;
        }
        else {
431
          $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
432
        }
433
434
435
436
437
438
439
        $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,
440
          '#maxlength' => 255,
441
        );
442
443
      }
      else {
444
445
446
447
448
449
450
451
        // 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);
452
        $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
453
        $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
454
      }
Dries's avatar
Dries committed
455
    }
456
    if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
457
458
459
460
461
462
463
464
      if (count($form['taxonomy']) > 1) { // Add fieldset only if form has more than 1 element.
        $form['taxonomy'] += array(
          '#type' => 'fieldset',
          '#title' => t('Categories'),
          '#collapsible' => TRUE,
          '#collapsed' => FALSE,
        );
      }
465
466
      $form['taxonomy']['#weight'] = -3;
      $form['taxonomy']['#tree'] = TRUE;
Dries's avatar
Dries committed
467
    }
Dries's avatar
   
Dries committed
468
  }
Kjartan's avatar
Kjartan committed
469
}
Dries's avatar
   
Dries committed
470

Dries's avatar
   
Dries committed
471
/**
472
 * Find all terms associated with the given node, within one vocabulary.
Dries's avatar
   
Dries committed
473
 */
474
475
function taxonomy_node_get_terms_by_vocabulary($node, $vid, $key = 'tid') {
  $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.vid = %d ORDER BY weight', 't', 'tid'), $vid, $node->vid);
Kjartan's avatar
Kjartan committed
476
477
478
  $terms = array();
  while ($term = db_fetch_object($result)) {
    $terms[$term->$key] = $term;
Dries's avatar
   
Dries committed
479
  }
Kjartan's avatar
Kjartan committed
480
481
482
  return $terms;
}

Dries's avatar
   
Dries committed
483
/**
484
 * Find all terms associated with the given node, ordered by vocabulary and term weight.
Dries's avatar
   
Dries committed
485
 */
486
function taxonomy_node_get_terms($node, $key = 'tid') {
Kjartan's avatar
Kjartan committed
487
  static $terms;
Dries's avatar
   
Dries committed
488

489
  if (!isset($terms[$node->vid][$key])) {
490
    $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.vid = %d ORDER BY v.weight, t.weight, t.name', 't', 'tid'), $node->vid);
491
    $terms[$node->vid][$key] = array();
Dries's avatar
   
Dries committed
492
    while ($term = db_fetch_object($result)) {
493
      $terms[$node->vid][$key][$term->$key] = $term;
Dries's avatar
   
Dries committed
494
495
    }
  }
496
  return $terms[$node->vid][$key];
Kjartan's avatar
Kjartan committed
497
}
Dries's avatar
   
Dries committed
498

Dries's avatar
Dries committed
499
500
501
/**
 * Make sure incoming vids are free tagging enabled.
 */
502
function taxonomy_node_validate(&$node) {
503
  if (!empty($node->taxonomy)) {
504
    $terms = $node->taxonomy;
505
    if (!empty($terms['tags'])) {
Dries's avatar
Dries committed
506
      foreach ($terms['tags'] as $vid => $vid_value) {
507
        $vocabulary = taxonomy_vocabulary_load($vid);
508
        if (empty($vocabulary->tags)) {
509
510
          // see form_get_error $key = implode('][', $element['#parents']);
          // on why this is the key
511
          form_set_error("taxonomy][tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => $vocabulary->name)));
Dries's avatar
Dries committed
512
513
514
515
516
517
        }
      }
    }
  }
}

Dries's avatar
   
Dries committed
518
519
520
/**
 * Save term associations for a given node.
 */
521
522
523
function taxonomy_node_save($node, $terms) {

  taxonomy_node_delete_revision($node);
Dries's avatar
Dries committed
524
525
526

  // Free tagging vocabularies do not send their tids in the form,
  // so we'll detect them here and process them independently.
527
528
529
  if (isset($terms['tags'])) {
    $typed_input = $terms['tags'];
    unset($terms['tags']);
Dries's avatar
Dries committed
530
531

    foreach ($typed_input as $vid => $vid_value) {
532
      $typed_terms = drupal_explode_tags($vid_value);
Dries's avatar
Dries committed
533

534
      $inserted = array();
Dries's avatar
Dries committed
535
536
      foreach ($typed_terms as $typed_term) {
        // See if the term exists in the chosen vocabulary
537
        // and return the tid; otherwise, add a new record.
Dries's avatar
Dries committed
538
        $possibilities = taxonomy_get_term_by_name($typed_term);
539
        $typed_term_tid = NULL; // tid match, if any.
Dries's avatar
Dries committed
540
541
542
543
544
545
546
        foreach ($possibilities as $possibility) {
          if ($possibility->vid == $vid) {
            $typed_term_tid = $possibility->tid;
          }
        }

        if (!$typed_term_tid) {
547
548
549
          $edit = array('vid' => $vid, 'name' => $typed_term);
          $status = taxonomy_save_term($edit);
          $typed_term_tid = $edit['tid'];
Dries's avatar
Dries committed
550
551
        }

552
        // Defend against duplicate, differently cased tags
553
        if (!isset($inserted[$typed_term_tid])) {
554
          db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $typed_term_tid);
555
556
          $inserted[$typed_term_tid] = TRUE;
        }
Dries's avatar
Dries committed
557
558
559
      }
    }
  }
Dries's avatar
   
Dries committed
560

561
562
  if (is_array($terms)) {
    foreach ($terms as $term) {
563
564
565
      if (is_array($term)) {
        foreach ($term as $tid) {
          if ($tid) {
566
            db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $tid);
567
568
569
          }
        }
      }
570
      else if (is_object($term)) {
571
        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $term->tid);
572
      }
573
      else if ($term) {
574
        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $term);
575
      }
Dries's avatar
   
Dries committed
576
577
    }
  }
Kjartan's avatar
Kjartan committed
578
}
Dries's avatar
   
Dries committed
579

Dries's avatar
   
Dries committed
580
581
582
/**
 * Remove associations of a node to its terms.
 */
583
function taxonomy_node_delete($node) {
584
  db_query('DELETE FROM {term_node} WHERE nid = %d', $node->nid);
585
586
587
588
589
590
}

/**
 * Remove associations of a node to its terms.
 */
function taxonomy_node_delete_revision($node) {
591
  db_query('DELETE FROM {term_node} WHERE vid = %d', $node->vid);
Kjartan's avatar
Kjartan committed
592
}
Dries's avatar
   
Dries committed
593

594
595
596
597
598
/**
 * Implementation of hook_node_type().
 */
function taxonomy_node_type($op, $info) {
  if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) {
599
600
601
602
    db_query("UPDATE {vocabulary_node_types} SET type = '%s' WHERE type = '%s'", $info->type, $info->old_type);
  }
  elseif ($op == 'delete') {
    db_query("DELETE FROM {vocabulary_node_types} WHERE type = '%s'", $info->type);
603
604
605
  }
}

Dries's avatar
   
Dries committed
606
607
608
609
/**
 * Find all term objects related to a given term ID.
 */
function taxonomy_get_related($tid, $key = 'tid') {
Kjartan's avatar
Kjartan committed
610
  if ($tid) {
Dries's avatar
   
Dries committed
611
    $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
612
613
614
    $related = array();
    while ($term = db_fetch_object($result)) {
      $related[$term->$key] = $term;
Dries's avatar
   
Dries committed
615
    }
Kjartan's avatar
Kjartan committed
616
    return $related;
Dries's avatar
   
Dries committed
617
  }
Kjartan's avatar
Kjartan committed
618
619
  else {
    return array();
Dries's avatar
   
Dries committed
620
  }
Kjartan's avatar
Kjartan committed
621
}
Dries's avatar
   
Dries committed
622

Dries's avatar
   
Dries committed
623
624
625
626
/**
 * Find all parents of a given term ID.
 */
function taxonomy_get_parents($tid, $key = 'tid') {
Kjartan's avatar
Kjartan committed
627
  if ($tid) {
628
    $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
629
630
631
    $parents = array();
    while ($parent = db_fetch_object($result)) {
      $parents[$parent->$key] = $parent;
Dries's avatar
   
Dries committed
632
    }
Kjartan's avatar
Kjartan committed
633
    return $parents;
Dries's avatar
   
Dries committed
634
  }
Kjartan's avatar
Kjartan committed
635
636
637
638
  else {
    return array();
  }
}
Dries's avatar
   
Dries committed
639

Dries's avatar
   
Dries committed
640
641
642
643
/**
 * Find all ancestors of a given term ID.
 */
function taxonomy_get_parents_all($tid) {
Dries's avatar
   
Dries committed
644
645
646
647
648
649
650
651
652
653
654
655
  $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
656
657
658
659
/**
 * Find all children of a term ID.
 */
function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
Kjartan's avatar
Kjartan committed
660
  if ($vid) {
661
    $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
662
  }
Kjartan's avatar
Kjartan committed
663
  else {
664
    $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
665
666
667
668
669
670
671
  }
  $children = array();
  while ($term = db_fetch_object($result)) {
    $children[$term->$key] = $term;
  }
  return $children;
}
Dries's avatar
   
Dries committed
672

Dries's avatar
   
Dries committed
673
674
675
676
677
678
679
680
681
682
683
684
685
/**
 * 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
686
687
688
 * @param $max_depth
 *   The number of levels of the tree to return. Leave NULL to return all levels.
 *
Dries's avatar
   
Dries committed
689
690
691
 * @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.
692
 *   Results are statically cached.
Dries's avatar
   
Dries committed
693
694
 */
function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
Dries's avatar
   
Dries committed
695
  static $children, $parents, $terms;
Dries's avatar
   
Dries committed
696

Kjartan's avatar
Kjartan committed
697
  $depth++;
Dries's avatar
   
Dries committed
698

Dries's avatar
   
Dries committed
699
700
701
702
  // 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
703

704
    $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
705
    while ($term = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
706
707
708
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
Dries's avatar
   
Dries committed
709
710
    }
  }
Dries's avatar
   
Dries committed
711

Dries's avatar
   
Dries committed
712
  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
713
714
  $tree = array();
  if (!empty($children[$vid][$parent])) {
Dries's avatar
Dries committed
715
716
    foreach ($children[$vid][$parent] as $child) {
      if ($max_depth > $depth) {
717
718
        $term = drupal_clone($terms[$vid][$child]);
        $term->depth = $depth;
Dries's avatar
Dries committed
719
        // The "parent" attribute is not useful, as it would show one parent only.
720
721
722
        unset($term->parent);
        $term->parents = $parents[$vid][$child];
        $tree[] = $term;
Dries's avatar
Dries committed
723

724
        if (!empty($children[$vid][$child])) {
Dries's avatar
Dries committed
725
726
          $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $depth, $max_depth));
        }
Dries's avatar
   
Dries committed
727
      }
Dries's avatar
   
Dries committed
728
    }
Kjartan's avatar
Kjartan committed
729
  }
Dries's avatar
   
Dries committed
730

731
  return $tree;
Kjartan's avatar
Kjartan committed
732
}
Dries's avatar
   
Dries committed
733

Dries's avatar
   
Dries committed
734
735
736
/**
 * Return an array of synonyms of the given term ID.
 */
Kjartan's avatar
Kjartan committed
737
738
function taxonomy_get_synonyms($tid) {
  if ($tid) {
739
    $synonyms = array();
Dries's avatar
   
Dries committed
740
    $result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d', $tid);
Kjartan's avatar
Kjartan committed
741
    while ($synonym = db_fetch_array($result)) {
Dries's avatar
   
Dries committed
742
      $synonyms[] = $synonym['name'];
Dries's avatar
   
Dries committed
743
    }
744
    return $synonyms;
Dries's avatar
   
Dries committed
745
  }
Kjartan's avatar
Kjartan committed
746
747
  else {
    return array();
Dries's avatar
   
Dries committed
748
  }
Kjartan's avatar
Kjartan committed
749
}
Dries's avatar
   
Dries committed
750

Dries's avatar
   
Dries committed
751
752
753
754
755
/**
 * 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
756
}
Dries's avatar
   
Dries committed
757

Dries's avatar
   
Dries committed
758
/**
759
760
761
762
763
764
765
766
767
768
769
770
 * Count the number of published nodes classified by a term.
 *
 * @param $tid
 *   The term's ID
 *
 * @param $type
 *   The $node->type. If given, taxonomy_term_count_nodes only counts
 *   nodes of $type that are classified with the term $tid.
 *
 * @return int
 *   An integer representing a number of nodes.
 *   Results are statically cached.
Dries's avatar
   
Dries committed
771
 */
Dries's avatar
   
Dries committed
772
function taxonomy_term_count_nodes($tid, $type = 0) {
Kjartan's avatar
Kjartan committed
773
  static $count;
Dries's avatar
   
Dries committed
774

Dries's avatar
   
Dries committed
775
  if (!isset($count[$type])) {
776
    // $type == 0 always evaluates TRUE if $type is a string
Dries's avatar
   
Dries committed
777
    if (is_numeric($type)) {
778
      $result = db_query(db_rewrite_sql('SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.status = 1 GROUP BY t.tid'));
Dries's avatar
   
Dries committed
779
780
    }
    else {
781
      $result = db_query(db_rewrite_sql("SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.status = 1 AND n.type = '%s' GROUP BY t.tid"), $type);
Dries's avatar
   
Dries committed
782
    }
Kjartan's avatar
Kjartan committed
783
    while ($term = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
784
      $count[$type][$term->tid] = $term->c;
Dries's avatar
   
Dries committed
785
786
    }
  }
787
  $children_count = 0;
Kjartan's avatar
Kjartan committed
788
  foreach (_taxonomy_term_children($tid) as $c) {
Dries's avatar
   
Dries committed
789
    $children_count += taxonomy_term_count_nodes($c, $type);
Kjartan's avatar
Kjartan committed
790
  }
Dries's avatar
   
Dries committed
791
  return $count[$type][$tid] + $children_count;
Kjartan's avatar
Kjartan committed
792
793
}

Dries's avatar
   
Dries committed
794
/**
795
796
797
798
799
800
801
802
803
804
 * Helper for taxonomy_term_count_nodes(). Used to find out
 * which terms are children of a parent term.
 *
 * @param $tid
 *   The parent term's ID
 *
 * @return array
 *   An array of term IDs representing the children of $tid.
 *   Results are statically cached.
 *
Dries's avatar
   
Dries committed
805
 */
Kjartan's avatar
Kjartan committed
806
807
function _taxonomy_term_children($tid) {
  static $children;
Dries's avatar
   
Dries committed
808

Dries's avatar
   
Dries committed
809
  if (!isset($children)) {
Dries's avatar
   
Dries committed
810
    $result = db_query('SELECT tid, parent FROM {term_hierarchy}');
Kjartan's avatar
Kjartan committed
811
812
    while ($term = db_fetch_object($result)) {
      $children[$term->parent][] = $term->tid;
Dries's avatar
   
Dries committed
813
    }
Dries's avatar
   
Dries committed
814
  }
815
  return isset($children[$tid]) ? $children[$tid] : array();
Kjartan's avatar
Kjartan committed
816
}
Dries's avatar
   
Dries committed
817

Dries's avatar
   
Dries committed
818
/**
Dries's avatar
   
Dries committed
819
 * Try to map a string to an existing term, as for glossary use.
Dries's avatar
   
Dries committed
820
 *
Dries's avatar
   
Dries committed
821
822
823
824
825
826
827
828
 * Provides a case-insensitive and trimmed mapping, to maximize the
 * likelihood of a successful match.
 *
 * @param name
 *   Name of the term to search for.
 *
 * @return
 *   An array of matching term objects.
Dries's avatar
   
Dries committed
829
830
 */
function taxonomy_get_term_by_name($name) {
831
  $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t WHERE LOWER(t.name) LIKE LOWER('%s')", 't', 'tid'), trim($name));
Dries's avatar
   
Dries committed
832
833
834
835
836
837
838
839
  $result = array();
  while ($term = db_fetch_object($db_result)) {
    $result[] = $term;
  }

  return $result;
}

Dries's avatar
   
Dries committed
840
841
/**
 * Return the vocabulary object matching a vocabulary ID.
842
843
844
845
846
847
848
 *
 * @param $vid
 *   The vocabulary's ID
 *
 * @return Object
 *   The vocabulary object with all of its metadata.
 *   Results are statically cached.
Dries's avatar
   
Dries committed
849
 */
850
function taxonomy_vocabulary_load($vid) {
851
852
853
  static $vocabularies = array();

  if (!array_key_exists($vid, $vocabularies)) {
854
    $result = db_query('SELECT v.*, n.type FROM {vocabulary} v LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE v.vid = %d', $vid);
855
856
    $node_types = array();
    while ($voc = db_fetch_object($result)) {
857
858
859
      if (!empty($voc->type)) {
        $node_types[] = $voc->type;
      }
860
861
862
863
      unset($voc->type);
      $voc->nodes = $node_types;
      $vocabularies[$vid] = $voc;
    }
Dries's avatar
   
Dries committed
864
865
  }

866
  return $vocabularies[$vid];
Kjartan's avatar
Kjartan committed
867
}
Dries's avatar
   
Dries committed
868

Dries's avatar
   
Dries committed
869
870
/**
 * Return the term object matching a term ID.
871
872
873
874
875
876
 *
 * @param $tid
 *   A term's ID
 *
 * @return Object
 *   A term object. Results are statically cached.
Dries's avatar
   
Dries committed
877
 */
Kjartan's avatar
Kjartan committed
878
function taxonomy_get_term($tid) {
879
880
881
882
883
884
885
  static $terms = array();

  if (!isset($terms[$tid])) {
    $terms[$tid] = db_fetch_object(db_query('SELECT * FROM {term_data} WHERE tid = %d', $tid));
  }

  return $terms[$tid];
Kjartan's avatar
Kjartan committed
886
}
Dries's avatar
   
Dries committed
887

Kjartan's avatar
Kjartan committed
888
function _taxonomy_term_select($title, $name, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) {
Dries's avatar
   
Dries committed
889
  $tree = taxonomy_get_tree($vocabulary_id);
890
891
  $options = array();

Dries's avatar
Dries committed
892
  if ($blank) {
893
    $options[0] = $blank;
Dries's avatar
Dries committed
894
  }
Kjartan's avatar
Kjartan committed
895
896
897
  if ($tree) {
    foreach ($tree as $term) {
      if (!in_array($term->tid, $exclude)) {
898
899
900
        $choice = new stdClass();
        $choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
        $options[]