Code coverage for /20081101/modules/taxonomy/taxonomy.module

Line #Times calledCode
1
<?php
2
// $Id: taxonomy.module,v 1.434 2008/10/12 04:30:08 webchick Exp $
3
4
/**
5
 * @file
6
 * Enables the organization of content into categories.
7
 */
8
9
/**
10
 * Implementation of hook_perm().
11
 */
122366
function taxonomy_perm() {
13
  return array(
14
    'administer taxonomy' => array(
15172
      'title' => t('Administer taxonomy'),
16172
      'description' => t('Manage taxonomy vocabularies and terms.'),
17172
    ),
18172
  );
190
}
20
21
/**
22
 * Implementation of hook_theme().
23
 */
242366
function taxonomy_theme() {
25
  return array(
26
    'taxonomy_term_select' => array(
27178
      'arguments' => array('element' => NULL),
28178
    ),
29
    'taxonomy_term_page' => array(
30178
      'arguments' => array('tids' => array(), 'result' => NULL),
31178
    ),
32
    'taxonomy_overview_vocabularies' => array(
33178
      'arguments' => array('form' => array()),
34178
    ),
35
    'taxonomy_overview_terms' => array(
36178
      'arguments' => array('form' => array()),
37178
    ),
38178
  );
390
}
40
41
/**
42
 * Implementation of hook_link().
43
 *
44
 * This hook is extended with $type = 'taxonomy terms' to allow themes to
45
 * print lists of terms associated with a node. Themes can print taxonomy
46
 * links with:
47
 *
48
 * if (module_exists('taxonomy')) {
49
 *   $terms = taxonomy_link('taxonomy terms', $node);
50
 *   print theme('links', $terms);
51
 * }
52
 */
532366
function taxonomy_link($type, $node = NULL) {
54254
  if ($type == 'taxonomy terms' && $node != NULL) {
55254
    $links = array();
56
    // If previewing, the terms must be converted to objects first.
57254
    if (isset($node->build_mode) && $node->build_mode ==
NODE_BUILD_PREVIEW) {
583
      $node->taxonomy = taxonomy_preview_terms($node);
593
    }
60254
    if (!empty($node->taxonomy)) {
6158
      foreach ($node->taxonomy as $term) {
62
        // During preview the free tagging terms are in an array unlike the
63
        // other terms which are objects. So we have to check if a $term
64
        // is an object or not.
6558
        if (is_object($term)) {
6658
          $links['taxonomy_term_' . $term->tid] = array(
6758
            'title' => $term->name,
6858
            'href' => taxonomy_term_path($term),
6958
            'attributes' => array('rel' => 'tag', 'title' =>
strip_tags($term->description))
7058
          );
7158
        }
72
        // Previewing free tagging terms; we don't link them because the
73
        // term-page might not exist yet.
74
        else {
750
          foreach ($term as $free_typed) {
760
            $typed_terms = drupal_explode_tags($free_typed);
770
            foreach ($typed_terms as $typed_term) {
780
              $links['taxonomy_preview_term_' . $typed_term] = array(
790
                'title' => $typed_term,
80
              );
810
            }
820
          }
83
        }
8458
      }
8558
    }
86
87
    // We call this hook again because some modules and themes
88
    // call taxonomy_link('taxonomy terms') directly.
89254
    drupal_alter('link', $links, $node);
90
91254
    return $links;
920
  }
93251
}
94
95
/**
96
 * For vocabularies not maintained by taxonomy.module, give the maintaining
97
 * module a chance to provide a path for terms in that vocabulary.
98
 *
99
 * @param $term
100
 *   A term object.
101
 * @return
102
 *   An internal Drupal path.
103
 */
104
1052366
function taxonomy_term_path($term) {
10658
  $vocabulary = taxonomy_vocabulary_load($term->vid);
10758
  if ($vocabulary->module != 'taxonomy' && $path =
module_invoke($vocabulary->module, 'term_path', $term)) {
10855
    return $path;
1090
  }
1103
  return 'taxonomy/term/' . $term->tid;
1110
}
112
113
/**
114
 * Implementation of hook_menu().
115
 */
1162366
function taxonomy_menu() {
117161
  $items['admin/content/taxonomy'] = array(
118161
    'title' => 'Taxonomy',
119161
    'description' => 'Manage tagging, categorization, and classification of
your content.',
120161
    'page callback' => 'drupal_get_form',
121161
    'page arguments' => array('taxonomy_overview_vocabularies'),
122161
    'access arguments' => array('administer taxonomy'),
123
  );
124
125161
  $items['admin/content/taxonomy/list'] = array(
126161
    'title' => 'List',
127161
    'type' => MENU_DEFAULT_LOCAL_TASK,
128161
    'weight' => -10,
129
  );
130
131161
  $items['admin/content/taxonomy/add'] = array(
132161
    'title' => 'Add vocabulary',
133161
    'page callback' => 'drupal_get_form',
134161
    'page arguments' => array('taxonomy_form_vocabulary'),
135161
    'access arguments' => array('administer taxonomy'),
136161
    'type' => MENU_LOCAL_TASK,
137
  );
138
139161
  $items['taxonomy/term/%taxonomy_terms'] = array(
140161
    'title' => 'Taxonomy term',
141161
    'page callback' => 'taxonomy_term_page',
142161
    'page arguments' => array(2),
143161
    'access arguments' => array('access content'),
144161
    'type' => MENU_CALLBACK,
145
  );
146
147161
  $items['taxonomy/term/%taxonomy_terms/view'] = array(
148161
    'title' => 'View',
149161
    'type' => MENU_DEFAULT_LOCAL_TASK,
150
  );
151
152161
  $items['taxonomy/term/%taxonomy_term/edit'] = array(
153161
    'title' => 'Edit term',
154161
    'page callback' => 'taxonomy_term_edit',
155161
    'page arguments' => array(2),
156161
    'access arguments' => array('administer taxonomy'),
157161
    'type' => MENU_LOCAL_TASK,
158161
    'weight' => 10,
159
  );
160
161161
  $items['taxonomy/autocomplete'] = array(
162161
    'title' => 'Autocomplete taxonomy',
163161
    'page callback' => 'taxonomy_autocomplete',
164161
    'access arguments' => array('access content'),
165161
    'type' => MENU_CALLBACK,
166
  );
167
168161
  $items['admin/content/taxonomy/%taxonomy_vocabulary'] = array(
169161
    'title' => 'Vocabulary', // this is replaced by callback
170161
    'page callback' => 'drupal_get_form',
171161
    'page arguments' => array('taxonomy_form_vocabulary', 3),
172161
    'title callback' => 'taxonomy_admin_vocabulary_title_callback',
173161
    'title arguments' => array(3),
174161
    'access arguments' => array('administer taxonomy'),
175161
    'type' => MENU_CALLBACK,
176
  );
177
178161
  $items['admin/content/taxonomy/%taxonomy_vocabulary/edit'] = array(
179161
    'title' => 'Edit vocabulary',
180161
    'type' => MENU_DEFAULT_LOCAL_TASK,
181161
    'weight' => -20,
182
  );
183
184161
  $items['admin/content/taxonomy/%taxonomy_vocabulary/list'] = array(
185161
    'title' => 'List terms',
186161
    'page callback' => 'drupal_get_form',
187161
    'page arguments' => array('taxonomy_overview_terms', 3),
188161
    'access arguments' => array('administer taxonomy'),
189161
    'type' => MENU_LOCAL_TASK,
190161
    'weight' => -10,
191
  );
192
193161
  $items['admin/content/taxonomy/%taxonomy_vocabulary/add'] = array(
194161
    'title' => 'Add term',
195161
    'page callback' => 'drupal_get_form',
196161
    'page arguments' => array('taxonomy_form_term', 3),
197161
    'access arguments' => array('administer taxonomy'),
198161
    'type' => MENU_LOCAL_TASK,
199
  );
200
201161
  return $items;
2020
}
203
204
/**
205
 * Return the vocabulary name given the vocabulary object.
206
 */
2072366
function taxonomy_admin_vocabulary_title_callback($vocabulary) {
20810
  return check_plain($vocabulary->name);
2090
}
210
211
/**
212
 * Save a vocabulary given form values or an equivalent array.
213
 */
2142366
function taxonomy_save_vocabulary(&$edit) {
21511
  $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
216
21711
  if (!isset($edit['module'])) {
2182
    $edit['module'] = 'taxonomy';
2192
  }
220
22111
  if (!empty($edit['vid']) && !empty($edit['name'])) {
2224
    drupal_write_record('vocabulary', $edit, 'vid');
2234
    db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d",
$edit['vid']);
2244
    foreach ($edit['nodes'] as $type => $selected) {
2254
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d,
'%s')", $edit['vid'], $type);
2264
    }
2274
    module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
2284
    $status = SAVED_UPDATED;
2294
  }
2308
  elseif (!empty($edit['vid'])) {
2315
    $status = taxonomy_del_vocabulary($edit['vid']);
2325
  }
233
  else {
2348
    drupal_write_record('vocabulary', $edit);
2358
    foreach ($edit['nodes'] as $type => $selected) {
2364
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d,
'%s')", $edit['vid'], $type);
2374
    }
2388
    module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
2398
    $status = SAVED_NEW;
240
  }
241
24211
  cache_clear_all();
243
24411
  return $status;
2450
}
246
247
/**
248
 * Delete a vocabulary.
249
 *
250
 * @param $vid
251
 *   A vocabulary ID.
252
 * @return
253
 *   Constant indicating items were deleted.
254
 */
2552366
function taxonomy_del_vocabulary($vid) {
2565
  $vocabulary = (array) taxonomy_vocabulary_load($vid);
257
2585
  db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
2595
  db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
2605
  $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
2615
  while ($term = db_fetch_object($result)) {
2624
    taxonomy_del_term($term->tid);
2634
  }
264
2655
  module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
266
2675
  cache_clear_all();
268
2695
  return SAVED_DELETED;
2700
}
271
272
/**
273
 * Dynamicly check and update the hierarachy flag of a vocabulary.
274
 *
275
 * Checks the current parents of all terms in a vocabulary and updates the
276
 * vocabularies hierarchy setting to the lowest possible level. A hierarchy
with
277
 * no parents in any of its terms will be given a hierarchy of 0. If terms
278
 * contain at most a single parent, the vocabulary will be given a
hierarchy of
279
 * 1. If any term contain multiple parents, the vocabulary will be given a
280
 * hieararchy of 2.
281
 *
282
 * @param $vocabulary
283
 *   An array of the vocabulary structure.
284
 * @param $changed_term
285
 *   An array of the term structure that was updated.
286
 */
2872366
function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
2880
  $tree = taxonomy_get_tree($vocabulary['vid']);
2890
  $hierarchy = 0;
2900
  foreach ($tree as $term) {
291
    // Update the changed term with the new parent value before
comparision.
2920
    if ($term->tid == $changed_term['tid']) {
2930
      $term = (object)$changed_term;
2940
      $term->parents = $term->parent;
2950
    }
296
    // Check this term's parent count.
2970
    if (count($term->parents) > 1) {
2980
      $hierarchy = 2;
2990
      break;
3000
    }
3010
    elseif (count($term->parents) == 1 && 0 !==
array_shift($term->parents)) {
3020
      $hierarchy = 1;
3030
    }
3040
  }
3050
  if ($hierarchy != $vocabulary['hierarchy']) {
3060
    $vocabulary['hierarchy'] = $hierarchy;
3070
    taxonomy_save_vocabulary($vocabulary);
3080
  }
309
3100
  return $hierarchy;
3110
}
312
313
/**
314
 * Helper function for taxonomy_form_term_submit().
315
 *
316
 * @param $form_state['values']
317
 * @return
318
 *   Status constant indicating if term was inserted or updated.
319
 */
3202366
function taxonomy_save_term(&$form_values) {
321
  $form_values += array(
32210
    'description' => '',
323
    'weight' => 0
32410
  );
325
32610
  if (!empty($form_values['tid']) && $form_values['name']) {
3271
    drupal_write_record('term_data', $form_values, 'tid');
3281
    $hook = 'update';
3291
    $status = SAVED_UPDATED;
3301
  }
3319
  elseif (!empty($form_values['tid'])) {
3320
    return taxonomy_del_term($form_values['tid']);
3330
  }
334
  else {
3359
    drupal_write_record('term_data', $form_values);
3369
    $hook = 'insert';
3379
    $status = SAVED_NEW;
338
  }
339
34010
  db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d',
$form_values['tid'], $form_values['tid']);
34110
  if (!empty($form_values['relations'])) {
3421
    foreach ($form_values['relations'] as $related_id) {
3431
      if ($related_id != 0) {
3441
        db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d,
%d)', $form_values['tid'], $related_id);
3451
      }
3461
    }
3471
  }
348
34910
  db_query('DELETE FROM {term_hierarchy} WHERE tid = %d',
$form_values['tid']);
35010
  if (!isset($form_values['parent']) || empty($form_values['parent'])) {
3517
    $form_values['parent'] = array(0);
3527
  }
35310
  if (is_array($form_values['parent'])) {
35410
    foreach ($form_values['parent'] as $parent) {
35510
      if (is_array($parent)) {
3561
        foreach ($parent as $tid) {
3571
          db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d,
%d)', $form_values['tid'], $tid);
3581
        }
3591
      }
360
      else {
36110
        db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d,
%d)', $form_values['tid'], $parent);
362
      }
36310
    }
36410
  }
365
  else {
3660
    db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)',
$form_values['tid'], $form_values['parent']);
367
  }
368
36910
  db_query('DELETE FROM {term_synonym} WHERE tid = %d',
$form_values['tid']);
37010
  if (!empty($form_values['synonyms'])) {
3711
    foreach (explode ("\n", str_replace("\r", '',
$form_values['synonyms'])) as $synonym) {
3721
      if ($synonym) {
3731
        db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d,
'%s')", $form_values['tid'], chop($synonym));
3741
      }
3751
    }
3761
  }
377
37810
  if (isset($hook)) {
37910
    module_invoke_all('taxonomy', $hook, 'term', $form_values);
38010
  }
381
38210
  cache_clear_all();
383
38410
  return $status;
3850
}
386
387
/**
388
 * Delete a term.
389
 *
390
 * @param $tid
391
 *   The term ID.
392
 * @return
393
 *   Status constant indicating deletion.
394
 */
3952366
function taxonomy_del_term($tid) {
3964
  $tids = array($tid);
3974
  while ($tids) {
3984
    $children_tids = $orphans = array();
3994
    foreach ($tids as $tid) {
400
      // See if any of the term's children are about to be become orphans:
4014
      if ($children = taxonomy_get_children($tid)) {
4022
        foreach ($children as $child) {
403
          // If the term has multiple parents, we don't delete it.
4042
          $parents = taxonomy_get_parents($child->tid);
4052
          if (count($parents) == 1) {
4061
            $orphans[] = $child->tid;
4071
          }
4082
        }
4092
      }
410
4114
      $term = (array) taxonomy_term_load($tid);
412
4134
      db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
4144
      db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
4154
      db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d',
$tid, $tid);
4164
      db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
4174
      db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
418
4194
      module_invoke_all('taxonomy', 'delete', 'term', $term);
4204
    }
421
4224
    $tids = $orphans;
4234
  }
424
4254
  cache_clear_all();
426
4274
  return SAVED_DELETED;
4280
}
429
430
/**
431
 * Generate a form element for selecting terms from a vocabulary.
432
 */
4332366
function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy')
{
43419
  $vocabulary = taxonomy_vocabulary_load($vid);
43519
  $help = ($help) ? $help : $vocabulary->help;
436
43719
  if (!$vocabulary->multiple) {
43817
    $blank = ($vocabulary->required) ? t('- Please choose -') : t('- None
selected -');
43917
  }
440
  else {
4412
    $blank = ($vocabulary->required) ? 0 : t('- None -');
442
  }
443
44419
  return _taxonomy_term_select(check_plain($vocabulary->name), $name,
$value, $vid, $help, intval($vocabulary->multiple), $blank);
4450
}
446
447
/**
448
 * Generate a set of options for selecting a term from all vocabularies.
449
 */
4502366
function taxonomy_form_all($free_tags = 0) {
45111
  $vocabularies = taxonomy_get_vocabularies();
45211
  $options = array();
45311
  foreach ($vocabularies as $vid => $vocabulary) {
45411
    if ($vocabulary->tags && !$free_tags) {
4550
      continue;
4560
    }
45711
    $tree = taxonomy_get_tree($vid);
45811
    if ($tree && (count($tree) > 0)) {
4590
      $options[$vocabulary->name] = array();
4600
      foreach ($tree as $term) {
4610
        $options[$vocabulary->name][$term->tid] = str_repeat('-',
$term->depth) . $term->name;
4620
      }
4630
    }
46411
  }
46511
  return $options;
4660
}
467
468
/**
469
 * Return an array of all vocabulary objects.
470
 *
471
 * @param $type
472
 *   If set, return only those vocabularies associated with this node type.
473
 */
4742366
function taxonomy_get_vocabularies($type = NULL) {
47520
  if ($type) {
4760
    $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);
4770
  }
478
  else {
47920
    $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'));
480
  }
481
48220
  $vocabularies = array();
48320
  $node_types = array();
48420
  while ($voc = db_fetch_object($result)) {
485
    // If no node types are associated with a vocabulary, the LEFT JOIN
will
486
    // return a NULL value for type.
48720
    if (isset($voc->type)) {
48820
      $node_types[$voc->vid][$voc->type] = $voc->type;
48920
      unset($voc->type);
49020
      $voc->nodes = $node_types[$voc->vid];
49120
    }
4921
    elseif (!isset($voc->nodes)) {
4931
      $voc->nodes = array();
4941
    }
49520
    $vocabularies[$voc->vid] = $voc;
49620
  }
497
49820
  return $vocabularies;
4990
}
500
501
/**
502
 * Implementation of hook_form_alter().
503
 * Generate a form for selecting terms to associate with a node.
504
 * We check for taxonomy_override_selector before loading the full
505
 * vocabulary, so contrib modules can intercept before hook_form_alter
506
 *  and provide scalable alternatives.
507
 */
5082366
function taxonomy_form_alter(&$form, $form_state, $form_id) {
5091575
  if (!variable_get('taxonomy_override_selector', FALSE) &&
!empty($form['#node_edit_form'])) {
51089
    $node = $form['#node'];
511
51289
    if (!isset($node->taxonomy)) {
51341
      $terms = empty($node->nid) ? array() :
taxonomy_node_get_terms($node);
51441
    }
515
    else {
516
      // After preview the terms must be converted to objects.
51748
      if (isset($form_state['node_preview'])) {
5180
        $node->taxonomy = taxonomy_preview_terms($node);
5190
      }
52048
      $terms = $node->taxonomy;
521
    }
522
52389
    $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);
524
52589
    while ($vocabulary = db_fetch_object($c)) {
52621
      if ($vocabulary->tags) {
5274
        if (isset($form_state['node_preview'])) {
528
          // Typed string can be changed by the user before preview,
529
          // so we just insert the tags directly as provided in the form.
5300
          $typed_string = $node->taxonomy['tags'][$vocabulary->vid];
5310
        }
532
        else {
5334
          $typed_string = taxonomy_implode_tags($terms, $vocabulary->vid) .
(array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] :
NULL);
534
        }
5354
        if ($vocabulary->help) {
5364
          $help = $vocabulary->help;
5374
        }
538
        else {
5390
          $help = t('A comma-separated list of terms describing this
content. Example: funny, bungee jumping, "Company, Inc.".');
540
        }
5414
        $form['taxonomy']['tags'][$vocabulary->vid] = array('#type' =>
'textfield',
5424
          '#title' => $vocabulary->name,
5434
          '#description' => $help,
5444
          '#required' => $vocabulary->required,
5454
          '#default_value' => $typed_string,
5464
          '#autocomplete_path' => 'taxonomy/autocomplete/' .
$vocabulary->vid,
5474
          '#weight' => $vocabulary->weight,
5484
          '#maxlength' => 255,
549
        );
5504
      }
551
      else {
552
        // Extract terms belonging to the vocabulary in question.
55319
        $default_terms = array();
55419
        foreach ($terms as $term) {
555
          // Free tagging has no default terms and also no vid after
preview.
55618
          if (isset($term->vid) && $term->vid == $vocabulary->vid) {
55718
            $default_terms[$term->tid] = $term;
55818
          }
55918
        }
56019
        $form['taxonomy'][$vocabulary->vid] =
taxonomy_form($vocabulary->vid, array_keys($default_terms),
$vocabulary->help);
56119
        $form['taxonomy'][$vocabulary->vid]['#weight'] =
$vocabulary->weight;
56219
        $form['taxonomy'][$vocabulary->vid]['#required'] =
$vocabulary->required;
563
      }
56421
    }
56589
    if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
56621
      if (count($form['taxonomy']) > 1) {
567
        // Add fieldset only if form has more than 1 element.
5680
        $form['taxonomy'] += array(
5692
          '#type' => 'fieldset',
5702
          '#title' => t('Vocabularies'),
5712
          '#collapsible' => TRUE,
5722
          '#collapsed' => FALSE,
573
        );
5742
      }
57521
      $form['taxonomy']['#weight'] = -3;
57621
      $form['taxonomy']['#tree'] = TRUE;
57721
    }
57889
  }
5791575
}
580
581
/**
582
 * Helper function to convert terms after a preview.
583
 *
584
 * After preview the tags are an array instead of proper objects. This
function
585
 * converts them back to objects with the exception of 'free tagging'
terms,
586
 * because new tags can be added by the user before preview and those do
not
587
 * yet exist in the database. We therefore save those tags as a string so
588
 * we can fill the form again after the preview.
589
 */
5902366
function taxonomy_preview_terms($node) {
5913
  $taxonomy = array();
5923
  if (isset($node->taxonomy)) {
5930
    foreach ($node->taxonomy as $key => $term) {
5940
      unset($node->taxonomy[$key]);
595
      // A 'Multiple select' and a 'Free tagging' field returns an array.
5960
      if (is_array($term)) {
5970
        foreach ($term as $tid) {
5980
          if ($key == 'tags') {
599
            // Free tagging; the values will be saved for later as strings
600
            // instead of objects to fill the form again.
6010
            $taxonomy['tags'] = $term;
6020
          }
603
          else {
6040
            $taxonomy[$tid] = taxonomy_term_load($tid);
605
          }
6060
        }
6070
      }
608
      // A 'Single select' field returns the term id.
6090
      elseif ($term) {
6100
        $taxonomy[$term] = taxonomy_term_load($term);
6110
      }
6120
    }
6130
  }
6143
  return $taxonomy;
6150
}
616
617
/**
618
 * Find all terms associated with the given node, within one vocabulary.
619
 */
6202366
function taxonomy_node_get_terms_by_vocabulary($node, $vid, $key = 'tid') {
62161
  $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);
62261
  $terms = array();
62361
  while ($term = db_fetch_object($result)) {
62461
    $terms[$term->$key] = $term;
62561
  }
62661
  return $terms;
6270
}
628
629
/**
630
 * Find all terms associated with the given node, ordered by vocabulary and
term weight.
631
 */
6322366
function taxonomy_node_get_terms($node, $key = 'tid') {
633436
  static $terms;
634
635436
  if (!isset($terms[$node->vid][$key])) {
636436
    $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);
637436
    $terms[$node->vid][$key] = array();
638436
    while ($term = db_fetch_object($result)) {
63982
      $terms[$node->vid][$key][$term->$key] = $term;
64082
    }
641436
  }
642436
  return $terms[$node->vid][$key];
6430
}
644
645
/**
646
 * Make sure incoming vids are free tagging enabled.
647
 */
6482366
function taxonomy_node_validate(&$node) {
64979
  if (!empty($node->taxonomy)) {
65018
    $terms = $node->taxonomy;
65118
    if (!empty($terms['tags'])) {
6524
      foreach ($terms['tags'] as $vid => $vid_value) {
6534
        $vocabulary = taxonomy_vocabulary_load($vid);
6544
        if (empty($vocabulary->tags)) {
655
          // see form_get_error $key = implode('][', $element['#parents']);
656
          // on why this is the key
6570
          form_set_error("taxonomy][tags][$vid", t('The %name vocabulary
can not be modified in this way.', array('%name' => $vocabulary->name)));
6580
        }
6594
      }
6604
    }
66118
  }
66279
}
663
664
/**
665
 * Save term associations for a given node.
666
 */
6672366
function taxonomy_node_save($node, $terms) {
668
66917
  taxonomy_node_delete_revision($node);
670
671
  // Free tagging vocabularies do not send their tids in the form,
672
  // so we'll detect them here and process them independently.
67317
  if (isset($terms['tags'])) {
6744
    $typed_input = $terms['tags'];
6754
    unset($terms['tags']);
676
6774
    foreach ($typed_input as $vid => $vid_value) {
6784
      $typed_terms = drupal_explode_tags($vid_value);
679
6804
      $inserted = array();
6814
      foreach ($typed_terms as $typed_term) {
682
        // See if the term exists in the chosen vocabulary
683
        // and return the tid; otherwise, add a new record.
6840
        $possibilities = taxonomy_get_term_by_name($typed_term);
6850
        $typed_term_tid = NULL; // tid match, if any.
6860
        foreach ($possibilities as $possibility) {
6870
          if ($possibility->vid == $vid) {
6880
            $typed_term_tid = $possibility->tid;
6890
          }
6900
        }
691
6920
        if (!$typed_term_tid) {
6930
          $edit = array('vid' => $vid, 'name' => $typed_term);
6940
          $status = taxonomy_save_term($edit);
6950
          $typed_term_tid = $edit['tid'];
6960
        }
697
698
        // Defend against duplicate, differently cased tags
6990
        if (!isset($inserted[$typed_term_tid])) {
7000
          db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d,
%d)', $node->nid, $node->vid, $typed_term_tid);
7010
          $inserted[$typed_term_tid] = TRUE;
7020
        }
7030
      }
7044
    }
7054
  }
706
70717
  if (is_array($terms)) {
70817
    foreach ($terms as $term) {
70915
      if (is_array($term)) {
7102
        foreach ($term as $tid) {
7112
          if ($tid) {
7122
            db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d,
%d, %d)', $node->nid, $node->vid, $tid);
7132
          }
7142
        }
7152
      }
71613
      elseif (is_object($term)) {
7170
        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d,
%d)', $node->nid, $node->vid, $term->tid);
7180
      }
71913
      elseif ($term) {
72013
        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d,
%d)', $node->nid, $node->vid, $term);
72113
      }
72215
    }
72317
  }
72417
}
725
726
/**
727
 * Remove associations of a node to its terms.
728
 */
7292366
function taxonomy_node_delete($node) {
73012
  db_query('DELETE FROM {term_node} WHERE nid = %d', $node->nid);
73112
}
732
733
/**
734
 * Remove associations of a node to its terms.
735
 */
7362366
function taxonomy_node_delete_revision($node) {
73718
  db_query('DELETE FROM {term_node} WHERE vid = %d', $node->vid);
73818
}
739
740
/**
741
 * Implementation of hook_node_type().
742
 */
7432366
function taxonomy_node_type($op, $info) {
744135
  if ($op == 'update' && !empty($info->old_type) && $info->type !=
$info->old_type) {
7450
    db_query("UPDATE {vocabulary_node_types} SET type = '%s' WHERE type =
'%s'", $info->type, $info->old_type);
7460
  }
747135
  elseif ($op == 'delete') {
7480
    db_query("DELETE FROM {vocabulary_node_types} WHERE type = '%s'",
$info->type);
7490
  }
750135
}
751
752
/**
753
 * Find all term objects related to a given term ID.
754
 */
7552366
function taxonomy_get_related($tid, $key = 'tid') {
75611
  if ($tid) {
7574
    $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);
7584
    $related = array();
7594
    while ($term = db_fetch_object($result)) {
7601
      $related[$term->$key] = $term;
7611
    }
7624
    return $related;
7630
  }
764
  else {
7657
    return array();
766
  }
7670
}
768
769
/**
770
 * Find all parents of a given term ID.
771
 */
7722366
function taxonomy_get_parents($tid, $key = 'tid') {
77387
  if ($tid) {
77474
    $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);
77574
    $parents = array();
77674
    while ($parent = db_fetch_object($result)) {
77761
      $parents[$parent->$key] = $parent;
77861
    }
77974
    return $parents;
7800
  }
781
  else {
78213
    return array();
783
  }
7840
}
785
786
/**
787
 * Find all ancestors of a given term ID.
788
 */
7892366
function taxonomy_get_parents_all($tid) {
79067
  $parents = array();
79167
  if ($tid) {
79267
    $parents[] = taxonomy_term_load($tid);
79367
    $n = 0;
79467
    while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
79559
      $parents = array_merge($parents, $parent);
79659
      $n++;
79759
    }
79867
  }
79967
  return $parents;
8000
}
801
802
/**
803
 * Find all children of a term ID.
804
 */
8052366
function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
8064
  if ($vid) {
8070
    $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);
8080
  }
809
  else {
8104
    $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);
811
  }
8124
  $children = array();
8134
  while ($term = db_fetch_object($result)) {
8142
    $children[$term->$key] = $term;
8152
  }
8164
  return $children;
8170
}
818
819
/**
820
 * Create a hierarchical representation of a vocabulary.
821
 *
822
 * @param $vid
823
 *   Which vocabulary to generate the tree for.
824
 *
825
 * @param $parent
826
 *   The term ID under which to generate the tree. If 0, generate the tree
827
 *   for the entire vocabulary.
828
 *
829
 * @param $depth
830
 *   Internal use only.
831
 *
832
 * @param $max_depth
833
 *   The number of levels of the tree to return. Leave NULL to return all
levels.
834
 *
835
 * @return
836
 *   An array of all term objects in the tree. Each term object is extended
837
 *   to have "depth" and "parents" attributes in addition to its normal
ones.
838
 *   Results are statically cached.
839
 */
8402366
function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth =
NULL) {
841130
  static $children, $parents, $terms;
842
843130
  $depth++;
844
845
  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
846
  // and its children, too.
847130
  if (!isset($children[$vid])) {
848130
    $children[$vid] = array();
849
850130
    $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);
851130
    while ($term = db_fetch_object($result)) {
852112
      $children[$vid][$term->parent][] = $term->tid;
853112
      $parents[$vid][$term->tid][] = $term->parent;
854112
      $terms[$vid][$term->tid] = $term;
855112
    }
856130
  }
857
858130
  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
859130
  $tree = array();
860130
  if (!empty($children[$vid][$parent])) {
861103
    foreach ($children[$vid][$parent] as $child) {
862103
      if ($max_depth > $depth) {
863103
        $term = clone $terms[$vid][$child];
864103
        $term->depth = $depth;
865
        // The "parent" attribute is not useful, as it would show one
parent only.
866103
        unset($term->parent);
867103
        $term->parents = $parents[$vid][$child];
868103
        $tree[] = $term;
869
870103
        if (!empty($children[$vid][$child])) {
87188
          $tree = array_merge($tree, taxonomy_get_tree($vid, $child,
$depth, $max_depth));
87288
        }
873103
      }
874103
    }
875103
  }
876
877130
  return $tree;
8780
}
879
880
/**
881
 * Return an array of synonyms of the given term ID.
882
 */
8832366
function taxonomy_get_synonyms($tid) {
88411
  if ($tid) {
8854
    $synonyms = array();
8864
    $result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d',
$tid);
8874
    while ($synonym = db_fetch_array($result)) {
8881
      $synonyms[] = $synonym['name'];
8891
    }
8904
    return $synonyms;
8910
  }
892
  else {
8937
    return array();
894
  }
8950
}
896
897
/**
898
 * Return the term object that has the given string as a synonym.
899
 *
900
 * @param $synonym
901
 *   The string to compare against.
902
 * @param $reset
903
 *   Whether to reset the internal cache for this synonym.
904
 * @return
905
 *   A term object, or FALSE if no matching term is found.
906
 */
9072366
function taxonomy_get_synonym_root($synonym, $reset = FALSE) {
9080
  static $synonyms = array();
909
9100
  if ($reset) {
9110
    unset($synonyms[$synonym]);
9120
  }
913
9140
  if (!isset($synonyms[$synonym])) {
9150
    $synonyms[$synonym] = db_query("SELECT * FROM {term_synonym} s,
{term_data} t WHERE t.tid = s.tid AND s.name = :name", array(':name' =>
$synonym))->fetch();
9160
  }
9170
  return $synonyms[$synonym];
9180
}
919
920
/**
921
 * Count the number of published nodes classified by a term.
922
 *
923
 * @param $tid
924
 *   The term's ID
925
 *
926
 * @param $type
927
 *   The $node->type. If given, taxonomy_term_count_nodes only counts
928
 *   nodes of $type that are classified with the term $tid.
929
 *
930
 * @return int
931
 *   An integer representing a number of nodes.
932
 *   Results are statically cached.
933
 */
9342366
function taxonomy_term_count_nodes($tid, $type = 0) {
9350
  static $count;
936
9370
  if (!isset($count[$type])) {
938
    // $type == 0 always evaluates TRUE if $type is a string
9390
    if (is_numeric($type)) {
9400
      $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'));
9410
    }
942
    else {
9430
      $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);
944
    }
9450
    $count[$type] = array();
9460
    while ($term = db_fetch_object($result)) {
9470
      $count[$type][$term->tid] = $term->c;
9480
    }
9490
  }
9500
  $children_count = 0;
9510
  foreach (_taxonomy_term_children($tid) as $c) {
9520
    $children_count += taxonomy_term_count_nodes($c, $type);
9530
  }
9540
  return $children_count + (isset($count[$type][$tid]) ?
$count[$type][$tid] : 0);
9550
}
956
957
/**
958
 * Helper for taxonomy_term_count_nodes(). Used to find out
959
 * which terms are children of a parent term.
960
 *
961
 * @param $tid
962
 *   The parent term's ID
963
 *
964
 * @return array
965
 *   An array of term IDs representing the children of $tid.
966
 *   Results are statically cached.
967
 *
968
 */
9692366
function _taxonomy_term_children($tid) {
9700
  static $children;
971
9720
  if (!isset($children)) {
9730
    $result = db_query('SELECT tid, parent FROM {term_hierarchy}');
9740
    while ($term = db_fetch_object($result)) {
9750
      $children[$term->parent][] = $term->tid;
9760
    }
9770
  }
9780
  return isset($children[$tid]) ? $children[$tid] : array();
9790
}
980
981
/**
982
 * Try to map a string to an existing term, as for glossary use.
983
 *
984
 * Provides a case-insensitive and trimmed mapping, to maximize the
985
 * likelihood of a successful match.
986
 *
987
 * @param name
988
 *   Name of the term to search for.
989
 *
990
 * @return
991
 *   An array of matching term objects.
992
 */
9932366
function taxonomy_get_term_by_name($name) {
9945
  $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data}
t WHERE LOWER(t.name) = LOWER('%s')", 't', 'tid'), trim($name));
9955
  $result = array();
9965
  while ($term = db_fetch_object($db_result)) {
9975
    $result[] = $term;
9985
  }
999
10005
  return $result;
10010
}
1002
1003
/**
1004
 * Return the vocabulary object matching a vocabulary ID.
1005
 *
1006
 * @param $vid
1007
 *   The vocabulary's ID.
1008
 *
1009
 * @param $reset
1010
 *   A boolean flag indicating whether to reset the internal cache.
1011
 *
1012
 * @return
1013
 *   The vocabulary object with all of its metadata, if exists, FALSE
otherwise.
1014
 *   Results are statically cached.
1015
 */
10162366
function taxonomy_vocabulary_load($vid, $reset = FALSE) {
1017139
  static $vocabularies = array();
1018
1019139
  if ($reset) {
10202
    unset($vocabularies[$vid]);
10212
  }
1022
1023139
  if (empty($vocabularies[$vid])) {
1024
    // Initialize so if this vocabulary does not exist, we have
1025
    // that cached, and we will not try to load this later.
1026139
    $vocabularies[$vid] = FALSE;
1027
    // Try to load the data and fill up the object.
1028139
    $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);
1029139
    $node_types = array();
1030139
    while ($voc = db_fetch_object($result)) {
1031139
      if (!empty($voc->type)) {
1032136
        $node_types[$voc->type] = $voc->type;
1033136
      }
1034139
      unset($voc->type);
1035139
      $voc->nodes = $node_types;
1036139
      $vocabularies[$vid] = $voc;
1037139
    }
1038139
  }
1039
1040
  // Return FALSE if this vocabulary does not exist.
1041139
  return !empty($vocabularies[$vid]) ? $vocabularies[$vid] : FALSE;
10420
}
1043
1044
/**
1045
 * Return array of tids and join operator.
1046
 *
1047
 * This is a wrapper function for taxonomy_terms_parse_string which is
called
1048
 * by the menu system when loading a path with taxonomy terms.
1049
 */
10502366
function taxonomy_terms_load($str_tids) {
10513
  $terms = taxonomy_terms_parse_string($str_tids);
10523
  return $terms;
10530
}
1054
1055
/**
1056
 * Return the term object matching a term ID.
1057
 *
1058
 * @param $tid
1059
 *   A term's ID
1060
 *
1061
 * @return Object
1062
 *   A term object. Results are statically cached.
1063
 */
10642366
function taxonomy_term_load($tid, $reset = FALSE) {
106577
  if (!is_numeric($tid)) {
10660
    return FALSE;
10670
  }
106877
  static $terms = array();
106977
  if (!isset($terms[$tid]) || $reset) {
107077
    $terms[$tid] = db_fetch_object(db_query('SELECT * FROM {term_data}
WHERE tid = %d', $tid));
107177
  }
107277
  return $terms[$tid];
10730
}
1074
10752366
function _taxonomy_term_select($title, $name, $value, $vocabulary_id,
$description, $multiple, $blank, $exclude = array()) {
107629
  $tree = taxonomy_get_tree($vocabulary_id);
107729
  $options = array();
1078
107929
  if ($blank) {
108029
    $options[''] = $blank;
108129
  }
108229
  if ($tree) {
108324
    foreach ($tree as $term) {
108424
      if (!in_array($term->tid, $exclude)) {
108521
        $choice = new stdClass();
108621
        $choice->option = array($term->tid => str_repeat('-', $term->depth)
. $term->name);
108721
        $options[] = $choice;
108821
      }
108924
    }
109024
  }
1091
109229
  return array('#type' => 'select',
109329
    '#title' => $title,
109429
    '#default_value' => $value,
109529
    '#options' => $options,
109629
    '#description' => $description,
109729
    '#multiple' => $multiple,
109829
    '#size' => $multiple ? min(9, count($options)) : 0,
109929
    '#weight' => -15,
110029
    '#theme' => 'taxonomy_term_select',
110129
  );
11020
}
1103
1104
/**
1105
 * Format the selection field for choosing terms
1106
 * (by deafult the default selection field is used).
1107
 *
1108
 * @ingroup themeable
1109
 */
11102366
function theme_taxonomy_term_select($element) {
111128
  return theme('select', $element);
11120
}
1113
1114
/**
1115
 * Finds all nodes that match selected taxonomy conditions.
1116
 *
1117
 * @param $tids
1118
 *   An array of term IDs to match.
1119
 * @param $operator
1120
 *   How to interpret multiple IDs in the array. Can be "or" or "and".
1121
 * @param $depth
1122
 *   How many levels deep to traverse the taxonomy tree. Can be a
nonnegative
1123
 *   integer or "all".
1124
 * @param $pager
1125
 *   Whether the nodes are to be used with a pager (the case on most Drupal
1126
 *   pages) or not (in an XML feed, for example).
1127
 * @param $order
1128
 *   The order clause for the query that retrieve the nodes.
1129
 * @return
1130
 *   A resource identifier pointing to the query results.
1131
 */
11322366
function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth =
0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC') {
11331
  if (count($tids) > 0) {
1134
    // For each term ID, generate an array of descendant term IDs to the
right depth.
11351
    $descendant_tids = array();
11361
    if ($depth === 'all') {
11370
      $depth = NULL;
11380
    }
11391
    foreach ($tids as $index => $tid) {
11401
      $term = taxonomy_term_load($tid);
11411
      $tree = taxonomy_get_tree($term->vid, $tid, -1, $depth);
11421
      $descendant_tids[] = array_merge(array($tid),
array_map('_taxonomy_get_tid_from_term', $tree));
11431
    }
1144
11451
    if ($operator == 'or') {
11460
      $args = call_user_func_array('array_merge', $descendant_tids);
11470
      $placeholders = db_placeholders($args, 'int');
11480
      $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM
{node} n INNER JOIN {term_node} tn ON n.vid = tn.vid WHERE tn.tid IN (' .
$placeholders . ') AND n.status = 1 ORDER BY ' . $order;
11490
      $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN
{term_node} tn ON n.vid = tn.vid WHERE tn.tid IN (' . $placeholders . ')
AND n.status = 1';
11500
    }
1151
    else {
11521
      $joins = '';
11531
      $wheres = '';
11541
      $args = array();
11551
      foreach ($descendant_tids as $index => $tids) {
11561
        $joins .= ' INNER JOIN {term_node} tn' . $index . ' ON n.vid = tn'
. $index . '.vid';
11571
        $wheres .= ' AND tn' . $index . '.tid IN (' .
db_placeholders($tids, 'int') . ')';
11581
        $args = array_merge($args, $tids);
11591
      }
11601
      $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM
{node} n ' . $joins . ' WHERE n.status = 1 ' . $wheres . ' ORDER BY ' .
$order;
11611
      $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n ' . $joins
. ' WHERE n.status = 1 ' . $wheres;
1162
    }
11631
    $sql = db_rewrite_sql($sql);
11641
    $sql_count = db_rewrite_sql($sql_count);
11651
    if ($pager) {
11661
      $result = pager_query($sql, variable_get('default_nodes_main', 10),
0, $sql_count, $args);
11671
    }
1168
    else {
11690
      $result = db_query_range($sql, $args, 0,
variable_get('feed_default_items', 10));
1170
    }
11711
  }
1172
11731
  return $result;
11740
}
1175
1176
/**
1177
 * Accepts the result of a pager_query() call, such as that performed by
1178
 * taxonomy_select_nodes(), and formats each node along with a pager.
1179
 */
11802366
function taxonomy_render_nodes($result) {
11811
  $output = '';
11821
  $has_rows = FALSE;
11831
  while ($node = db_fetch_object($result)) {
11840
    $output .= node_view(node_load($node->nid), 1);
11850
    $has_rows = TRUE;
11860
  }
11871
  if ($has_rows) {
11880
    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10),
0);
11890
  }
1190
  else {
11911
    $output .= '<p>' . t('There are currently no posts in this category.')
. '</p>';
1192
  }
11931
  return $output;
11940
}
1195
1196
/**
1197
 * Implementation of hook_nodeapi_load().
1198
 */
11992366
function taxonomy_nodeapi_load($node, $arg = 0) {
1200436
  $output['taxonomy'] = taxonomy_node_get_terms($node);
1201436
  return $output;
12020
}
1203
1204
/**
1205
 * Implementation of hook_nodeapi_insert().
1206
 */
12072366
function taxonomy_nodeapi_insert($node, $arg = 0) {
120858
  if (!empty($node->taxonomy)) {
120911
    taxonomy_node_save($node, $node->taxonomy);
121011
  }
121158
}
1212
1213
/**
1214
 * Implementation of hook_nodeapi_update().
1215
 */
12162366
function taxonomy_nodeapi_update($node, $arg = 0) {
121731
  if (!empty($node->taxonomy)) {
12186
    taxonomy_node_save($node, $node->taxonomy);
12196
  }
122031
}
1221
1222
/**
1223
 * Implementation of hook_nodeapi_delete().
1224
 */
12252366
function taxonomy_nodeapi_delete($node, $arg = 0) {
122612
  taxonomy_node_delete($node);
122712
}
1228
1229
/**
1230
 * Implementation of hook_nodeapi_delete_revision().
1231
 */
12322366
function taxonomy_nodeapi_delete_revision($node, $arg = 0) {
12331
  taxonomy_node_delete_revision($node);
12341
}
1235
1236
/**
1237
 * Implementation of hook_nodeapi_validate().
1238
 */
12392366
function taxonomy_nodeapi_validate($node, $arg = 0) {
124079
  taxonomy_node_validate($node);
124179
}
1242
1243
/**
1244
 * Implementation of hook_nodeapi_rss_item().
1245
 */
12462366
function taxonomy_nodeapi_rss_item($node, $arg = 0) {
12470
  return taxonomy_rss_item($node);
12480
}
1249
1250
/**
1251
 * Implementation of hook_nodeapi_update_index().
1252
 */
12532366
function taxonomy_nodeapi_update_index($node, $arg = 0) {
12542
  return taxonomy_node_update_index($node);
12550
}
1256
1257
/**
1258
 * Implementation of hook_nodeapi('update_index').
1259
 */
12602366
function taxonomy_node_update_index(&$node) {
12612
  $output = array();
12622
  foreach ($node->taxonomy as $term) {
12630
    $output[] = $term->name;
12640
  }
12652
  if (count($output)) {
12660
    return '<strong>(' . implode(', ', $output) . ')</strong>';
12670
  }
12682
}
1269
1270
/**
1271
 * Parses a comma or plus separated string of term IDs.
1272
 *
1273
 * @param $str_tids
1274
 *   A string of term IDs, separated by plus or comma.
1275
 *   comma (,) means AND
1276
 *   plus (+) means OR
1277
 *
1278
 * @return an associative array with an operator key (either 'and'
1279
 *   or 'or') and a tid key containing an array of the term ids.
1280
 */
12812366
function taxonomy_terms_parse_string($str_tids) {
12823
  $terms = array('operator' => '', 'tids' => array());
12833
  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
12840
    $terms['operator'] = 'or';
1285
    // The '+' character in a query string may be parsed as ' '.
12860
    $terms['tids'] = preg_split('/[+ ]/', $str_tids);
12870
  }
12883
  elseif (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
12893
    $terms['operator'] = 'and';
12903
    $terms['tids'] = explode(',', $str_tids);
12913
  }
12923
  $terms['str_tids'] = $str_tids;
12933
  return $terms;
12940
}
1295
1296
/**
1297
 * Provides category information for RSS feeds.
1298
 */
12992366
function taxonomy_rss_item($node) {
13000
  $output = array();
13010
  foreach ($node->taxonomy as $term) {
13020
    $output[] = array('key'   => 'category',
13030
                      'value' => check_plain($term->name),
13040
                      'attributes' => array('domain' =>
url(taxonomy_term_path($term), array('absolute' => TRUE))));
13050
  }
13060
  return $output;
13070
}
1308
1309
/**
1310
 * Implementation of hook_help().
1311
 */
13122366
function taxonomy_help($path, $arg) {
1313
  switch ($path) {
13141678
    case 'admin/help#taxonomy':
131529
      $output = '<p>' . t('The taxonomy module allows you to categorize
content using various systems of classification. Free-tagging vocabularies
are created by users on the fly when they submit posts (as commonly found
in blogs and social bookmarking applications). Controlled vocabularies
allow for administrator-defined short lists of terms as well as complex
hierarchies with multiple relationships between different terms. These
methods can be applied to different content types and combined together to
create a powerful and flexible method of classifying and presenting your
content.') . '</p>';
131629
      $output .= '<p>' . t('For example, when creating a recipe site, you
might want to classify posts by both the type of meal and preparation time.
A vocabulary for each allows you to categorize using each criteria
independently instead of creating a tag for every possible combination.') .
'</p>';
131729
      $output .= '<p>' . t('Type of Meal: <em>Appetizer, Main Course,
Salad, Dessert</em>') . '</p>';
131829
      $output .= '<p>' . t('Preparation Time: <em>0-30mins, 30-60mins, 1-2
hrs, 2hrs+</em>') . '</p>';
131929
      $output .= '<p>' . t("Each taxonomy term (often called a 'category'
or 'tag' in other systems) automatically provides lists of posts and a
corresponding RSS feed. These taxonomy/term URLs can be manipulated to
generate AND and OR lists of posts classified with terms. In our recipe
site example, it then becomes easy to create pages displaying 'Main
courses', '30 minute recipes', or '30 minute main courses and appetizers'
by using terms on their own or in combination with others. There are a
significant number of contributed modules which you to alter and extend the
behavior of the core module for both display and organization of terms.") .
'</p>';
132029
      $output .= '<p>' . t("Terms can also be organized in parent/child
relationships from the admin interface. An example would be a vocabulary
grouping countries under their parent geo-political regions. The taxonomy
module also enables advanced implementations of hierarchy, for example
placing Turkey in both the 'Middle East' and 'Europe'.") . '</p>';
132129
      $output .= '<p>' . t('The taxonomy module supports the use of both
synonyms and related terms, but does not directly use this functionality.
However, optional contributed or custom modules may make full use of these
advanced features.') . '</p>';
132229
      $output .= '<p>' . t('For more information, see the online handbook
entry for <a href="@taxonomy">Taxonomy module</a>.', array('@taxonomy' =>
'http://drupal.org/handbook/modules/taxonomy/')) . '</p>';
132329
      return $output;
13241676
    case 'admin/content/taxonomy':
13256
      $output = '<p>' . t("The taxonomy module allows you to categorize
your content using both tags and administrator defined terms. It is a
flexible tool for classifying content with many advanced features. To
begin, create a 'Vocabulary' to hold one set of terms or tags. You can
create one free-tagging vocabulary for everything, or separate controlled
vocabularies to define the various properties of your content, for example
'Countries' or 'Colors'.") . '</p>';
13266
      $output .= '<p>' . t('Use the list below to configure and review the
vocabularies defined on your site, or to list and manage the terms (tags)
they contain. A vocabulary may (optionally) be tied to specific content
types as shown in the <em>Type</em> column and, if so, will be displayed
when creating or editing posts of that type. Multiple vocabularies tied to
the same content type will be displayed in the order shown below. To change
the order of a vocabulary, grab a drag-and-drop handle under the
<em>Name</em> column and drag it to a new location in the list. (Grab a
handle by clicking and holding the mouse while hovering over a handle
icon.) Remember that your changes will not be saved until you click the
<em>Save</em> button at the bottom of the page.') . '</p>';
13276
      return $output;
13281670
    case 'admin/content/taxonomy/%/list':
13292
      $vocabulary = taxonomy_vocabulary_load($arg[3]);
13302
      if ($vocabulary->tags) {
13312
        return '<p>' . t('%capital_name is a free-tagging vocabulary. To
change the name or description of a term, click the <em>edit</em> link next
to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name)))
. '</p>';
13320
      }
13330
      switch ($vocabulary->hierarchy) {
13340
        case 0:
13350
          return '<p>' . t('%capital_name is a flat vocabulary. You may
organize the terms in the %name vocabulary by using the handles on the left
side of the table. To change the name or description of a term, click the
<em>edit</em> link next to the term.', array('%capital_name' =>
drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
13360
        case 1:
13370
          return '<p>' . t('%capital_name is a single hierarchy vocabulary.
You may organize the terms in the %name vocabulary by using the handles on
the left side of the table. To change the name or description of a term,
click the <em>edit</em> link next to the term.', array('%capital_name' =>
drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
13380
        case 2:
13390
          return '<p>' . t('%capital_name is a multiple hierarchy
vocabulary. To change the name or description of a term, click the
<em>edit</em> link next to the term. Drag and drop of multiple hierarchies
is not supported, but you can re-enable drag and drop support by editing
each term to include only a single parent.', array('%capital_name' =>
drupal_ucfirst($vocabulary->name))) . '</p>';
13400
      }
13411668
    case 'admin/content/taxonomy/add':
13422
      return '<p>' . t('Define how your vocabulary will be presented to
administrators and users, and which content types to categorize with it.
Tags allows users to create terms when submitting posts by typing a comma
separated list. Otherwise terms are chosen from a select list and can only
be created by users with the "administer taxonomy" permission.') . '</p>';
13430
  }
13441666
}
1345
1346
/**
1347
 * Helper function for array_map purposes.
1348
 */
13492366
function _taxonomy_get_tid_from_term($term) {
13500
  return $term->tid;
13510
}
1352
1353
/**
1354
 * Implode a list of tags of a certain vocabulary into a string.
1355
 */
13562366
function taxonomy_implode_tags($tags, $vid = NULL) {
13574
  $typed_tags = array();
13584
  foreach ($tags as $tag) {
1359
    // Extract terms belonging to the vocabulary in question.
13601
    if (is_null($vid) || $tag->vid == $vid) {
1361
1362
      // Commas and quotes in tag names are special cases, so encode 'em.
13630
      if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !==
FALSE) {
13640
        $tag->name = '"' . str_replace('"', '""', $tag->name) . '"';
13650
      }
1366
13670
      $typed_tags[] = $tag->name;
13680
    }
13691
  }
13704
  return implode(', ', $typed_tags);
13710
}
1372
1373
/**
1374
 * Implementation of hook_hook_info().
1375
 */
13762366
function taxonomy_hook_info() {
1377
  return array(
1378
    'taxonomy' => array(
1379
      'taxonomy' => array(
1380
        'insert' => array(
138155
          'runs when' => t('After saving a new term to the database'),
138255
        ),
1383
        'update' => array(
138455
          'runs when' => t('After saving an updated term to the database'),
138555
        ),
1386
        'delete' => array(
138755
          'runs when' => t('After deleting a term')
138855
        ),
138955
      ),
139055
    ),
139155
  );
13920
}
13932366