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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
        // 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.        
        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))
          );
        }
        // Previewing free tagging terms; we don't link them because the term-page might not exist yet.
        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,
              );           
            }
          }
70
        }
Dries's avatar
 
Dries committed
71
      }
Dries's avatar
 
Dries committed
72
    }
Kjartan's avatar
Kjartan committed
73

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

    return $links;
78 79 80
  }
}

81 82 83 84 85 86 87 88 89 90
/**
 * 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.
 */

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

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

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

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

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

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

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

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

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

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

Dries's avatar
 
Dries committed
180 181
  return $items;
}
Dries's avatar
 
Dries committed
182

183
function taxonomy_save_vocabulary(&$edit) {
184
  $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
Dries's avatar
 
Dries committed
185

186 187 188 189
  if (!isset($edit['module'])) {
    $edit['module'] = 'taxonomy';
  }

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

  cache_clear_all();
Dries's avatar
 
Dries committed
212

213
  return $status;
Kjartan's avatar
Kjartan committed
214
}
Dries's avatar
 
Dries committed
215

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

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

Dries's avatar
 
Dries committed
234
  module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
Dries's avatar
 
Dries committed
235

Dries's avatar
 
Dries committed
236 237
  cache_clear_all();

238
  return SAVED_DELETED;
Dries's avatar
 
Dries committed
239 240
}

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

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

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

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

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

306
  if (isset($hook)) {
307
    module_invoke_all('taxonomy', $hook, 'term', $form_values);
308 309
  }

Dries's avatar
 
Dries committed
310 311
  cache_clear_all();

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

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

339
      $term = (array) taxonomy_get_term($tid);
Dries's avatar
 
Dries committed
340

341 342 343 344 345
      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
346

347 348
      module_invoke_all('taxonomy', 'delete', 'term', $term);
    }
Dries's avatar
 
Dries committed
349

350 351
    $tids = $orphans;
  }
Dries's avatar
 
Dries committed
352

Dries's avatar
 
Dries committed
353
  cache_clear_all();
354 355

  return SAVED_DELETED;
Dries's avatar
 
Dries committed
356 357
}

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

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

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

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

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

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

Kjartan's avatar
Kjartan committed
422 423
  return $vocabularies;
}
Dries's avatar
 
Dries committed
424

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

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

444
    $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
445

446 447
    while ($vocabulary = db_fetch_object($c)) {
      if ($vocabulary->tags) {
448 449 450 451 452 453 454
        if (isset($form_state['node_preview'])) {
          // Typed string can be changed by the user before preview, so we just insert the tags directly as provided in the form.
          $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);
        }
455 456 457 458
        if ($vocabulary->help) {
          $help = $vocabulary->help;
        }
        else {
459
          $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
460
        }
461 462 463 464 465 466 467
        $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,
468
          '#maxlength' => 255,
469
        );
470 471
      }
      else {
472 473
        // Extract terms belonging to the vocabulary in question.
        $default_terms = array();
474 475 476
        foreach ($terms as $term) {     
          // Free tagging has no default terms and also no vid after preview.
          if (isset($term->vid) && $term->vid == $vocabulary->vid) {
477 478 479 480
            $default_terms[$term->tid] = $term;
          }
        }
        $form['taxonomy'][$vocabulary->vid] = taxonomy_form($vocabulary->vid, array_keys($default_terms), $vocabulary->help);
481
        $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
482
        $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
483
      }
Dries's avatar
Dries committed
484
    }
485
    if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
486 487 488 489 490 491 492 493
      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,
        );
      }
494 495
      $form['taxonomy']['#weight'] = -3;
      $form['taxonomy']['#tree'] = TRUE;
496 497 498 499 500 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
    }  
  }
}

/**
 * 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
529
    }
Dries's avatar
 
Dries committed
530
  }
531
  return $taxonomy;
Kjartan's avatar
Kjartan committed
532
}
Dries's avatar
 
Dries committed
533

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

Dries's avatar
 
Dries committed
546
/**
547
 * Find all terms associated with the given node, ordered by vocabulary and term weight.
Dries's avatar
 
Dries committed
548
 */
549
function taxonomy_node_get_terms($node, $key = 'tid') {
Kjartan's avatar
Kjartan committed
550
  static $terms;
Dries's avatar
 
Dries committed
551

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

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

Dries's avatar
 
Dries committed
581 582 583
/**
 * Save term associations for a given node.
 */
584 585 586
function taxonomy_node_save($node, $terms) {

  taxonomy_node_delete_revision($node);
Dries's avatar
Dries committed
587 588 589

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

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

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

        if (!$typed_term_tid) {
610 611 612
          $edit = array('vid' => $vid, 'name' => $typed_term);
          $status = taxonomy_save_term($edit);
          $typed_term_tid = $edit['tid'];
Dries's avatar
Dries committed
613 614
        }

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

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

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

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

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

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

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

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

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

Kjartan's avatar
Kjartan committed
760
  $depth++;
Dries's avatar
 
Dries committed
761

Dries's avatar
 
Dries committed
762 763 764 765
  // 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
766

767
    $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
768
    while ($term = db_fetch_object($result)) {
Dries's avatar
 
Dries committed
769 770 771
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
Dries's avatar
 
Dries committed
772 773
    }
  }
Dries's avatar
 
Dries committed
774

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

787
        if (!empty($children[$vid][$child])) {
Dries's avatar
Dries committed
788 789
          $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $depth, $max_depth));
        }
Dries's avatar
 
Dries committed
790
      }
Dries's avatar
 
Dries committed
791
    }
Kjartan's avatar
Kjartan committed
792
  }
Dries's avatar
 
Dries committed
793

794
  return $tree;
Kjartan's avatar
Kjartan committed
795
}
Dries's avatar
 
Dries committed
796

Dries's avatar
 
Dries committed
797 798 799
/**
 * Return an array of synonyms of the given term ID.
 */
Kjartan's avatar
Kjartan committed
800 801
function taxonomy_get_synonyms($tid) {
  if ($tid) {
802
    $synonyms = array();
Dries's avatar
 
Dries committed
803
    $result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d', $tid);
Kjartan's avatar
Kjartan committed
804
    while ($synonym = db_fetch_array($result)) {
Dries's avatar
 
Dries committed