taxonomy.module 39.8 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
/**
 * Implementation of hook_theme()
 */
function taxonomy_theme() {
  return array(
    'taxonomy_term_select' => array(
      'arguments' => array('element' => NULL),
23 24 25
    ),
    'taxonomy_term_page' => array(
      'arguments' => array('tids' => array(), 'result' => NULL),
26 27 28 29
    ),
  );
}

Dries's avatar
 
Dries committed
30 31 32 33 34 35 36
/**
 * 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:
 *
37
 * if (module_exists('taxonomy')) {
38 39
 *   $terms = taxonomy_link('taxonomy terms', $node);
 *   print theme('links', $terms);
Dries's avatar
 
Dries committed
40 41
 * }
 */
Dries's avatar
 
Dries committed
42
function taxonomy_link($type, $node = NULL) {
Dries's avatar
 
Dries committed
43
  if ($type == 'taxonomy terms' && $node != NULL) {
Kjartan's avatar
 
Kjartan committed
44
    $links = array();
45 46 47 48
    // If previewing, the terms must be converted to objects first.
    if ($node->build_mode == NODE_BUILD_PREVIEW) {
      $node->taxonomy = taxonomy_preview_terms($node);
    }
49
    if (!empty($node->taxonomy)) {
50
      foreach ($node->taxonomy as $term) {
51 52 53
        // During preview the free tagging terms are in an array unlike the
        // other terms which are objects. So we have to check if a $term 
        // is an object or not.
54 55 56 57 58 59 60
        if (is_object($term)) {
          $links['taxonomy_term_'. $term->tid] = array(
            'title' => $term->name,
            'href' => taxonomy_term_path($term),
            'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
          );
        }
61 62
        // Previewing free tagging terms; we don't link them because the
        // term-page might not exist yet.
63 64 65 66 67 68 69 70 71
        else {
          foreach ($term as $free_typed) {
            $typed_terms = drupal_explode_tags($free_typed);
            foreach($typed_terms as $typed_term) {
              $links['taxonomy_preview_term_'. $typed_term] = array(
                'title' => $typed_term,
              );           
            }
          }
72
        }
Dries's avatar
 
Dries committed
73
      }
Dries's avatar
 
Dries committed
74
    }
Kjartan's avatar
Kjartan committed
75

76 77
    // We call this hook again because some modules and themes
    // call taxonomy_link('taxonomy terms') directly.
78
    drupal_alter('link', $links, $node);
79 80

    return $links;
81 82 83
  }
}

84 85 86 87 88 89 90 91 92 93
/**
 * 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.
 */

94
function taxonomy_term_path($term) {
95
  $vocabulary = taxonomy_vocabulary_load($term->vid);
96 97 98 99 100 101
  if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
    return $path;
  }
  return 'taxonomy/term/'. $term->tid;
}

Dries's avatar
 
Dries committed
102 103 104
/**
 * Implementation of hook_menu().
 */
105 106
function taxonomy_menu() {
  $items['admin/content/taxonomy'] = array(
107 108
    'title' => 'Categories',
    'description' => 'Create vocabularies and terms to categorize your content.',
109 110
    'page callback' => 'taxonomy_overview_vocabularies',
    'access arguments' => array('administer taxonomy'),
111
    'file' => 'taxonomy.admin.inc',
112 113 114
  );

  $items['admin/content/taxonomy/list'] = array(
115
    'title' => 'List',
116 117 118 119 120
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

  $items['admin/content/taxonomy/add/vocabulary'] = array(
121
    'title' => 'Add vocabulary',
122 123 124
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_form_vocabulary'),
    'type' => MENU_LOCAL_TASK,
125
    'parent' => 'admin/content/taxonomy',
126
    'file' => 'taxonomy.admin.inc',
127 128
  );

129
  $items['admin/content/taxonomy/edit/vocabulary/%taxonomy_vocabulary'] = array(
130
    'title' => 'Edit vocabulary',
131 132 133
    'page callback' => 'taxonomy_admin_vocabulary_edit',
    'page arguments' => array(5),
    'type' => MENU_CALLBACK,
134
    'file' => 'taxonomy.admin.inc',
135 136 137
  );

  $items['admin/content/taxonomy/edit/term'] = array(
138
    'title' => 'Edit term',
139 140
    'page callback' => 'taxonomy_admin_term_edit',
    'type' => MENU_CALLBACK,
141
    'file' => 'taxonomy.admin.inc',
142 143 144
  );

  $items['taxonomy/term'] = array(
145
    'title' => 'Taxonomy term',
146 147 148
    'page callback' => 'taxonomy_term_page',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
149
    'file' => 'taxonomy.pages.inc',
150 151 152
  );

  $items['taxonomy/autocomplete'] = array(
153
    'title' => 'Autocomplete taxonomy',
154 155 156
    'page callback' => 'taxonomy_autocomplete',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
157
    'file' => 'taxonomy.pages.inc',
158
  );
159
  $items['admin/content/taxonomy/%taxonomy_vocabulary'] = array(
160
    'title' => 'List terms',
161 162 163 164
    'page callback' => 'taxonomy_overview_terms',
    'page arguments' => array(3),
    'access arguments' => array('administer taxonomy'),
    'type' => MENU_CALLBACK,
165
    'file' => 'taxonomy.admin.inc',
166 167
  );

168
  $items['admin/content/taxonomy/%taxonomy_vocabulary/list'] = array(
169
    'title' => 'List',
170 171 172 173
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

174
  $items['admin/content/taxonomy/%taxonomy_vocabulary/add/term'] = array(
175
    'title' => 'Add term',
176 177
    'page callback' => 'taxonomy_add_term_page',
    'page arguments' => array(3),
178
    'type' => MENU_LOCAL_TASK,
179
    'parent' => 'admin/content/taxonomy/%taxonomy_vocabulary',
180
    'file' => 'taxonomy.admin.inc',
181
  );
Dries's avatar
 
Dries committed
182

Dries's avatar
 
Dries committed
183 184
  return $items;
}
Dries's avatar
 
Dries committed
185

186
function taxonomy_save_vocabulary(&$edit) {
187
  $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
Dries's avatar
 
Dries committed
188

189 190 191 192
  if (!isset($edit['module'])) {
    $edit['module'] = 'taxonomy';
  }

193
  if (!empty($edit['vid']) && !empty($edit['name'])) {
194
    drupal_write_record('vocabulary', $edit, 'vid');
Dries's avatar
 
Dries committed
195
    db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d", $edit['vid']);
196
    foreach ($edit['nodes'] as $type => $selected) {
Dries's avatar
 
Dries committed
197 198
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
    }
Dries's avatar
 
Dries committed
199
    module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
200
    $status = SAVED_UPDATED;
Dries's avatar
 
Dries committed
201
  }
202
  else if (!empty($edit['vid'])) {
203
    $status = taxonomy_del_vocabulary($edit['vid']);
Kjartan's avatar
Kjartan committed
204 205
  }
  else {
206
    drupal_write_record('vocabulary', $edit);
207
    foreach ($edit['nodes'] as $type => $selected) {
Dries's avatar
 
Dries committed
208 209
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
    }
Dries's avatar
 
Dries committed
210
    module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
211
    $status = SAVED_NEW;
Kjartan's avatar
Kjartan committed
212
  }
Dries's avatar
 
Dries committed
213 214

  cache_clear_all();
Dries's avatar
 
Dries committed
215

216
  return $status;
Kjartan's avatar
Kjartan committed
217
}
Dries's avatar
 
Dries committed
218

219 220 221 222 223 224 225 226
/**
 * Delete a vocabulary.
 *
 * @param $vid
 *   A vocabulary ID.
 * @return
 *   Constant indicating items were deleted.
 */
Kjartan's avatar
Kjartan committed
227
function taxonomy_del_vocabulary($vid) {
228
  $vocabulary = (array) taxonomy_vocabulary_load($vid);
Dries's avatar
 
Dries committed
229

Dries's avatar
 
Dries committed
230
  db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
Dries's avatar
 
Dries committed
231
  db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
Dries's avatar
 
Dries committed
232
  $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
Kjartan's avatar
Kjartan committed
233 234
  while ($term = db_fetch_object($result)) {
    taxonomy_del_term($term->tid);
Dries's avatar
 
Dries committed
235
  }
Dries's avatar
 
Dries committed
236

Dries's avatar
 
Dries committed
237
  module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
Dries's avatar
 
Dries committed
238

Dries's avatar
 
Dries committed
239 240
  cache_clear_all();

241
  return SAVED_DELETED;
Dries's avatar
 
Dries committed
242 243
}

244 245 246
/**
 * Helper function for taxonomy_form_term_submit().
 *
247
 * @param $form_state['values']
248 249 250 251
 * @return
 *   Status constant indicating if term was inserted or updated.
 */
function taxonomy_save_term(&$form_values) {
252 253 254 255 256
  $form_values += array(
    'description' => '',
    'weight' => 0
  );

257
  if (!empty($form_values['tid']) && $form_values['name']) {
258
    drupal_write_record('term_data', $form_values, 'tid');
259
    $hook = 'update';
260
    $status = SAVED_UPDATED;
Kjartan's avatar
Kjartan committed
261
  }
262
  else if (!empty($form_values['tid'])) {
263
    return taxonomy_del_term($form_values['tid']);
Kjartan's avatar
Kjartan committed
264 265
  }
  else {
266
    drupal_write_record('term_data', $form_values);
267
    $hook = 'insert';
268
    $status = SAVED_NEW;
Kjartan's avatar
Kjartan committed
269
  }
Dries's avatar
 
Dries committed
270

271
  db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $form_values['tid'], $form_values['tid']);
272
  if (!empty($form_values['relations'])) {
273
    foreach ($form_values['relations'] as $related_id) {
Kjartan's avatar
Kjartan committed
274
      if ($related_id != 0) {
275
        db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $form_values['tid'], $related_id);
Dries's avatar
 
Dries committed
276
      }
Kjartan's avatar
Kjartan committed
277
    }
Kjartan's avatar
Kjartan committed
278
  }
Dries's avatar
 
Dries committed
279

280 281 282
  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
283
  }
284 285
  if (is_array($form_values['parent'])) {
    foreach ($form_values['parent'] as $parent) {
286 287
      if (is_array($parent)) {
        foreach ($parent as $tid) {
288
          db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $tid);
289 290 291
        }
      }
      else {
292
        db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $parent);
293
      }
Dries's avatar
 
Dries committed
294
    }
Kjartan's avatar
Kjartan committed
295
  }
296
  else {
297
    db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $form_values['parent']);
298
  }
Dries's avatar
 
Dries committed
299

300
  db_query('DELETE FROM {term_synonym} WHERE tid = %d', $form_values['tid']);
301
  if (!empty($form_values['synonyms'])) {
302
    foreach (explode ("\n", str_replace("\r", '', $form_values['synonyms'])) as $synonym) {
Dries's avatar
 
Dries committed
303
      if ($synonym) {
304
        db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $form_values['tid'], chop($synonym));
Dries's avatar
 
Dries committed
305
      }
Kjartan's avatar
Kjartan committed
306
    }
Dries's avatar
 
Dries committed
307
  }
Dries's avatar
 
Dries committed
308

309
  if (isset($hook)) {
310
    module_invoke_all('taxonomy', $hook, 'term', $form_values);
311 312
  }

Dries's avatar
 
Dries committed
313 314
  cache_clear_all();

315
  return $status;
Kjartan's avatar
Kjartan committed
316
}
Dries's avatar
 
Dries committed
317

318 319 320 321 322 323 324 325
/**
 * Delete a term.
 *
 * @param $tid
 *   The term ID.
 * @return
 *   Status constant indicating deletion.
 */
Kjartan's avatar
Kjartan committed
326
function taxonomy_del_term($tid) {
327 328 329 330 331 332 333 334 335 336 337 338 339 340
  $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
341

342
      $term = (array) taxonomy_get_term($tid);
Dries's avatar
 
Dries committed
343

344 345 346 347 348
      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
349

350 351
      module_invoke_all('taxonomy', 'delete', 'term', $term);
    }
Dries's avatar
 
Dries committed
352

353 354
    $tids = $orphans;
  }
Dries's avatar
 
Dries committed
355

Dries's avatar
 
Dries committed
356
  cache_clear_all();
357 358

  return SAVED_DELETED;
Dries's avatar
 
Dries committed
359 360
}

Dries's avatar
 
Dries committed
361 362 363
/**
 * Generate a form element for selecting terms from a vocabulary.
 */
364
function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') {
365
  $vocabulary = taxonomy_vocabulary_load($vid);
366
  $help = ($help) ? $help : $vocabulary->help;
367
  $blank = 0;
368

369 370
  if (!$vocabulary->multiple) {
    $blank = ($vocabulary->required) ? t('- Please choose -') : t('- None selected -');
Kjartan's avatar
Kjartan committed
371
  }
Dries's avatar
 
Dries committed
372

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

376
/**
377
 * Generate a set of options for selecting a term from all vocabularies.
378 379 380 381 382 383 384
 */
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);
385
    if ($tree && (count($tree) > 0)) {
386
      $options[$vocabulary->name] = array();
387
      foreach ($tree as $term) {
388
        $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name;
389 390 391 392 393 394
      }
    }
  }
  return $options;
}

Dries's avatar
 
Dries committed
395 396 397 398 399 400
/**
 * 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
401
function taxonomy_get_vocabularies($type = NULL) {
Kjartan's avatar
Kjartan committed
402
  if ($type) {
403
    $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
404 405
  }
  else {
406
    $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
407
  }
Dries's avatar
 
Dries committed
408

Kjartan's avatar
Kjartan committed
409
  $vocabularies = array();
Dries's avatar
 
Dries committed
410
  $node_types = array();
Kjartan's avatar
Kjartan committed
411
  while ($voc = db_fetch_object($result)) {
412 413 414 415 416 417 418 419 420 421
    // 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
422
    $vocabularies[$voc->vid] = $voc;
Dries's avatar
 
Dries committed
423
  }
Dries's avatar
 
Dries committed
424

Kjartan's avatar
Kjartan committed
425 426
  return $vocabularies;
}
Dries's avatar
 
Dries committed
427

Dries's avatar
 
Dries committed
428
/**
429
 * Implementation of hook_form_alter().
Dries's avatar
 
Dries committed
430 431
 * Generate a form for selecting terms to associate with a node.
 */
432
function taxonomy_form_alter(&$form, $form_state, $form_id) {
433
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
434 435
    $node = $form['#node'];

436
    if (!isset($node->taxonomy)) {
437
      $terms = empty($node->nid) ? array() : taxonomy_node_get_terms($node);
Kjartan's avatar
Kjartan committed
438 439
    }
    else {
440 441 442 443
      // After preview the terms must be converted to objects.
      if (isset($form_state['node_preview'])) {
        $node->taxonomy = taxonomy_preview_terms($node);
      }
444
      $terms = $node->taxonomy;
Dries's avatar
 
Dries committed
445 446
    }

447
    $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
448

449 450
    while ($vocabulary = db_fetch_object($c)) {
      if ($vocabulary->tags) {
451
        if (isset($form_state['node_preview'])) {
452 453
          // Typed string can be changed by the user before preview, 
          // so we just insert the tags directly as provided in the form.
454 455 456 457 458
          $typed_string = $node->taxonomy['tags'][$vocabulary->vid];
        }
        else {
          $typed_string = taxonomy_implode_tags($terms, $vocabulary->vid) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
        }
459 460 461 462
        if ($vocabulary->help) {
          $help = $vocabulary->help;
        }
        else {
463
          $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
464
        }
465 466 467 468 469 470 471
        $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,
472
          '#maxlength' => 255,
473
        );
474 475
      }
      else {
476 477
        // Extract terms belonging to the vocabulary in question.
        $default_terms = array();
478 479 480
        foreach ($terms as $term) {     
          // Free tagging has no default terms and also no vid after preview.
          if (isset($term->vid) && $term->vid == $vocabulary->vid) {
481 482 483 484
            $default_terms[$term->tid] = $term;
          }
        }
        $form['taxonomy'][$vocabulary->vid] = taxonomy_form($vocabulary->vid, array_keys($default_terms), $vocabulary->help);
485
        $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
486
        $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
487
      }
Dries's avatar
Dries committed
488
    }
489
    if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
490 491
      if (count($form['taxonomy']) > 1) { 
        // Add fieldset only if form has more than 1 element.
492 493 494 495 496 497 498
        $form['taxonomy'] += array(
          '#type' => 'fieldset',
          '#title' => t('Categories'),
          '#collapsible' => TRUE,
          '#collapsed' => FALSE,
        );
      }
499 500
      $form['taxonomy']['#weight'] = -3;
      $form['taxonomy']['#tree'] = TRUE;
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
    }  
  }
}

/**
 * Helper function to covert terms after a preview.
 *
 * After preview the tags are an array instead of proper objects. This function
 * converts them back to objects with the exception of 'free tagging' terms,
 * because new tags can be added by the user before preview and those do not 
 * yet exist in the database. We therefore save those tags as a string so 
 * we can fill the form again after the preview.
 */
function taxonomy_preview_terms($node) {
  $taxonomy = array();
  foreach ($node->taxonomy as $key => $term) {
    unset($node->taxonomy[$key]);
    // A 'Multiple select' and a 'Free tagging' field returns an array.
    if (is_array($term)) {
      foreach ($term as $tid) {
        if ($key == 'tags') {
          // Free tagging; the values will be saved for later as strings 
	  // instead of objects to fill the form again.
          $taxonomy['tags'] = $term;
        }
        else {
          $taxonomy[$tid] = taxonomy_get_term($tid);
        }
      }
    }
    // A 'Single select' field returns the term id.
    elseif ($term) {
      $taxonomy[$term] = taxonomy_get_term($term);
Dries's avatar
Dries committed
534
    }
Dries's avatar
 
Dries committed
535
  }
536
  return $taxonomy;
Kjartan's avatar
Kjartan committed
537
}
Dries's avatar
 
Dries committed
538

Dries's avatar
 
Dries committed
539
/**
540
 * Find all terms associated with the given node, within one vocabulary.
Dries's avatar
 
Dries committed
541
 */
542 543
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
544 545 546
  $terms = array();
  while ($term = db_fetch_object($result)) {
    $terms[$term->$key] = $term;
Dries's avatar
 
Dries committed
547
  }
Kjartan's avatar
Kjartan committed
548 549 550
  return $terms;
}

Dries's avatar
 
Dries committed
551
/**
552
 * Find all terms associated with the given node, ordered by vocabulary and term weight.
Dries's avatar
 
Dries committed
553
 */
554
function taxonomy_node_get_terms($node, $key = 'tid') {
Kjartan's avatar
Kjartan committed
555
  static $terms;
Dries's avatar
 
Dries committed
556

557
  if (!isset($terms[$node->vid][$key])) {
558
    $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);
559
    $terms[$node->vid][$key] = array();
Dries's avatar
 
Dries committed
560
    while ($term = db_fetch_object($result)) {
561
      $terms[$node->vid][$key][$term->$key] = $term;
Dries's avatar
 
Dries committed
562 563
    }
  }
564
  return $terms[$node->vid][$key];
Kjartan's avatar
Kjartan committed
565
}
Dries's avatar
 
Dries committed
566

Dries's avatar
Dries committed
567 568 569
/**
 * Make sure incoming vids are free tagging enabled.
 */
570
function taxonomy_node_validate(&$node) {
571
  if (!empty($node->taxonomy)) {
572
    $terms = $node->taxonomy;
573
    if (!empty($terms['tags'])) {
Dries's avatar
Dries committed
574
      foreach ($terms['tags'] as $vid => $vid_value) {
575
        $vocabulary = taxonomy_vocabulary_load($vid);
576
        if (empty($vocabulary->tags)) {
577 578
          // see form_get_error $key = implode('][', $element['#parents']);
          // on why this is the key
579
          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
580 581 582 583 584 585
        }
      }
    }
  }
}

Dries's avatar
 
Dries committed
586 587 588
/**
 * Save term associations for a given node.
 */
589 590 591
function taxonomy_node_save($node, $terms) {

  taxonomy_node_delete_revision($node);
Dries's avatar
Dries committed
592 593 594

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

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

602
      $inserted = array();
Dries's avatar
Dries committed
603 604
      foreach ($typed_terms as $typed_term) {
        // See if the term exists in the chosen vocabulary
605
        // and return the tid; otherwise, add a new record.
Dries's avatar
Dries committed
606
        $possibilities = taxonomy_get_term_by_name($typed_term);
607
        $typed_term_tid = NULL; // tid match, if any.
Dries's avatar
Dries committed
608 609 610 611 612 613 614
        foreach ($possibilities as $possibility) {
          if ($possibility->vid == $vid) {
            $typed_term_tid = $possibility->tid;
          }
        }

        if (!$typed_term_tid) {
615 616 617
          $edit = array('vid' => $vid, 'name' => $typed_term);
          $status = taxonomy_save_term($edit);
          $typed_term_tid = $edit['tid'];
Dries's avatar
Dries committed
618 619
        }

620
        // Defend against duplicate, differently cased tags
621
        if (!isset($inserted[$typed_term_tid])) {
622
          db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $typed_term_tid);
623 624
          $inserted[$typed_term_tid] = TRUE;
        }
Dries's avatar
Dries committed
625 626 627
      }
    }
  }
Dries's avatar
 
Dries committed
628

629 630
  if (is_array($terms)) {
    foreach ($terms as $term) {
631 632 633
      if (is_array($term)) {
        foreach ($term as $tid) {
          if ($tid) {
634
            db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $tid);
635 636 637
          }
        }
      }
638
      else if (is_object($term)) {
639
        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $term->tid);
640
      }
641
      else if ($term) {
642
        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $term);
643
      }
Dries's avatar
 
Dries committed
644 645
    }
  }
Kjartan's avatar
Kjartan committed
646
}
Dries's avatar
 
Dries committed
647

Dries's avatar
 
Dries committed
648 649 650
/**
 * Remove associations of a node to its terms.
 */
651
function taxonomy_node_delete($node) {
652
  db_query('DELETE FROM {term_node} WHERE nid = %d', $node->nid);
653 654 655 656 657 658
}

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

662 663 664 665 666
/**
 * Implementation of hook_node_type().
 */
function taxonomy_node_type($op, $info) {
  if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) {
667 668 669 670
    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);
671 672 673
  }
}

Dries's avatar
 
Dries committed
674 675 676 677
/**
 * Find all term objects related to a given term ID.
 */
function taxonomy_get_related($tid, $key = 'tid') {
Kjartan's avatar
Kjartan committed
678
  if ($tid) {
Dries's avatar
 
Dries committed
679
    $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
680 681 682
    $related = array();
    while ($term = db_fetch_object($result)) {
      $related[$term->$key] = $term;
Dries's avatar
 
Dries committed
683
    }
Kjartan's avatar
Kjartan committed
684
    return $related;
Dries's avatar
 
Dries committed
685
  }
Kjartan's avatar
Kjartan committed
686 687
  else {
    return array();
Dries's avatar
 
Dries committed
688
  }
Kjartan's avatar
Kjartan committed
689
}
Dries's avatar
 
Dries committed
690

Dries's avatar
 
Dries committed
691 692 693 694
/**
 * Find all parents of a given term ID.
 */
function taxonomy_get_parents($tid, $key = 'tid') {
Kjartan's avatar
Kjartan committed
695
  if ($tid) {
696
    $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
697 698 699
    $parents = array();
    while ($parent = db_fetch_object($result)) {
      $parents[$parent->$key] = $parent;
Dries's avatar
 
Dries committed
700
    }
Kjartan's avatar
Kjartan committed
701
    return $parents;
Dries's avatar
 
Dries committed
702
  }
Kjartan's avatar
Kjartan committed
703 704 705 706
  else {
    return array();
  }
}
Dries's avatar
 
Dries committed
707

Dries's avatar
 
Dries committed
708 709 710 711
/**
 * Find all ancestors of a given term ID.
 */
function taxonomy_get_parents_all($tid) {
Dries's avatar
 
Dries committed
712 713 714 715 716 717 718 719 720 721 722 723
  $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
724 725 726 727
/**
 * Find all children of a term ID.
 */
function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
Kjartan's avatar
Kjartan committed
728
  if ($vid) {
729
    $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
730
  }
Kjartan's avatar
Kjartan committed
731
  else {
732
    $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
733 734 735 736 737 738 739
  }
  $children = array();
  while ($term = db_fetch_object($result)) {
    $children[$term->$key] = $term;
  }
  return $children;
}
Dries's avatar
 
Dries committed
740

Dries's avatar
 
Dries committed
741 742 743 744 745 746 747 748 749 750 751 752 753
/**
 * 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
754 755 756
 * @param $max_depth
 *   The number of levels of the tree to return. Leave NULL to return all levels.
 *
Dries's avatar
 
Dries committed
757 758 759
 * @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.
760
 *   Results are statically cached.
Dries's avatar
 
Dries committed
761 762
 */
function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
Dries's avatar
 
Dries committed
763
  static $children, $parents, $terms;
Dries's avatar
 
Dries committed
764

Kjartan's avatar
Kjartan committed
765
  $depth++;
Dries's avatar
 
Dries committed
766

Dries's avatar
 
Dries committed
767 768 769 770
  // 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
771

772
    $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
773
    while ($term = db_fetch_object($result)) {
Dries's avatar
 
Dries committed
774 775 776
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
Dries's avatar
 
Dries committed
777 778
    }
  }
Dries's avatar
 
Dries committed
779

Dries's avatar
 
Dries committed
780
  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
781 782
  $tree = array();
  if (!empty($children[$vid][$parent])) {
Dries's avatar
Dries committed
783 784
    foreach ($children[$vid][$parent] as $child) {
      if ($max_depth > $depth) {
785 786
        $term = drupal_clone($terms[$vid][$child]);
        $term->depth = $depth;
Dries's avatar
Dries committed
787
        // The "parent" attribute is not useful, as it would show one parent only.
788 789 790
        unset($term->parent);
        $term->parents = $parents[$vid][$child];
        $tree[] = $term;
Dries's avatar
Dries committed
791

792
        if (!empty($children[$vid][$child])) {
Dries's avatar
Dries committed
793 794
          $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $depth, $max_depth));
        }
Dries's avatar
 
Dries committed
795
      }
Dries's avatar
 
Dries committed
796
    }
Kjartan's avatar
Kjartan committed
797
  }
Dries's avatar
 
Dries committed
798

799
  return $tree;
Kjartan's avatar
Kjartan committed
800
}
Dries's avatar
 
Dries committed
801

Dries's avatar