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

Line #Times calledCode
1
<?php
2
// $Id: taxonomy.module,v 1.425 2008/07/24 16:25:19 dries Exp $
3
4
/**
5
 * @file
6
 * Enables the organization of content into categories.
7
 */
8
9
/**
10
 * Implementation of hook_perm().
11
 */
122027
function taxonomy_perm() {
13
  return array(
1487
    'administer taxonomy' => t('Manage taxonomy vocabularies and terms.'),
1587
  );
160
}
17
18
/**
19
 * Implementation of hook_theme().
20
 */
212027
function taxonomy_theme() {
22
  return array(
23
    'taxonomy_term_select' => array(
2491
      'arguments' => array('element' => NULL),
2591
    ),
26
    'taxonomy_term_page' => array(
2791
      'arguments' => array('tids' => array(), 'result' => NULL),
2891
    ),
29
    'taxonomy_overview_vocabularies' => array(
3091
      'arguments' => array('form' => array()),
3191
    ),
32
    'taxonomy_overview_terms' => array(
3391
      'arguments' => array('form' => array()),
3491
    ),
3591
  );
360
}
37
38
/**
39
 * Implementation of hook_link().
40
 *
41
 * This hook is extended with $type = 'taxonomy terms' to allow themes to
42
 * print lists of terms associated with a node. Themes can print taxonomy
43
 * links with:
44
 *
45
 * if (module_exists('taxonomy')) {
46
 *   $terms = taxonomy_link('taxonomy terms', $node);
47
 *   print theme('links', $terms);
48
 * }
49
 */
502027
function taxonomy_link($type, $node = NULL) {
51233
  if ($type == 'taxonomy terms' && $node != NULL) {
52233
    $links = array();
53
    // If previewing, the terms must be converted to objects first.
54233
    if (isset($node->build_mode) && $node->build_mode ==
NODE_BUILD_PREVIEW) {
552
      $node->taxonomy = taxonomy_preview_terms($node);
562
    }
57233
    if (!empty($node->taxonomy)) {
5858
      foreach ($node->taxonomy as $term) {
59
        // During preview the free tagging terms are in an array unlike
the
60
        // other terms which are objects. So we have to check if a $term
61
        // is an object or not.
6258
        if (is_object($term)) {
6358
          $links['taxonomy_term_' . $term->tid] = array(
6458
            'title' => $term->name,
6558
            'href' => taxonomy_term_path($term),
6658
            'attributes' => array('rel' => 'tag', 'title' =>
strip_tags($term->description))
6758
          );
6858
        }
69
        // Previewing free tagging terms; we don't link them because the
70
        // term-page might not exist yet.
71
        else {
720
          foreach ($term as $free_typed) {
730
            $typed_terms = drupal_explode_tags($free_typed);
740
            foreach ($typed_terms as $typed_term) {
750
              $links['taxonomy_preview_term_' . $typed_term] = array(
760
                'title' => $typed_term,
77
              );
780
            }
790
          }
80
        }
8158
      }
8258
    }
83
84
    // We call this hook again because some modules and themes
85
    // call taxonomy_link('taxonomy terms') directly.
86233
    drupal_alter('link', $links, $node);
87
88233
    return $links;
890
  }
90231
}
91
92
/**
93
 * For vocabularies not maintained by taxonomy.module, give the
maintaining
94
 * module a chance to provide a path for terms in that vocabulary.
95
 *
96
 * @param $term
97
 *   A term object.
98
 * @return
99
 *   An internal Drupal path.
100
 */
101
1022027
function taxonomy_term_path($term) {
10358
  $vocabulary = taxonomy_vocabulary_load($term->vid);
10458
  if ($vocabulary->module != 'taxonomy' && $path =
module_invoke($vocabulary->module, 'term_path', $term)) {
1050
    return $path;
1060
  }
10758
  return 'taxonomy/term/' . $term->tid;
1080
}
109
110
/**
111
 * Implementation of hook_menu().
112
 */
1132027
function taxonomy_menu() {
11485
  $items['admin/content/taxonomy'] = array(
11585
    'title' => 'Taxonomy',
11685
    'description' => 'Manage tagging, categorization, and classification of
your content.',
11785
    'page callback' => 'drupal_get_form',
11885
    'page arguments' => array('taxonomy_overview_vocabularies'),
11985
    'access arguments' => array('administer taxonomy'),
120
  );
121
12285
  $items['admin/content/taxonomy/list'] = array(
12385
    'title' => 'List',
12485
    'type' => MENU_DEFAULT_LOCAL_TASK,
12585
    'weight' => -10,
126
  );
127
12885
  $items['admin/content/taxonomy/add/vocabulary'] = array(
12985
    'title' => 'Add vocabulary',
13085
    'page callback' => 'drupal_get_form',
13185
    'page arguments' => array('taxonomy_form_vocabulary'),
13285
    'access arguments' => array('administer taxonomy'),
13385
    'type' => MENU_LOCAL_TASK,
13485
    'parent' => 'admin/content/taxonomy',
135
  );
136
13785
  $items['admin/content/taxonomy/edit/vocabulary/%taxonomy_vocabulary'] =
array(
13885
    'title' => 'Edit vocabulary',
13985
    'page callback' => 'taxonomy_admin_vocabulary_edit',
14085
    'page arguments' => array(5),
14185
    'access arguments' => array('administer taxonomy'),
14285
    'type' => MENU_CALLBACK,
143
  );
144
14585
  $items['admin/content/taxonomy/edit/term'] = array(
14685
    'title' => 'Edit term',
14785
    'page callback' => 'taxonomy_admin_term_edit',
14885
    'access arguments' => array('administer taxonomy'),
14985
    'type' => MENU_CALLBACK,
150
  );
151
15285
  $items['taxonomy/term/%'] = array(
15385
    'title' => 'Taxonomy term',
15485
    'page callback' => 'taxonomy_term_page',
15585
    'page arguments' => array(2),
15685
    'access arguments' => array('access content'),
15785
    'type' => MENU_CALLBACK,
158
  );
159
16085
  $items['taxonomy/autocomplete'] = array(
16185
    'title' => 'Autocomplete taxonomy',
16285
    'page callback' => 'taxonomy_autocomplete',
16385
    'access arguments' => array('access content'),
16485
    'type' => MENU_CALLBACK,
165
  );
16685
  $items['admin/content/taxonomy/%taxonomy_vocabulary'] = array(
16785
    'title' => 'List terms',
16885
    'page callback' => 'drupal_get_form',
16985
    'page arguments' => array('taxonomy_overview_terms', 3),
17085
    'access arguments' => array('administer taxonomy'),
17185
    'type' => MENU_CALLBACK,
172
  );
173
17485
  $items['admin/content/taxonomy/%taxonomy_vocabulary/list'] = array(
17585
    'title' => 'List',
17685
    'type' => MENU_DEFAULT_LOCAL_TASK,
17785
    'weight' => -10,
178
  );
179
18085
  $items['admin/content/taxonomy/%taxonomy_vocabulary/add/term'] = array(
18185
    'title' => 'Add term',
18285
    'page callback' => 'taxonomy_add_term_page',
18385
    'page arguments' => array(3),
18485
    'access arguments' => array('administer taxonomy'),
18585
    'type' => MENU_LOCAL_TASK,
18685
    'parent' => 'admin/content/taxonomy/%taxonomy_vocabulary',
187
  );
188
18985
  return $items;
1900
}
191
1922027
function taxonomy_save_vocabulary(&$edit) {
1938
  $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
194
1958
  if (!isset($edit['module'])) {
1961
    $edit['module'] = 'taxonomy';
1971
  }
198
1998
  if (!empty($edit['vid']) && !empty($edit['name'])) {
2003
    drupal_write_record('vocabulary', $edit, 'vid');
2013
    db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d",
$edit['vid']);
2023
    foreach ($edit['nodes'] as $type => $selected) {
2033
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d,
'%s')", $edit['vid'], $type);
2043
    }
2053
    module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
2063
    $status = SAVED_UPDATED;
2073
  }
2085
  else if (!empty($edit['vid'])) {
2093
    $status = taxonomy_del_vocabulary($edit['vid']);
2103
  }
211
  else {
2125
    drupal_write_record('vocabulary', $edit);
2135
    foreach ($edit['nodes'] as $type => $selected) {
2144
      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d,
'%s')", $edit['vid'], $type);
2154
    }
2165
    module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
2175
    $status = SAVED_NEW;
218
  }
219
2208
  cache_clear_all();
221
2228
  return $status;
2230
}
224
225
/**
226
 * Delete a vocabulary.
227
 *
228
 * @param $vid
229
 *   A vocabulary ID.
230
 * @return
231
 *   Constant indicating items were deleted.
232
 */
2332027
function taxonomy_del_vocabulary($vid) {
2343
  $vocabulary = (array) taxonomy_vocabulary_load($vid);
235
2363
  db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
2373
  db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
2383
  $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
2393
  while ($term = db_fetch_object($result)) {
2402
    taxonomy_del_term($term->tid);
2412
  }
242
2433
  module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
244
2453
  cache_clear_all();
246
2473
  return SAVED_DELETED;
2480
}
249
250
/**
251
 * Dynamicly check and update the hierarachy flag of a vocabulary.
252
 *
253
 * Checks the current parents of all terms in a vocabulary and updates the
254
 * vocabularies hierarchy setting to the lowest possible level. A hierarchy
with
255
 * no parents in any of its terms will be given a hierarchy of 0. If terms
256
 * contain at most a single parent, the vocabulary will be given a
hierarchy of
257
 * 1. If any term contain multiple parents, the vocabulary will be given a
258
 * hieararchy of 2.
259
 *
260
 * @param $vocabulary
261
 *   An array of the vocabulary structure.
262
 * @param $changed_term
263
 *   An array of the term structure that was updated.
264
 */
2652027
function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
2660
  $tree = taxonomy_get_tree($vocabulary['vid']);
2670
  $hierarchy = 0;
2680
  foreach ($tree as $term) {
269
    // Update the changed term with the new parent value before
comparision.
2700
    if ($term->tid == $changed_term['tid']) {
2710
      $term = (object)$changed_term;
2720
      $term->parents = $term->parent;
2730
    }
274
    // Check this term's parent count.
2750
    if (count($term->parents) > 1) {
2760
      $hierarchy = 2;
2770
      break;
2780
    }
2790
    elseif (count($term->parents) == 1 && 0 !==
array_shift($term->parents)) {
2800
      $hierarchy = 1;
2810
    }
2820
  }
2830
  if ($hierarchy != $vocabulary['hierarchy']) {
2840
    $vocabulary['hierarchy'] = $hierarchy;
2850
    taxonomy_save_vocabulary($vocabulary);
2860
  }
287
2880
  return $hierarchy;
2890
}
290
291
/**
292
 * Helper function for taxonomy_form_term_submit().
293
 *
294
 * @param $form_state['values']
295
 * @return
296
 *   Status constant indicating if term was inserted or updated.
297
 */
2982027
function taxonomy_save_term(&$form_values) {
299
  $form_values += array(
3006
    'description' => '',
301
    'weight' => 0
3026
  );
303
3046
  if (!empty($form_values['tid']) && $form_values['name']) {
3050
    drupal_write_record('term_data', $form_values, 'tid');
3060
    $hook = 'update';
3070
    $status = SAVED_UPDATED;
3080
  }
3096
  else if (!empty($form_values['tid'])) {
3100
    return taxonomy_del_term($form_values['tid']);
3110
  }
312
  else {
3136
    drupal_write_record('term_data', $form_values);
3146
    $hook = 'insert';
3156
    $status = SAVED_NEW;
316
  }
317
3186
  db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d',
$form_values['tid'], $form_values['tid']);
3196
  if (!empty($form_values['relations'])) {
3201
    foreach ($form_values['relations'] as $related_id) {
3211
      if ($related_id != 0) {
3221
        db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d,
%d)', $form_values['tid'], $related_id);
3231
      }
3241
    }
3251
  }
326
3276
  db_query('DELETE FROM {term_hierarchy} WHERE tid = %d',
$form_values['tid']);
3286
  if (!isset($form_values['parent']) || empty($form_values['parent'])) {
3293
    $form_values['parent'] = array(0);
3303
  }
3316
  if (is_array($form_values['parent'])) {
3326
    foreach ($form_values['parent'] as $parent) {
3336
      if (is_array($parent)) {
3341
        foreach ($parent as $tid) {
3351
          db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d,
%d)', $form_values['tid'], $tid);
3361
        }
3371
      }
338
      else {
3396
        db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d,
%d)', $form_values['tid'], $parent);
340
      }
3416
    }
3426
  }
343
  else {
3440
    db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)',
$form_values['tid'], $form_values['parent']);
345
  }
346
3476
  db_query('DELETE FROM {term_synonym} WHERE tid = %d',
$form_values['tid']);
3486
  if (!empty($form_values['synonyms'])) {
3491
    foreach (explode ("\n", str_replace("\r", '',
$form_values['synonyms'])) as $synonym) {
3501
      if ($synonym) {
3511
        db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d,
'%s')", $form_values['tid'], chop($synonym));
3521
      }
3531
    }
3541
  }
355
3566
  if (isset($hook)) {
3576
    module_invoke_all('taxonomy', $hook, 'term', $form_values);
3586
  }
359
3606
  cache_clear_all();
361
3626
  return $status;
3630
}
364
365
/**
366
 * Delete a term.
367
 *
368
 * @param $tid
369
 *   The term ID.
370
 * @return
371
 *   Status constant indicating deletion.
372
 */
3732027
function taxonomy_del_term($tid) {
3742
  $tids = array($tid);
3752
  while ($tids) {
3762
    $children_tids = $orphans = array();
3772
    foreach ($tids as $tid) {
378
      // See if any of the term's children are about to be become orphans:
3792
      if ($children = taxonomy_get_children($tid)) {
3801
        foreach ($children as $child) {
381
          // If the term has multiple parents, we don't delete it.
3821
          $parents = taxonomy_get_parents($child->tid);
3831
          if (count($parents) == 1) {
3840
            $orphans[] = $child->tid;
3850
          }
3861
        }
3871
      }
388
3892
      $term = (array) taxonomy_get_term($tid);
390
3912
      db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
3922
      db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
3932
      db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d',
$tid, $tid);
3942
      db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
3952
      db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
396
3972
      module_invoke_all('taxonomy', 'delete', 'term', $term);
3982
    }
399
4002
    $tids = $orphans;
4012
  }
402
4032
  cache_clear_all();
404
4052
  return SAVED_DELETED;
4060
}
407
408
/**
409
 * Generate a form element for selecting terms from a vocabulary.
410
 */
4112027
function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy')
{
41219
  $vocabulary = taxonomy_vocabulary_load($vid);
41319
  $help = ($help) ? $help : $vocabulary->help;
414
41519
  if (!$vocabulary->multiple) {
41617
    $blank = ($vocabulary->required) ? t('- Please choose -') : t('- None
selected -');
41717
  }
418
  else {
4192
    $blank = ($vocabulary->required) ? 0 : t('- None -');
420
  }
421
42219
  return _taxonomy_term_select(check_plain($vocabulary->name), $name,
$value, $vid, $help, intval($vocabulary->multiple), $blank);
4230
}
424
425
/**
426
 * Generate a set of options for selecting a term from all vocabularies.
427
 */
4282027
function taxonomy_form_all($free_tags = 0) {
4290
  $vocabularies = taxonomy_get_vocabularies();
4300
  $options = array();
4310
  foreach ($vocabularies as $vid => $vocabulary) {
4320
    if ($vocabulary->tags && !$free_tags) {
4330
      continue;
4340
    }
4350
    $tree = taxonomy_get_tree($vid);
4360
    if ($tree && (count($tree) > 0)) {
4370
      $options[$vocabulary->name] = array();
4380
      foreach ($tree as $term) {
4390
        $options[$vocabulary->name][$term->tid] = str_repeat('-',
$term->depth) . $term->name;
4400
      }
4410
    }
4420
  }
4430
  return $options;
4440
}
445
446
/**
447
 * Return an array of all vocabulary objects.
448
 *
449
 * @param $type
450
 *   If set, return only those vocabularies associated with this node
type.
451
 */
4522027
function taxonomy_get_vocabularies($type = NULL) {
4535
  if ($type) {
4540
    $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);
4550
  }
456
  else {
4575
    $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'));
458
  }
459
4605
  $vocabularies = array();
4615
  $node_types = array();
4625
  while ($voc = db_fetch_object($result)) {
463
    // If no node types are associated with a vocabulary, the LEFT JOIN
will
464
    // return a NULL value for type.
4655
    if (isset($voc->type)) {
4665
      $node_types[$voc->vid][$voc->type] = $voc->type;
4675
      unset($voc->type);
4685
      $voc->nodes = $node_types[$voc->vid];
4695
    }
4700
    elseif (!isset($voc->nodes)) {
4710
      $voc->nodes = array();
4720
    }
4735
    $vocabularies[$voc->vid] = $voc;
4745
  }
475
4765
  return $vocabularies;
4770
}
478
479
/**
480
 * Implementation of hook_form_alter().
481
 * Generate a form for selecting terms to associate with a node.
482
 * We check for taxonomy_override_selector before loading the full
483
 * vocabulary, so contrib modules can intercept before hook_form_alter
484
 *  and provide scalable alternatives.
485
 */
4862027
function taxonomy_form_alter(&$form, $form_state, $form_id) {
4871338
  if (isset($form['type']) && isset($form['#node']) &&
(!variable_get('taxonomy_override_selector', FALSE)) &&
$form['type']['#value'] . '_node_form' == $form_id) {
48883
    $node = $form['#node'];
489
49083
    if (!isset($node->taxonomy)) {
49136
      $terms = empty($node->nid) ? array() :
taxonomy_node_get_terms($node);
49236
    }
493
    else {
494
      // After preview the terms must be converted to objects.
49547
      if (isset($form_state['node_preview'])) {
4960
        $node->taxonomy = taxonomy_preview_terms($node);
4970
      }
49847
      $terms = $node->taxonomy;
499
    }
500
50183
    $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);
502
50383
    while ($vocabulary = db_fetch_object($c)) {
50421
      if ($vocabulary->tags) {
5054
        if (isset($form_state['node_preview'])) {
506
          // Typed string can be changed by the user before preview,
507
          // so we just insert the tags directly as provided in the form.
5080
          $typed_string = $node->taxonomy['tags'][$vocabulary->vid];
5090
        }
510
        else {
5114
          $typed_string = taxonomy_implode_tags($terms, $vocabulary->vid) .
(array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] :
NULL);
512
        }
5134
        if ($vocabulary->help) {
5144
          $help = $vocabulary->help;
5154
        }
516
        else {
5170
          $help = t('A comma-separated list of terms describing this
content. Example: funny, bungee jumping, "Company, Inc.".');
518
        }
5194
        $form['taxonomy']['tags'][$vocabulary->vid] = array('#type' =>
'textfield',
5204
          '#title' => $vocabulary->name,
5214
          '#description' => $help,
5224
          '#required' => $vocabulary->required,
5234
          '#default_value' => $typed_string,
5244
          '#autocomplete_path' => 'taxonomy/autocomplete/' .
$vocabulary->vid,
5254
          '#weight' => $vocabulary->weight,
5264
          '#maxlength' => 255,
527
        );
5284
      }
529
      else {
530
        // Extract terms belonging to the vocabulary in question.
53119
        $default_terms = array();
53219
        foreach ($terms as $term) {
533
          // Free tagging has no default terms and also no vid after
preview.
53418
          if (isset($term->vid) && $term->vid == $vocabulary->vid) {
53518
            $default_terms[$term->tid] = $term;
53618
          }
53718
        }
53819
        $form['taxonomy'][$vocabulary->vid] =
taxonomy_form($vocabulary->vid, array_keys($default_terms),
$vocabulary->help);
53919
        $form['taxonomy'][$vocabulary->vid]['#weight'] =
$vocabulary->weight;
54019
        $form['taxonomy'][$vocabulary->vid]['#required'] =
$vocabulary->required;
541
      }
54221
    }
54383
    if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
54421
      if (count($form['taxonomy']) > 1) {
545
        // Add fieldset only if form has more than 1 element.
5460
        $form['taxonomy'] += array(
5472
          '#type' => 'fieldset',
5482
          '#title' => t('Vocabularies'),
5492
          '#collapsible' => TRUE,
5502
          '#collapsed' => FALSE,
551
        );
5522
      }
55321
      $form['taxonomy']['#weight'] = -3;
55421
      $form['taxonomy']['#tree'] = TRUE;
55521
    }
55683
  }
5571338
}
558
559
/**
560
 * Helper function to convert terms after a preview.
561
 *
562
 * After preview the tags are an array instead of proper objects. This
function
563
 * converts them back to objects with the exception of 'free tagging'
terms,
564
 * because new tags can be added by the user before preview and those do
not
565
 * yet exist in the database. We therefore save those tags as a string so
566
 * we can fill the form again after the preview.
567
 */
5682027
function taxonomy_preview_terms($node) {
5692
  $taxonomy = array();
5702
  if (isset($node->taxonomy)) {
5710
    foreach ($node->taxonomy as $key => $term) {
5720
      unset($node->taxonomy[$key]);
573
      // A 'Multiple select' and a 'Free tagging' field returns an array.
5740
      if (is_array($term)) {
5750
        foreach ($term as $tid) {
5760
          if ($key == 'tags') {
577
            // Free tagging; the values will be saved for later as strings
578
            // instead of objects to fill the form again.
5790
            $taxonomy['tags'] = $term;
5800
          }
581
          else {
5820
            $taxonomy[$tid] = taxonomy_get_term($tid);
583
          }
5840
        }
5850
      }
586
      // A 'Single select' field returns the term id.
5870
      elseif ($term) {
5880
        $taxonomy[$term] = taxonomy_get_term($term);
5890
      }
5900
    }
5910
  }
5922
  return $taxonomy;
5930
}
594
595
/**
596
 * Find all terms associated with the given node, within one vocabulary.
597
 */
5982027
function taxonomy_node_get_terms_by_vocabulary($node, $vid, $key = 'tid')
{
59961
  $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);
60061
  $terms = array();
60161
  while ($term = db_fetch_object($result)) {
60261
    $terms[$term->$key] = $term;
60361
  }
60461
  return $terms;
6050
}
606
607
/**
608
 * Find all terms associated with the given node, ordered by vocabulary and
term weight.
609
 */
6102027
function taxonomy_node_get_terms($node, $key = 'tid') {
611403
  static $terms;
612
613403
  if (!isset($terms[$node->vid][$key])) {
614403
    $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);
615403
    $terms[$node->vid][$key] = array();
616403
    while ($term = db_fetch_object($result)) {
61782
      $terms[$node->vid][$key][$term->$key] = $term;
61882
    }
619403
  }
620403
  return $terms[$node->vid][$key];
6210
}
622
623
/**
624
 * Make sure incoming vids are free tagging enabled.
625
 */
6262027
function taxonomy_node_validate(&$node) {
62775
  if (!empty($node->taxonomy)) {
62818
    $terms = $node->taxonomy;
62918
    if (!empty($terms['tags'])) {
6304
      foreach ($terms['tags'] as $vid => $vid_value) {
6314
        $vocabulary = taxonomy_vocabulary_load($vid);
6324
        if (empty($vocabulary->tags)) {
633
          // see form_get_error $key = implode('][',
$element['#parents']);
634
          // on why this is the key
6350
          form_set_error("taxonomy][tags][$vid", t('The %name vocabulary
can not be modified in this way.', array('%name' => $vocabulary->name)));
6360
        }
6374
      }
6384
    }
63918
  }
64075
}
641
642
/**
643
 * Save term associations for a given node.
644
 */
6452027
function taxonomy_node_save($node, $terms) {
646
64717
  taxonomy_node_delete_revision($node);
648
649
  // Free tagging vocabularies do not send their tids in the form,
650
  // so we'll detect them here and process them independently.
65117
  if (isset($terms['tags'])) {
6524
    $typed_input = $terms['tags'];
6534
    unset($terms['tags']);
654
6554
    foreach ($typed_input as $vid => $vid_value) {
6564
      $typed_terms = drupal_explode_tags($vid_value);
657
6584
      $inserted = array();
6594
      foreach ($typed_terms as $typed_term) {
660
        // See if the term exists in the chosen vocabulary
661
        // and return the tid; otherwise, add a new record.
6620
        $possibilities = taxonomy_get_term_by_name($typed_term);
6630
        $typed_term_tid = NULL; // tid match, if any.
6640
        foreach ($possibilities as $possibility) {
6650
          if ($possibility->vid == $vid) {
6660
            $typed_term_tid = $possibility->tid;
6670
          }
6680
        }
669
6700
        if (!$typed_term_tid) {
6710
          $edit = array('vid' => $vid, 'name' => $typed_term);
6720
          $status = taxonomy_save_term($edit);
6730
          $typed_term_tid = $edit['tid'];
6740
        }
675
676
        // Defend against duplicate, differently cased tags
6770
        if (!isset($inserted[$typed_term_tid])) {
6780
          db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d,
%d)', $node->nid, $node->vid, $typed_term_tid);
6790
          $inserted[$typed_term_tid] = TRUE;
6800
        }
6810
      }
6824
    }
6834
  }
684
68517
  if (is_array($terms)) {
68617
    foreach ($terms as $term) {
68715
      if (is_array($term)) {
6882
        foreach ($term as $tid) {
6892
          if ($tid) {
6902
            db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d,
%d, %d)', $node->nid, $node->vid, $tid);
6912
          }
6922
        }
6932
      }
69413
      else if (is_object($term)) {
6950
        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d,
%d)', $node->nid, $node->vid, $term->tid);
6960
      }
69713
      else if ($term) {
69813
        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d,
%d)', $node->nid, $node->vid, $term);
69913
      }
70015
    }
70117
  }
70217
}
703
704
/**
705
 * Remove associations of a node to its terms.
706
 */
7072027
function taxonomy_node_delete($node) {
70813
  db_query('DELETE FROM {term_node} WHERE nid = %d', $node->nid);
70913
}
710
711
/**
712
 * Remove associations of a node to its terms.
713
 */
7142027
function taxonomy_node_delete_revision($node) {
71518
  db_query('DELETE FROM {term_node} WHERE vid = %d', $node->vid);
71618
}
717
718
/**
719
 * Implementation of hook_node_type().
720
 */
7212027
function taxonomy_node_type($op, $info) {
72263
  if ($op == 'update' && !empty($info->old_type) && $info->type !=
$info->old_type) {
7230
    db_query("UPDATE {vocabulary_node_types} SET type = '%s' WHERE type =
'%s'", $info->type, $info->old_type);
7240
  }
72563
  elseif ($op == 'delete') {
7260
    db_query("DELETE FROM {vocabulary_node_types} WHERE type = '%s'",
$info->type);
7270
  }
72863
}
729
730
/**
731
 * Find all term objects related to a given term ID.
732
 */
7332027
function taxonomy_get_related($tid, $key = 'tid') {
7344
  if ($tid) {
7351
    $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);
7361
    $related = array();
7371
    while ($term = db_fetch_object($result)) {
7381
      $related[$term->$key] = $term;
7391
    }
7401
    return $related;
7410
  }
742
  else {
7433
    return array();
744
  }
7450
}
746
747
/**
748
 * Find all parents of a given term ID.
749
 */
7502027
function taxonomy_get_parents($tid, $key = 'tid') {
75178
  if ($tid) {
75269
    $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);
75369
    $parents = array();
75469
    while ($parent = db_fetch_object($result)) {
75560
      $parents[$parent->$key] = $parent;
75660
    }
75769
    return $parents;
7580
  }
759
  else {
7609
    return array();
761
  }
7620
}
763
764
/**
765
 * Find all ancestors of a given term ID.
766
 */
7672027
function taxonomy_get_parents_all($tid) {
76867
  $parents = array();
76967
  if ($tid) {
77067
    $parents[] = taxonomy_get_term($tid);
77167
    $n = 0;
77267
    while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
77359
      $parents = array_merge($parents, $parent);
77459
      $n++;
77559
    }
77667
  }
77767
  return $parents;
7780
}
779
780
/**
781
 * Find all children of a term ID.
782
 */
7832027
function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
7842
  if ($vid) {
7850
    $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);
7860
  }
787
  else {
7882
    $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);
789
  }
7902
  $children = array();
7912
  while ($term = db_fetch_object($result)) {
7921
    $children[$term->$key] = $term;
7931
  }
7942
  return $children;
7950
}
796
797
/**
798
 * Create a hierarchical representation of a vocabulary.
799
 *
800
 * @param $vid
801
 *   Which vocabulary to generate the tree for.
802
 *
803
 * @param $parent
804
 *   The term ID under which to generate the tree. If 0, generate the tree
805
 *   for the entire vocabulary.
806
 *
807
 * @param $depth
808
 *   Internal use only.
809
 *
810
 * @param $max_depth
811
 *   The number of levels of the tree to return. Leave NULL to return all
levels.
812
 *
813
 * @return
814
 *   An array of all term objects in the tree. Each term object is
extended
815
 *   to have "depth" and "parents" attributes in addition to its normal
ones.
816
 *   Results are statically cached.
817
 */
8182027
function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth =
NULL) {
819111
  static $children, $parents, $terms;
820
821111
  $depth++;
822
823
  // We cache trees, so it's not CPU-intensive to call get_tree() on a
term
824
  // and its children, too.
825111
  if (!isset($children[$vid])) {
826111
    $children[$vid] = array();
827
828111
    $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);
829111
    while ($term = db_fetch_object($result)) {
830107
      $children[$vid][$term->parent][] = $term->tid;
831107
      $parents[$vid][$term->tid][] = $term->parent;
832107
      $terms[$vid][$term->tid] = $term;
833107
    }
834111
  }
835
836111
  $max_depth = (is_null($max_depth)) ? count($children[$vid]) :
$max_depth;
837111
  $tree = array();
838111
  if (!empty($children[$vid][$parent])) {
83999
    foreach ($children[$vid][$parent] as $child) {
84099
      if ($max_depth > $depth) {
84199
        $term = clone $terms[$vid][$child];
84299
        $term->depth = $depth;
843
        // The "parent" attribute is not useful, as it would show one
parent only.
84499
        unset($term->parent);
84599
        $term->parents = $parents[$vid][$child];
84699
        $tree[] = $term;
847
84899
        if (!empty($children[$vid][$child])) {
84988
          $tree = array_merge($tree, taxonomy_get_tree($vid, $child,
$depth, $max_depth));
85088
        }
85199
      }
85299
    }
85399
  }
854
855111
  return $tree;
8560
}
857
858
/**
859
 * Return an array of synonyms of the given term ID.
860
 */
8612027
function taxonomy_get_synonyms($tid) {
8624
  if ($tid) {
8631
    $synonyms = array();
8641
    $result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d',
$tid);
8651
    while ($synonym = db_fetch_array($result)) {
8661
      $synonyms[] = $synonym['name'];
8671
    }
8681
    return $synonyms;
8690
  }
870
  else {
8713
    return array();
872
  }
8730
}
874
875
/**
876
 * Return the term object that has the given string as a synonym.
877
 */
8782027
function taxonomy_get_synonym_root($synonym) {
8790
  return db_fetch_object(db_query("SELECT * FROM {term_synonym} s,
{term_data} t WHERE t.tid = s.tid AND s.name = '%s'", $synonym));
8800
}
881
882
/**
883
 * Count the number of published nodes classified by a term.
884
 *
885
 * @param $tid
886
 *   The term's ID
887
 *
888
 * @param $type
889
 *   The $node->type. If given, taxonomy_term_count_nodes only counts
890
 *   nodes of $type that are classified with the term $tid.
891
 *
892
 * @return int
893
 *   An integer representing a number of nodes.
894
 *   Results are statically cached.
895
 */
8962027
function taxonomy_term_count_nodes($tid, $type = 0) {
8970
  static $count;
898
8990
  if (!isset($count[$type])) {
900
    // $type == 0 always evaluates TRUE if $type is a string
9010
    if (is_numeric($type)) {
9020
      $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'));
9030
    }
904
    else {
9050
      $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);
906
    }
9070
    $count[$type] = array();
9080
    while ($term = db_fetch_object($result)) {
9090
      $count[$type][$term->tid] = $term->c;
9100
    }
9110
  }
9120
  $children_count = 0;
9130
  foreach (_taxonomy_term_children($tid) as $c) {
9140
    $children_count += taxonomy_term_count_nodes($c, $type);
9150
  }
9160
  return $children_count + (isset($count[$type][$tid]) ?
$count[$type][$tid] : 0);
9170
}
918
919
/**
920
 * Helper for taxonomy_term_count_nodes(). Used to find out
921
 * which terms are children of a parent term.
922
 *
923
 * @param $tid
924
 *   The parent term's ID
925
 *
926
 * @return array
927
 *   An array of term IDs representing the children of $tid.
928
 *   Results are statically cached.
929
 *
930
 */
9312027
function _taxonomy_term_children($tid) {
9320
  static $children;
933
9340
  if (!isset($children)) {
9350
    $result = db_query('SELECT tid, parent FROM {term_hierarchy}');
9360
    while ($term = db_fetch_object($result)) {
9370
      $children[$term->parent][] = $term->tid;
9380
    }
9390
  }
9400
  return isset($children[$tid]) ? $children[$tid] : array();
9410
}
942
943
/**
944
 * Try to map a string to an existing term, as for glossary use.
945
 *
946
 * Provides a case-insensitive and trimmed mapping, to maximize the
947
 * likelihood of a successful match.
948
 *
949
 * @param name
950
 *   Name of the term to search for.
951
 *
952
 * @return
953
 *   An array of matching term objects.
954
 */
9552027
function taxonomy_get_term_by_name($name) {
9562
  $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));
9572
  $result = array();
9582
  while ($term = db_fetch_object($db_result)) {
9592
    $result[] = $term;
9602
  }
961
9622
  return $result;
9630
}
964
965
/**
966
 * Return the vocabulary object matching a vocabulary ID.
967
 *
968
 * @param $vid
969
 *   The vocabulary's ID
970
 *
971
 * @return
972
 *   The vocabulary object with all of its metadata, if exists, FALSE
otherwise.
973
 *   Results are statically cached.
974
 */
9752027
function taxonomy_vocabulary_load($vid) {
976128
  static $vocabularies = array();
977
978128
  if (!isset($vocabularies[$vid])) {
979
    // Initialize so if this vocabulary does not exist, we have
980
    // that cached, and we will not try to load this later.
981128
    $vocabularies[$vid] = FALSE;
982
    // Try to load the data and fill up the object.
983128
    $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);
984128
    $node_types = array();
985128
    while ($voc = db_fetch_object($result)) {
986128
      if (!empty($voc->type)) {
987127
        $node_types[$voc->type] = $voc->type;
988127
      }
989128
      unset($voc->type);
990128
      $voc->nodes = $node_types;
991128
      $vocabularies[$vid] = $voc;
992128
    }
993128
  }
994
995
  // Return FALSE if this vocabulary does not exist.
996128
  return !empty($vocabularies[$vid]) ? $vocabularies[$vid] : FALSE;
9970
}
998
999
/**
1000
 * Return the term object matching a term ID.
1001
 *
1002
 * @param $tid
1003
 *   A term's ID
1004
 *
1005
 * @return Object
1006
 *   A term object. Results are statically cached.
1007
 */
10082027
function taxonomy_get_term($tid) {
100971
  static $terms = array();
1010
101171
  if (!isset($terms[$tid])) {
101271
    $terms[$tid] = db_fetch_object(db_query('SELECT * FROM {term_data}
WHERE tid = %d', $tid));
101371
  }
1014
101571
  return $terms[$tid];
10160
}
1017
10182027
function _taxonomy_term_select($title, $name, $value, $vocabulary_id,
$description, $multiple, $blank, $exclude = array()) {
101922
  $tree = taxonomy_get_tree($vocabulary_id);
102022
  $options = array();
1021
102222
  if ($blank) {
102322
    $options[''] = $blank;
102422
  }
102522
  if ($tree) {
102620
    foreach ($tree as $term) {
102720
      if (!in_array($term->tid, $exclude)) {
102820
        $choice = new stdClass();
102920
        $choice->option = array($term->tid => str_repeat('-', $term->depth)
. $term->name);
103020
        $options[] = $choice;
103120
      }
103220
    }
103320
  }
1034
103522
  return array('#type' => 'select',
103622
    '#title' => $title,
103722
    '#default_value' => $value,
103822
    '#options' => $options,
103922
    '#description' => $description,
104022
    '#multiple' => $multiple,
104122
    '#size' => $multiple ? min(9, count($options)) : 0,
104222
    '#weight' => -15,
104322
    '#theme' => 'taxonomy_term_select',
104422
  );
10450
}
1046
1047
/**
1048
 * Format the selection field for choosing terms
1049
 * (by deafult the default selection field is used).
1050
 *
1051
 * @ingroup themeable
1052
 */
10532027
function theme_taxonomy_term_select($element) {
105423
  return theme('select', $element);
10550
}
1056
1057
/**
1058
 * Finds all nodes that match selected taxonomy conditions.
1059
 *
1060
 * @param $tids
1061
 *   An array of term IDs to match.
1062
 * @param $operator
1063
 *   How to interpret multiple IDs in the array. Can be "or" or "and".
1064
 * @param $depth
1065
 *   How many levels deep to traverse the taxonomy tree. Can be a
nonnegative
1066
 *   integer or "all".
1067
 * @param $pager
1068
 *   Whether the nodes are to be used with a pager (the case on most
Drupal
1069
 *   pages) or not (in an XML feed, for example).
1070
 * @param $order
1071
 *   The order clause for the query that retrieve the nodes.
1072
 * @return
1073
 *   A resource identifier pointing to the query results.
1074
 */
10752027
function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth =
0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC') {
10760
  if (count($tids) > 0) {
1077
    // For each term ID, generate an array of descendant term IDs to the
right depth.
10780
    $descendant_tids = array();
10790
    if ($depth === 'all') {
10800
      $depth = NULL;
10810
    }
10820
    foreach ($tids as $index => $tid) {
10830
      $term = taxonomy_get_term($tid);
10840
      $tree = taxonomy_get_tree($term->vid, $tid, -1, $depth);
10850
      $descendant_tids[] = array_merge(array($tid),
array_map('_taxonomy_get_tid_from_term', $tree));
10860
    }
1087
10880
    if ($operator == 'or') {
10890
      $args = call_user_func_array('array_merge', $descendant_tids);
10900
      $placeholders = db_placeholders($args, 'int');
10910
      $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;
10920
      $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';
10930
    }
1094
    else {
10950
      $joins = '';
10960
      $wheres = '';
10970
      $args = array();
10980
      foreach ($descendant_tids as $index => $tids) {
10990
        $joins .= ' INNER JOIN {term_node} tn' . $index . ' ON n.vid = tn'
. $index . '.vid';
11000
        $wheres .= ' AND tn' . $index . '.tid IN (' .
db_placeholders($tids, 'int') . ')';
11010
        $args = array_merge($args, $tids);
11020
      }
11030
      $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM
{node} n ' . $joins . ' WHERE n.status = 1 ' . $wheres . ' ORDER BY ' .
$order;
11040
      $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n ' . $joins
. ' WHERE n.status = 1 ' . $wheres;
1105
    }
11060
    $sql = db_rewrite_sql($sql);
11070
    $sql_count = db_rewrite_sql($sql_count);
11080
    if ($pager) {
11090
      $result = pager_query($sql, variable_get('default_nodes_main', 10),
0, $sql_count, $args);
11100
    }
1111
    else {
11120
      $result = db_query_range($sql, $args, 0,
variable_get('feed_default_items', 10));
1113
    }
11140
  }
1115
11160
  return $result;
11170
}
1118
1119
/**
1120
 * Accepts the result of a pager_query() call, such as that performed by
1121
 * taxonomy_select_nodes(), and formats each node along with a pager.
1122
 */
11232027
function taxonomy_render_nodes($result) {
11240
  $output = '';
11250
  $has_rows = FALSE;
11260
  while ($node = db_fetch_object($result)) {
11270
    $output .= node_view(node_load($node->nid), 1);
11280
    $has_rows = TRUE;
11290
  }
11300
  if ($has_rows) {
11310
    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10),
0);
11320
  }
1133
  else {
11340
    $output .= '<p>' . t('There are currently no posts in this category.')
. '</p>';
1135
  }
11360
  return $output;
11370
}
1138
1139
/**
1140
 * Implementation of hook_nodeapi().
1141
 */
11422027
function taxonomy_nodeapi($node, $op, $arg = 0) {
1143
  switch ($op) {
1144496
    case 'load':
1145403
      $output['taxonomy'] = taxonomy_node_get_terms($node);
1146403
      return $output;
1147
1148415
    case 'insert':
114949
      if (!empty($node->taxonomy)) {
115011
        taxonomy_node_save($node, $node->taxonomy);
115111
      }
115249
      break;
1153
1154415
    case 'update':
115531
      if (!empty($node->taxonomy)) {
11566
        taxonomy_node_save($node, $node->taxonomy);
11576
      }
115831
      break;
1159
1160415
    case 'delete':
116113
      taxonomy_node_delete($node);
116213
      break;
1163
1164403
    case 'delete revision':
11651
      taxonomy_node_delete_revision($node);
11661
      break;
1167
1168402
    case 'validate':
116975
      taxonomy_node_validate($node);
117075
      break;
1171
1172399
    case 'rss item':
11730
      return taxonomy_rss_item($node);
1174
1175399
    case 'update index':
11761
      return taxonomy_node_update_index($node);
11770
  }
1178415
}
1179
1180
/**
1181
 * Implementation of hook_nodeapi('update_index').
1182
 */
11832027
function taxonomy_node_update_index(&$node) {
11841
  $output = array();
11851
  foreach ($node->taxonomy as $term) {
11860
    $output[] = $term->name;
11870
  }
11881
  if (count($output)) {
11890
    return '<strong>(' . implode(', ', $output) . ')</strong>';
11900
  }
11911
}
1192
1193
/**
1194
 * Parses a comma or plus separated string of term IDs.
1195
 *
1196
 * @param $str_tids
1197
 *   A string of term IDs, separated by plus or comma.
1198
 *   comma (,) means AND
1199
 *   plus (+) means OR
1200
 *
1201
 * @return an associative array with an operator key (either 'and'
1202
 *   or 'or') and a tid key containing an array of the term ids.
1203
 */
12042027
function taxonomy_terms_parse_string($str_tids) {
12050
  $terms = array('operator' => '', 'tids' => array());
12060
  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
12070
    $terms['operator'] = 'or';
1208
    // The '+' character in a query string may be parsed as ' '.
12090
    $terms['tids'] = preg_split('/[+ ]/', $str_tids);
12100
  }
12110
  else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
12120
    $terms['operator'] = 'and';
12130
    $terms['tids'] = explode(',', $str_tids);
12140
  }
12150
  return $terms;
12160
}
1217
1218
/**
1219
 * Provides category information for RSS feeds.
1220
 */
12212027
function taxonomy_rss_item($node) {
12220
  $output = array();
12230
  foreach ($node->taxonomy as $term) {
12240
    $output[] = array('key'   => 'category',
12250
                      'value' => check_plain($term->name),
12260
                      'attributes' => array('domain' =>
url('taxonomy/term/' . $term->tid, array('absolute' => TRUE))));
12270
  }
12280
  return $output;
12290
}
1230
1231
/**
1232
 * Implementation of hook_help().
1233
 */
12342027
function taxonomy_help($path, $arg) {
1235
  switch ($path) {
12361491
    case 'admin/help#taxonomy':
123719
      $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>';
123819
      $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>';
123919
      $output .= '<p>' . t('Type of Meal: <em>Appetizer, Main Course,
Salad, Dessert</em>') . '</p>';
124019
      $output .= '<p>' . t('Preparation Time: <em>0-30mins, 30-60mins, 1-2
hrs, 2hrs+</em>') . '</p>';
124119
      $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>';
124219
      $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>';
124319
      $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>';
124419
      $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>';
124519
      return $output;
12461489
    case 'admin/content/taxonomy':
12473
      $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>';
12483
      $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>';
12493
      return $output;
12501486
    case 'admin/content/taxonomy/%':
12510
      $vocabulary = taxonomy_vocabulary_load($arg[3]);
12520
      if ($vocabulary->tags) {
12530
        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>';
12540
      }
12550
      switch ($vocabulary->hierarchy) {
12560
        case 0:
12570
          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>';
12580
        case 1:
12590
          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>';
12600
        case 2:
12610
          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>';
12620
      }
12631486
    case 'admin/content/taxonomy/add/vocabulary':
12641
      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>';
12650
  }
12661485
}
1267
1268
/**
1269
 * Helper function for array_map purposes.
1270
 */
12712027
function _taxonomy_get_tid_from_term($term) {
12720
  return $term->tid;
12730
}
1274
1275
/**
1276
 * Implode a list of tags of a certain vocabulary into a string.
1277
 */
12782027
function taxonomy_implode_tags($tags, $vid = NULL) {
12794
  $typed_tags = array();
12804
  foreach ($tags as $tag) {
1281
    // Extract terms belonging to the vocabulary in question.
12821
    if (is_null($vid) || $tag->vid == $vid) {
1283
1284
      // Commas and quotes in tag names are special cases, so encode 'em.
12850
      if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !==
FALSE) {
12860
        $tag->name = '"' . str_replace('"', '""', $tag->name) . '"';
12870
      }
1288
12890
      $typed_tags[] = $tag->name;
12900
    }
12911
  }
12924
  return implode(', ', $typed_tags);
12930
}
1294
1295
/**
1296
 * Implementation of hook_hook_info().
1297
 */
12982027
function taxonomy_hook_info() {
1299
  return array(
1300
    'taxonomy' => array(
1301
      'taxonomy' => array(
1302
        'insert' => array(
130355
          'runs when' => t('After saving a new term to the database'),
130455
        ),
1305
        'update' => array(
130655
          'runs when' => t('After saving an updated term to the
database'),
130755
        ),
1308
        'delete' => array(
130955
          'runs when' => t('After deleting a term')
131055
        ),
131155
      ),
131255
    ),
131355
  );
13140
}
13152027