taxonomy.module 57 KB
Newer Older
1
<?php
2

3 4 5 6 7
/**
 * @file
 * Enables the organization of content into categories.
 */

8
use Drupal\node\Node;
9 10
use Drupal\taxonomy\Term;
use Drupal\taxonomy\Vocabulary;
11

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/**
 * Denotes that no term in the vocabulary has a parent.
 */
const TAXONOMY_HIERARCHY_DISABLED = 0;

/**
 * Denotes that one or more terms in the vocabulary has a single parent.
 */
const TAXONOMY_HIERARCHY_SINGLE = 1;

/**
 * Denotes that one or more terms in the vocabulary have multiple parents.
 */
const TAXONOMY_HIERARCHY_MULTIPLE = 2;

27 28 29 30 31 32 33 34
/**
 * Users can create new terms in a free-tagging vocabulary when
 * submitting a taxonomy_autocomplete_widget. We store a term object
 * whose tid is 'autocreate' as a field data item during widget
 * validation and then actually create the term if/when that field
 * data item makes it to taxonomy_field_insert/update().
 */

35
/**
36
 * Implements hook_help().
37 38 39 40 41 42
 */
function taxonomy_help($path, $arg) {
  switch ($path) {
    case 'admin/help#taxonomy':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
43
      $output .= '<p>' . t('The Taxonomy module allows you to classify the content of your website. To classify content, you define <em>vocabularies</em> that contain related <em>terms</em>, and then assign the vocabularies to content types. For more information, see the online handbook entry for the <a href="@taxonomy">Taxonomy module</a>.', array('@taxonomy' => 'http://drupal.org/documentation/modules/taxonomy')) . '</p>';
44 45 46
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating vocabularies') . '</dt>';
47
      $output .= '<dd>' . t('Users with sufficient <a href="@perm">permissions</a> can create <em>vocabularies</em> and <em>terms</em> through the <a href="@taxo">Taxonomy page</a>. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A <em>controlled vocabulary</em> classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment'=>'module-taxonomy'))));
48
      $output .= '<ul><li>' . t('<em>vocabulary</em>: Music') . '</li>';
49 50 51 52 53 54 55 56 57
      $output .= '<ul><li>' . t('<em>term</em>: Jazz') . '</li>';
      $output .= '<ul><li>' . t('<em>sub-term</em>: Swing') . '</li>';
      $output .= '<li>' . t('<em>sub-term</em>: Fusion') . '</li></ul></ul>';
      $output .= '<ul><li>' . t('<em>term</em>: Rock') . '</li>';
      $output .= '<ul><li>' . t('<em>sub-term</em>: Country rock') . '</li>';
      $output .= '<li>' . t('<em>sub-term</em>: Hard rock') . '</li></ul></ul></ul>';
      $output .= t('You can assign a sub-term to multiple parent terms. For example, <em>fusion</em> can be assigned to both <em>rock</em> and <em>jazz</em>.') . '</dd>';
      $output .= '<dd>' . t('Terms in a <em>free-tagging vocabulary</em> can be built gradually as you create or edit content. This is often done used for blogs or photo management applications.') . '</dd>';
      $output .= '<dt>' . t('Assigning vocabularies to content types') . '</dt>';
58
      $output .= '<dd>' . t('Before you can use a new vocabulary to classify your content, a new Taxonomy term field must be added to a <a href="@ctedit">content type</a> on its <em>manage fields</em> page. When adding a taxonomy field, you choose a <em>widget</em> to use to enter the taxonomy information on the content editing page: a select list, checkboxes, radio buttons, or an auto-complete field (to build a free-tagging vocabulary). After choosing the field type and widget, on the subsequent <em>field settings</em> page you can choose the desired vocabulary, whether one or multiple terms can be chosen from the vocabulary, and other settings. The same vocabulary can be added to multiple content types, by using the "Re-use existing field" section on the manage fields page.', array('@ctedit' => url('admin/structure/types'))) . '</dd>';
59
      $output .= '<dt>' . t('Classifying content') . '</dt>';
60
      $output .= '<dd>' . t('After the vocabulary is assigned to the content type, you can start classifying content. The field with terms will appear on the content editing screen when you edit or <a href="@addnode">add new content</a>.', array('@addnode' => url('node/add'))) . '</dd>';
61 62 63 64 65 66 67
      $output .= '<dt>' . t('Viewing listings and RSS feeds by term') . '</dt>';
      $output .= '<dd>' . t("Each taxonomy term automatically provides a page listing content that has its classification, and a corresponding RSS feed. For example, if the taxonomy term <em>country rock</em> has the ID 123 (you can see this by looking at the URL when hovering on the linked term, which you can click to navigate to the listing page), then you will find this list at the path <em>taxonomy/term/123</em>. The RSS feed will use the path <em>taxonomy/term/123/feed</em> (the RSS icon for this term's listing will automatically display in your browser's address bar when viewing the listing page).") . '</dd>';
      $output .= '<dt>' . t('Extending Taxonomy module') . '</dt>';
      $output .= '<dd>' . t('There are <a href="@taxcontrib">many contributed modules</a> that extend the behavior of the Taxonomy module for both display and organization of terms.', array('@taxcontrib' => 'http://drupal.org/project/modules?filters=tid:71&solrsort=sis_project_release_usage%20desc'));
      $output .= '</dl>';
      return $output;
    case 'admin/structure/taxonomy':
68
      $output = '<p>' . t('Taxonomy is for categorizing content. Terms are grouped into vocabularies. For example, a vocabulary called "Fruit" would contain the terms "Apple" and "Banana".') . '</p>';
69 70
      return $output;
    case 'admin/structure/taxonomy/%':
71
      $vocabulary = taxonomy_vocabulary_machine_name_load($arg[3]);
72
      switch ($vocabulary->hierarchy) {
73
        case TAXONOMY_HIERARCHY_DISABLED:
74
          return '<p>' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
75
        case TAXONOMY_HIERARCHY_SINGLE:
76
          return '<p>' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
77
        case TAXONOMY_HIERARCHY_MULTIPLE:
78
          return '<p>' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents 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>';
79 80 81 82
      }
  }
}

Dries's avatar
Dries committed
83
/**
84
 * Implements hook_permission().
Dries's avatar
Dries committed
85
 */
86
function taxonomy_permission() {
87
  $permissions = array(
88
    'administer taxonomy' => array(
89
      'title' => t('Administer vocabularies and terms'),
90
    ),
91
  );
92
  foreach (taxonomy_vocabulary_load_multiple(FALSE) as $vocabulary) {
93 94 95 96 97 98 99
    $permissions += array(
      'edit terms in ' . $vocabulary->vid => array(
        'title' => t('Edit terms in %vocabulary', array('%vocabulary' => $vocabulary->name)),
      ),
    );
    $permissions += array(
       'delete terms in ' . $vocabulary->vid => array(
100
         'title' => t('Delete terms from %vocabulary', array('%vocabulary' => $vocabulary->name)),
101 102 103 104
      ),
    );
  }
  return $permissions;
Kjartan's avatar
Kjartan committed
105
}
106

107
/**
108
 * Implements hook_entity_info().
109
 */
110
function taxonomy_entity_info() {
111 112
  $return = array(
    'taxonomy_term' => array(
113
      'label' => t('Taxonomy term'),
114 115
      'entity class' => 'Drupal\taxonomy\Term',
      'controller class' => 'Drupal\taxonomy\TermStorageController',
116
      'base table' => 'taxonomy_term_data',
117
      'uri callback' => 'taxonomy_term_uri',
118
      'fieldable' => TRUE,
119
      'entity keys' => array(
120 121
        'id' => 'tid',
        'bundle' => 'vocabulary_machine_name',
122
        'label' => 'name',
123
        'uuid' => 'uuid',
124 125 126 127 128
      ),
      'bundle keys' => array(
        'bundle' => 'machine_name',
      ),
      'bundles' => array(),
129 130 131 132
      'view modes' => array(
        // @todo View mode for display as a field (when attached to nodes etc).
        'full' => array(
          'label' => t('Taxonomy term page'),
133
          'custom settings' => FALSE,
134 135
        ),
      ),
136 137
    ),
  );
138 139
  foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
    $return['taxonomy_term']['bundles'][$machine_name] = array(
140 141
      'label' => $vocabulary->name,
      'admin' => array(
142 143
        'path' => 'admin/structure/taxonomy/%taxonomy_vocabulary_machine_name',
        'real path' => 'admin/structure/taxonomy/' . $machine_name,
144 145 146 147 148
        'bundle argument' => 3,
        'access arguments' => array('administer taxonomy'),
      ),
    );
  }
149 150
  $return['taxonomy_vocabulary'] = array(
    'label' => t('Taxonomy vocabulary'),
151 152
    'entity class' => 'Drupal\taxonomy\Vocabulary',
    'controller class' => 'Drupal\taxonomy\VocabularyStorageController',
153
    'base table' => 'taxonomy_vocabulary',
154
    'entity keys' => array(
155
      'id' => 'vid',
156
      'label' => 'name',
157 158 159 160
    ),
    'fieldable' => FALSE,
  );

161 162 163
  return $return;
}

164
/**
165
 * Entity uri callback.
166
 */
167 168 169 170
function taxonomy_term_uri($term) {
  return array(
    'path' => 'taxonomy/term/' . $term->tid,
  );
171 172
}

173 174 175 176 177
/**
 * Implements hook_field_extra_fields().
 */
function taxonomy_field_extra_fields() {
  $return = array();
178 179 180
  $info = entity_get_info('taxonomy_term');
  foreach (array_keys($info['bundles']) as $bundle) {
    $return['taxonomy_term'][$bundle] = array(
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
      'form' => array(
        'name' => array(
          'label' => t('Name'),
          'description' => t('Term name textfield'),
          'weight' => -5,
        ),
        'description' => array(
          'label' => t('Description'),
          'description' => t('Term description textarea'),
          'weight' => 0,
        ),
      ),
      'display' => array(
        'description' => array(
          'label' => t('Description'),
          'description' => t('Term description'),
          'weight' => 0,
        ),
199 200 201 202 203 204 205
      ),
    );
  }

  return $return;
}

206 207 208 209 210 211 212
/**
 * Return nodes attached to a term across all field instances.
 *
 * This function requires taxonomy module to be maintaining its own tables,
 * and will return an empty array if it is not. If using other field storage
 * methods alternatives methods for listing terms will need to be used.
 *
213 214
 * @param $tid
 *   The term ID.
215 216
 * @param $pager
 *   Boolean to indicate whether a pager should be used.
217 218 219
 * @param $limit
 *   Integer. The maximum number of nodes to find.
 *   Set to FALSE for no limit.
220
 * @param $order
221 222 223 224 225
 *   An array of fields and directions.
 *
 * @return
 *   An array of nids matching the query.
 */
226
function taxonomy_select_nodes($tid, $pager = TRUE, $limit = FALSE, $order = array('t.sticky' => 'DESC', 't.created' => 'DESC')) {
227 228 229 230 231
  if (!variable_get('taxonomy_maintain_index_table', TRUE)) {
    return array();
  }
  $query = db_select('taxonomy_index', 't');
  $query->addTag('node_access');
232
  $query->addMetaData('base_table', 'taxonomy_index');
233
  $query->condition('tid', $tid);
234 235 236 237
  if ($pager) {
    $count_query = clone $query;
    $count_query->addExpression('COUNT(t.nid)');

238
    $query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender');
239 240 241
    if ($limit !== FALSE) {
      $query = $query->limit($limit);
    }
242 243 244
    $query->setCountQuery($count_query);
  }
  else {
245 246 247
    if ($limit !== FALSE) {
      $query->range(0, $limit);
    }
248 249 250 251 252 253 254 255 256 257 258 259 260
  }
  $query->addField('t', 'nid');
  $query->addField('t', 'tid');
  foreach ($order as $field => $direction) {
    $query->orderBy($field, $direction);
    // ORDER BY fields need to be loaded too, assume they are in the form
    // table_alias.name
    list($table_alias, $name) = explode('.', $field);
    $query->addField($table_alias, $name);
  }
  return $query->execute()->fetchCol();
}

261
/**
262
 * Implements hook_theme().
263 264 265
 */
function taxonomy_theme() {
  return array(
266
    'taxonomy_overview_vocabularies' => array(
267
      'render element' => 'form',
268 269
    ),
    'taxonomy_overview_terms' => array(
270
      'render element' => 'form',
271
    ),
272 273 274 275
    'taxonomy_term' => array(
      'render element' => 'elements',
      'template' => 'taxonomy-term',
    ),
276 277 278
  );
}

Dries's avatar
Dries committed
279
/**
280
 * Implements hook_menu().
Dries's avatar
Dries committed
281
 */
282
function taxonomy_menu() {
283
  $items['admin/structure/taxonomy'] = array(
284 285
    'title' => 'Taxonomy',
    'description' => 'Manage tagging, categorization, and classification of your content.',
286 287
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_overview_vocabularies'),
288
    'access arguments' => array('administer taxonomy'),
289
    'file' => 'taxonomy.admin.inc',
290
  );
291
  $items['admin/structure/taxonomy/list'] = array(
292
    'title' => 'List',
293 294 295
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
296
  $items['admin/structure/taxonomy/add'] = array(
297
    'title' => 'Add vocabulary',
298 299
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_form_vocabulary'),
300
    'access arguments' => array('administer taxonomy'),
301
    'type' => MENU_LOCAL_ACTION,
302
    'file' => 'taxonomy.admin.inc',
303 304
  );

305
  $items['taxonomy/term/%taxonomy_term'] = array(
306
    'title' => 'Taxonomy term',
307 308
    'title callback' => 'taxonomy_term_title',
    'title arguments' => array(2),
309 310 311
    'page callback' => 'taxonomy_term_page',
    'page arguments' => array(2),
    'access arguments' => array('access content'),
312
    'file' => 'taxonomy.pages.inc',
313
  );
314
  $items['taxonomy/term/%taxonomy_term/view'] = array(
315 316
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
317
  );
318
  $items['taxonomy/term/%taxonomy_term/edit'] = array(
319
    'title' => 'Edit',
320
    'page callback' => 'drupal_get_form',
321 322 323
    // Pass a NULL argument to ensure that additional path components are not
    // passed to taxonomy_form_term() as the vocabulary machine name argument.
    'page arguments' => array('taxonomy_form_term', 2, NULL),
324 325
    'access callback' => 'taxonomy_term_access',
    'access arguments' => array('edit', 2),
326 327
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
328
    'file' => 'taxonomy.admin.inc',
329
  );
330 331 332 333 334 335 336 337 338 339
  $items['taxonomy/term/%taxonomy_term/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_term_confirm_delete', 2),
    'access callback' => 'taxonomy_term_access',
    'access arguments' => array('delete', 2),
    'type' => MENU_LOCAL_TASK,
    'weight' => 11,
    'file' => 'taxonomy.admin.inc',
  );
340 341 342 343 344 345 346 347
  $items['taxonomy/term/%taxonomy_term/feed'] = array(
    'title' => 'Taxonomy term',
    'title callback' => 'taxonomy_term_title',
    'title arguments' => array(2),
    'page callback' => 'taxonomy_term_feed',
    'page arguments' => array(2),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
348
    'file' => 'taxonomy.pages.inc',
349
  );
350
  $items['taxonomy/autocomplete'] = array(
351
    'title' => 'Autocomplete taxonomy',
352 353 354
    'page callback' => 'taxonomy_autocomplete',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
355
    'file' => 'taxonomy.pages.inc',
356
  );
357

358
  $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name'] = array(
359 360
    'title callback' => 'taxonomy_admin_vocabulary_title_callback',
    'title arguments' => array(3),
361 362
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_overview_terms', 3),
363
    'access arguments' => array('administer taxonomy'),
364
    'file' => 'taxonomy.admin.inc',
365
  );
366
  $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/list'] = array(
367
    'title' => 'List',
368
    'type' => MENU_DEFAULT_LOCAL_TASK,
369 370
    'weight' => -20,
  );
371
  $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/edit'] = array(
372
    'title' => 'Edit',
373
    'page callback' => 'drupal_get_form',
374
    'page arguments' => array('taxonomy_form_vocabulary', 3),
375 376
    'access arguments' => array('administer taxonomy'),
    'type' => MENU_LOCAL_TASK,
377
    'weight' => -10,
378
    'file' => 'taxonomy.admin.inc',
379
  );
380

381
  $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add'] = array(
382
    'title' => 'Add term',
383
    'page callback' => 'drupal_get_form',
384
    'page arguments' => array('taxonomy_form_term', NULL, 3),
385
    'access arguments' => array('administer taxonomy'),
386
    'type' => MENU_LOCAL_ACTION,
387
    'file' => 'taxonomy.admin.inc',
388
  );
389

Dries's avatar
Dries committed
390 391
  return $items;
}
392

393 394 395 396 397 398
/**
 * Implements hook_admin_paths().
 */
function taxonomy_admin_paths() {
  $paths = array(
    'taxonomy/term/*/edit' => TRUE,
399
    'taxonomy/term/*/delete' => TRUE,
400 401 402 403
  );
  return $paths;
}

404
/**
405 406 407 408 409 410 411 412 413 414 415 416 417 418
 * Access callback: Checks a user's permission for performing a taxonomy term
 * operation.
 *
 * @param $op
 *   The operation to be performed on the taxonomy term. Possible values are:
 *   - "edit"
 *   - "delete"
 * @param $term
 *   The $term object on which the operation is to be performed.
 *
 * @return
 *   TRUE if the operation may be performed, FALSE otherwise.
 *
 * @see taxonomy_menu()
419
 */
420 421 422 423 424 425 426 427
function taxonomy_term_access($op, $term) {
  if (!$term || !in_array($op, array('edit', 'delete'), TRUE)) {
    // If there was no term to check against, or the $op was not one of the
    // supported ones, we return access denied.
    return FALSE;
  }

  return user_access("$op terms in $term->vid") || user_access('administer taxonomy');
428 429
}

430 431 432
/**
 * Return the vocabulary name given the vocabulary object.
 */
433
function taxonomy_admin_vocabulary_title_callback(Vocabulary $vocabulary) {
434
  return $vocabulary->name;
435 436 437
}

/**
438 439
 * Saves a vocabulary.
 *
440
 * @param Drupal\taxonomy\Vocabulary $vocabulary
441
 *   The taxonomy vocabulary entity to be saved.
442
 */
443
function taxonomy_vocabulary_save(Vocabulary $vocabulary) {
444
  return $vocabulary->save();
Kjartan's avatar
Kjartan committed
445
}
446

447
/**
448
 * Deletes a vocabulary.
449 450 451
 *
 * @param $vid
 *   A vocabulary ID.
452
 *
453
 */
454
function taxonomy_vocabulary_delete($vid) {
455 456
  taxonomy_vocabulary_delete_multiple(array($vid));
}
457

458 459 460 461 462 463 464 465
/**
 * Deletes vocabularies.
 *
 * @param $vids
 *   The vocabulary ids.
 */
function taxonomy_vocabulary_delete_multiple(array $vids) {
  entity_delete_multiple('taxonomy_vocabulary', $vids);
466 467
}

468
/**
469
 * Implements hook_taxonomy_vocabulary_update().
470
 */
471
function taxonomy_taxonomy_vocabulary_update(Vocabulary $vocabulary) {
472 473
  // Reflect machine name changes in the definitions of existing 'taxonomy'
  // fields.
474
  if (!empty($vocabulary->original->machine_name) && $vocabulary->original->machine_name != $vocabulary->machine_name) {
475 476 477 478 479
    $fields = field_read_fields();
    foreach ($fields as $field_name => $field) {
      $update = FALSE;
      if ($field['type'] == 'taxonomy_term_reference') {
        foreach ($field['settings']['allowed_values'] as $key => &$value) {
480
          if ($value['vocabulary'] == $vocabulary->original->machine_name) {
481
            $value['vocabulary'] = $vocabulary->machine_name;
482 483 484 485 486 487 488 489 490 491 492
            $update = TRUE;
          }
        }
        if ($update) {
          field_update_field($field);
        }
      }
    }
  }
}

493
/**
494
 * Checks and updates the hierarchy flag of a vocabulary.
495
 *
496
 * Checks the current parents of all terms in a vocabulary and updates the
497
 * vocabulary's hierarchy setting to the lowest possible level. If no term
498 499 500 501 502
 * has parent terms then the vocabulary will be given a hierarchy of
 * TAXONOMY_HIERARCHY_DISABLED. If any term has a single parent then the
 * vocabulary will be given a hierarchy of TAXONOMY_HIERARCHY_SINGLE. If any
 * term has multiple parents then the vocabulary will be given a hierarchy of
 * TAXONOMY_HIERARCHY_MULTIPLE.
503
 *
504
 * @param Drupal\taxonomy\Vocabulary $vocabulary
505
 *   A taxonomy vocabulary entity.
506 507
 * @param $changed_term
 *   An array of the term structure that was updated.
508 509 510
 *
 * @return
 *   An integer that represents the level of the vocabulary's hierarchy.
511
 */
512
function taxonomy_check_vocabulary_hierarchy(Vocabulary $vocabulary, $changed_term) {
513
  $tree = taxonomy_get_tree($vocabulary->vid);
514
  $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
515
  foreach ($tree as $term) {
516
    // Update the changed term with the new parent value before comparison.
517
    if ($term->tid == $changed_term['tid']) {
518
      $term = (object) $changed_term;
519 520 521 522
      $term->parents = $term->parent;
    }
    // Check this term's parent count.
    if (count($term->parents) > 1) {
523
      $hierarchy = TAXONOMY_HIERARCHY_MULTIPLE;
524 525
      break;
    }
526
    elseif (count($term->parents) == 1 && !isset($term->parents[0])) {
527
      $hierarchy = TAXONOMY_HIERARCHY_SINGLE;
528 529
    }
  }
530 531 532
  if ($hierarchy != $vocabulary->hierarchy) {
    $vocabulary->hierarchy = $hierarchy;
    taxonomy_vocabulary_save($vocabulary);
533 534 535 536 537
  }

  return $hierarchy;
}

538
/**
539
 * Saves a term object to the database.
540
 *
541
 * @param Drupal\taxonomy\Term $term
542
 *   The taxonomy term entity to be saved.
543
 *
544
 * @return
545 546 547
 *   Status constant indicating whether term was inserted (SAVED_NEW) or updated
 *   (SAVED_UPDATED). When inserting a new term, $term->tid will contain the
 *   term ID of the newly created term.
548
 */
549
function taxonomy_term_save(Term $term) {
550
  return $term->save();
Kjartan's avatar
Kjartan committed
551
}
552

553
/**
554
 * Deletes a term.
555 556 557 558
 *
 * @param $tid
 *   The term ID.
 */
559
function taxonomy_term_delete($tid) {
560 561
  taxonomy_term_delete_multiple(array($tid));
}
562

563 564 565 566 567 568 569 570
/**
 * Deletes taxonomy terms.
 *
 * @param $tids
 *   The term ids to be deleted.
 */
function taxonomy_term_delete_multiple(array $tids) {
  entity_delete_multiple('taxonomy_term', $tids);
571 572
}

573 574 575
/**
 * Generate an array for rendering the given term.
 *
576
 * @param Drupal\taxonomy\Term $term
577
 *   A taxonomy term entity.
578 579
 * @param $view_mode
 *   View mode, e.g. 'full', 'teaser'...
580 581 582
 * @param $langcode
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
583 584 585 586
 *
 * @return
 *   An array as expected by drupal_render().
 */
587
function taxonomy_term_view(Term $term, $view_mode = 'full', $langcode = NULL) {
588
  if (!isset($langcode)) {
589
    $langcode = language_manager(LANGUAGE_TYPE_CONTENT)->langcode;
590 591
  }

592 593
  field_attach_prepare_view('taxonomy_term', array($term->tid => $term), $view_mode, $langcode);
  entity_prepare_view('taxonomy_term', array($term->tid => $term), $langcode);
594 595 596 597 598

  $build = array(
    '#theme' => 'taxonomy_term',
    '#term' => $term,
    '#view_mode' => $view_mode,
599
    '#language' => $langcode,
600 601
  );

602
  $build += field_attach_view('taxonomy_term', $term, $view_mode, $langcode);
603

604 605 606 607 608 609 610 611 612
  // Add term description if the term has one.
  if (!empty($term->description)) {
    $build['description'] = array(
      '#markup' => check_markup($term->description, $term->format, '', TRUE),
      '#weight' => 0,
      '#prefix' => '<div class="taxonomy-term-description">',
      '#suffix' => '</div>',
    );
  }
613 614 615

  $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css';

616
  // Allow modules to modify the structured term.
617
  drupal_alter(array('taxonomy_term_view', 'entity_view'), $build, $term);
618

619 620 621 622 623 624 625 626 627 628 629
  return $build;
}

/**
 * Process variables for taxonomy-term.tpl.php.
 */
function template_preprocess_taxonomy_term(&$variables) {
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  $variables['term'] = $variables['elements']['#term'];
  $term = $variables['term'];

630 631
  $uri = entity_uri('taxonomy_term', $term);
  $variables['term_url']  = url($uri['path'], $uri['options']);
632
  $variables['term_name'] = check_plain($term->name);
633
  $variables['page']      = $variables['view_mode'] == 'full' && taxonomy_term_is_page($term);
634 635

  // Flatten the term object's member fields.
636
  $variables = array_merge((array) $term, $variables);
637 638

  // Helpful $content variable for templates.
639
  $variables['content'] = array();
640 641 642 643 644 645 646 647 648 649
  foreach (element_children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }

  // field_attach_preprocess() overwrites the $[field_name] variables with the
  // values of the field in the language that was selected for display, instead
  // of the raw values in $term->[field_name], which contain all values in all
  // languages.
  field_attach_preprocess('taxonomy_term', $term, $variables['content'], $variables);

650
  // Gather classes, and clean up name so there are no underscores.
651
  $vocabulary_name_css = str_replace('_', '-', $term->vocabulary_machine_name);
652
  $variables['attributes']['class'][] = 'vocabulary-' . $vocabulary_name_css;
653

654 655
  $variables['theme_hook_suggestions'][] = 'taxonomy_term__' . $term->vocabulary_machine_name;
  $variables['theme_hook_suggestions'][] = 'taxonomy_term__' . $term->tid;
656 657 658
}

/**
659
 * Returns whether the current page is the page of the passed-in term.
660
 *
661
 * @param Drupal\taxonomy\Term $term
662
 *   A taxonomy term entity.
663
 */
664
function taxonomy_term_is_page(Term $term) {
665 666 667 668
  $page_term = menu_get_object('taxonomy_term', 2);
  return (!empty($page_term) ? $page_term->tid == $term->tid : FALSE);
}

669
/**
670
 * Clear all static cache variables for terms.
671 672
 */
function taxonomy_terms_static_reset() {
673
  entity_get_controller('taxonomy_term')->resetCache();
674 675
}

676 677
/**
 * Clear all static cache variables for vocabularies.
678
 *
679
 * @param $ids
680
 *   An array of ids to reset in entity controller cache.
681
 */
682
function taxonomy_vocabulary_static_reset(array $ids = NULL) {
683 684 685
  entity_get_controller('taxonomy_vocabulary')->resetCache($ids);
}

686 687 688 689
/**
 * Get names for all taxonomy vocabularies.
 *
 * @return
690 691 692 693 694
 *   An associative array of objects keyed by vocabulary machine name with
 *   information about taxonomy vocabularies. Each object has properties:
 *   - name: The vocabulary name.
 *   - machine_name: The machine name.
 *   - vid: The vocabulary ID.
695 696
 */
function taxonomy_vocabulary_get_names() {
697 698 699 700 701
  $names = &drupal_static(__FUNCTION__);

  if (!isset($names)) {
    $names = db_query('SELECT name, machine_name, vid FROM {taxonomy_vocabulary}')->fetchAllAssoc('machine_name');
  }
702

703 704 705
  return $names;
}

Dries's avatar
Dries committed
706
/**
707 708 709 710 711 712
 * Finds all parents of a given term ID.
 *
 * @param $tid
 *   A taxonomy term ID.
 *
 * @return
713 714
 *   An array of term objects which are the parents of the term $tid, or an
 *   empty array if parents are not found.
Dries's avatar
Dries committed
715
 */
716
function taxonomy_term_load_parents($tid) {
717 718 719 720 721 722 723 724 725 726 727 728
  $parents = &drupal_static(__FUNCTION__, array());

  if ($tid && !isset($parents[$tid])) {
    $query = db_select('taxonomy_term_data', 't');
    $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid');
    $query->addField('t', 'tid');
    $query->condition('h.tid', $tid);
    $query->addTag('term_access');
    $query->orderBy('t.weight');
    $query->orderBy('t.name');
    $tids = $query->execute()->fetchCol();
    $parents[$tid] = taxonomy_term_load_multiple($tids);
Kjartan's avatar
Kjartan committed
729
  }
730 731

  return isset($parents[$tid]) ? $parents[$tid] : array();
Kjartan's avatar
Kjartan committed
732
}
733

Dries's avatar
Dries committed
734 735 736
/**
 * Find all ancestors of a given term ID.
 */
737
function taxonomy_term_load_parents_all($tid) {
738 739 740 741 742 743
  $cache = &drupal_static(__FUNCTION__, array());

  if (isset($cache[$tid])) {
    return $cache[$tid];
  }

744
  $parents = array();
745 746
  if ($term = taxonomy_term_load($tid)) {
    $parents[] = $term;
747
    $n = 0;
748
    while ($parent = taxonomy_term_load_parents($parents[$n]->tid)) {
749 750 751 752
      $parents = array_merge($parents, $parent);
      $n++;
    }
  }
753 754 755

  $cache[$tid] = $parents;

756 757 758
  return $parents;
}

Dries's avatar
Dries committed
759
/**
760 761 762 763 764 765 766 767
 * Finds all children of a term ID.
 *
 * @param $tid
 *   A taxonomy term ID.
 * @param $vid
 *   An optional vocabulary ID to restrict the child search.
 *
 * @return
768 769
 *   An array of term objects that are the children of the term $tid, or an
 *   empty array when no children exist.
Dries's avatar
Dries committed
770
 */
771
function taxonomy_term_load_children($tid, $vid = 0) {
772 773 774
  $children = &drupal_static(__FUNCTION__, array());

  if ($tid && !isset($children[$tid])) {
775 776
    $query = db_select('taxonomy_term_data', 't');
    $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
777 778
    $query->addField('t', 'tid');
    $query->condition('h.parent', $tid);
779 780 781
    if ($vid) {
      $query->condition('t.vid', $vid);
    }
782 783 784 785 786
    $query->addTag('term_access');
    $query->orderBy('t.weight');
    $query->orderBy('t.name');
    $tids = $query->execute()->fetchCol();
    $children[$tid] = taxonomy_term_load_multiple($tids);
Kjartan's avatar
Kjartan committed
787
  }
788 789

  return isset($children[$tid]) ? $children[$tid] : array();
Kjartan's avatar
Kjartan committed
790
}
791

Dries's avatar
Dries committed
792 793 794 795 796 797 798 799
/**
 * Create a hierarchical representation of a vocabulary.
 *
 * @param $vid
 *   Which vocabulary to generate the tree for.
 * @param $parent
 *   The term ID under which to generate the tree. If 0, generate the tree
 *   for the entire vocabulary.
800 801
 * @param $max_depth
 *   The number of levels of the tree to return. Leave NULL to return all levels.
802 803 804 805 806
 * @param $load_entities
 *   If TRUE, a full entity load will occur on the term objects. Otherwise they
 *   are partial objects queried directly from the {taxonomy_term_data} table to
 *   save execution time and memory consumption when listing large numbers of
 *   terms. Defaults to FALSE.
807
 *
Dries's avatar
Dries committed
808 809 810
 * @return
 *   An array of all term objects in the tree. Each term object is extended
 *   to have "depth" and "parents" attributes in addition to its normal ones.
811 812
 *   Results are statically cached. Term objects will be partial or complete
 *   depending on the $load_entities parameter.
Dries's avatar
Dries committed
813
 */
814
function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
815
  $children = &drupal_static(__FUNCTION__, array());
816 817
  $parents = &drupal_static(__FUNCTION__ . ':parents', array());
  $terms = &drupal_static(__FUNCTION__ . ':terms', array());
818

Dries's avatar
Dries committed
819 820 821 822
  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
  // and its children, too.
  if (!isset($children[$vid])) {
    $children[$vid] = array();
823 824
    $parents[$vid] = array();
    $terms[$vid] = array();
Dries's avatar
Dries committed
825

826 827
    $query = db_select('taxonomy_term_data', 't');
    $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
828 829 830 831 832 833 834 835 836 837 838 839 840 841
    $result = $query
      ->addTag('translatable')
      ->addTag('term_access')
      ->fields('t')
      ->fields('h', array('parent'))
      ->condition('t.vid', $vid)
      ->orderBy('t.weight')
      ->orderBy('t.name')
      ->execute();

    foreach ($result as $term) {
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
842 843
    }
  }
844

845 846 847 848 849 850
  // Load full entities, if necessary. The entity controller statically
  // caches the results.
  if ($load_entities) {
    $term_entities = taxonomy_term_load_multiple(array_keys($terms[$vid]));
  }

851
  $max_depth = (!isset($max_depth)) ? count($children[$vid]) : $max_depth;
852
  $tree = array();
853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872

  // Keeps track of the parents we have to process, the last entry is used
  // for the next processing step.
  $process_parents = array();
  $process_parents[] = $parent;

  // Loops over the parent terms and adds its children to the tree array.
  // Uses a loop instead of a recursion, because it's more efficient.
  while (count($process_parents)) {
    $parent = array_pop($process_parents);
    // The number of parents determines the current depth.
    $depth = count($process_parents);
    if ($max_depth > $depth && !empty($children[$vid][$parent])) {
      $has_children = FALSE;
      $child = current($children[$vid][$parent]);
      do {
        if (empty($child)) {
          break;
        }
        $term = $load_entities ? $term_entities[$child] : $terms[$vid][$child];
873 874 875
        if (isset($parents[$vid][$term->tid])) {
          // Clone the term so that the depth attribute remains correct
          // in the event of multiple parents.
876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
          $term = clone $term;
        }
        $term->depth = $depth;
        unset($term->parent);
        $term->parents = $parents[$vid][$term->tid];
        $tree[] = $term;
        if (!empty($children[$vid][$term->tid])) {
          $has_children = TRUE;

          // We have to continue with this parent later.
          $process_parents[] = $parent;
          // Use the current term as parent for the next iteration.
          $process_parents[] = $term->tid;

          // Reset pointers for child lists because we step in there more often
          // with multi parents.
          reset($children[$vid][$term->tid]);
          // Move pointer so that we get the correct term the next time.
          next($children[$vid][$parent]);
          break;
        }
      } while ($child = next($children[$vid][$parent]));

      if (!$has_children) {
        // We processed all terms in this hierarchy-level, reset pointer
        // so that this function works the next time it gets called.
        reset($children[$vid][$parent]);
903
      }
904
    }
Kjartan's avatar
Kjartan committed
905
  }
906

907
  return $tree;
Kjartan's avatar
Kjartan committed
908
}
909

Dries's avatar
Dries committed
910
/**
Dries's avatar
Dries committed
911
 * Try to map a string to an existing term, as for glossary use.
Dries's avatar
Dries committed
912
 *
Dries's avatar
Dries committed
913 914 915
 * Provides a case-insensitive and trimmed mapping, to maximize the
 * likelihood of a successful match.
 *
916
 * @param $name
Dries's avatar
Dries committed
917
 *   Name of the term to search for.
918 919
 * @param $vocabulary
 *   (optional) Vocabulary machine name to limit the search. Defaults to NULL.
Dries's avatar
Dries committed
920 921 922
 *
 * @return
 *   An array of matching term objects.
Dries's avatar
Dries committed
923
 */
924
function taxonomy_term_load_multiple_by_name($name, $vocabulary = NULL) {
925 926 927 928 929 930 931 932 933 934 935 936
  $conditions = array('name' => trim($name));
  if (isset($vocabulary)) {
    $vocabularies = taxonomy_vocabulary_get_names();
    if (isset($vocabularies[$vocabulary])){
      $conditions['vid'] = $vocabularies[$vocabulary]->vid;
    }
    else {
      // Return an empty array when filtering by a non-existing vocabulary.
      return array();
    }
  }
  return taxonomy_term_load_multiple(array(), $conditions);
Dries's avatar
Dries committed
937 938
}

939 940 941 942 943 944 945
/**
 * Load multiple taxonomy terms based on certain conditions.
 *
 * This function should be used whenever you need to load more than one term
 * from the database. Terms are loaded into memory and will not require
 * database access if loaded again during the same page request.
 *
946
 * @see entity_load_multiple()
947
 * @see Drupal\entity\EntityFieldQuery
948
 *
949 950 951
 * @param array|bool $tids
 *   An array of taxonomy term IDs, or FALSE to load all terms.
 * @param array $conditions
952 953 954
 *   (deprecated) An associative array of conditions on the {taxonomy_term}
 *   table, where the keys are the database fields and the values are the
 *   values those fields must have. Instead, it is preferable to use
955 956
 *   Drupal\entity\EntityFieldQuery to retrieve a list of entity IDs
 *   loadable by this function.
957
 *
958
 * @return array
959 960
 *   An array of taxonomy term entities, indexed by tid. When no results are
 *   found, an empty array is returned.
961 962
 *
 * @todo Remove $conditions in Drupal 8.
963
 */
964 965
function taxonomy_term_load_multiple($tids = array(), array $conditions = array()) {
  return entity_load_multiple('taxonomy_term', $tids, $conditions);
966
}
967

968
/**
969
 * Loads multiple taxonomy vocabularies based on certain conditions.
970 971 972 973 974
 *
 * This function should be used whenever you need to load more than one
 * vocabulary from the database. Terms are loaded into memory and will not
 * require database access if loaded again during the same page request.
 *
975
 * @see entity_load_multiple()
976
 *
977
 * @param array|bool $vids
978
 *  An array of taxonomy vocabulary IDs, or FALSE to load all vocabularies.
979
 * @param array $conditions
980 981
 *  An array of conditions to add to the query.
 *
982
 * @return array
983 984
 *  An array of vocabulary objects, indexed by vid.
 */
985 986
function taxonomy_vocabulary_load_multiple($vids = array(), array $conditions = array()) {
  return entity_load_multiple('taxonomy_vocabulary', $vids, $conditions);
987
}
988

989
/**
990
 * Return the taxonomy vocabulary entity matching a vocabulary ID.
991
 *
992
 * @param int $vid
993 994
 *   The vocabulary's ID.
 *
995
 * @return Drupal\taxonomy\Vocabulary|false
996 997
 *   The taxonomy vocabulary entity, if exists, FALSE otherwise. Results are
 *   statically cached.
998 999
 *
 * @see taxonomy_vocabulary_machine_name_load()
1000 1001
 */
function taxonomy_vocabulary_load($vid) {
1002
  return entity_load('taxonomy_vocabulary', $vid);
1003 1004
}

1005
/**
1006
 * Return the taxonomy vocabulary entity matching a vocabulary machine name.
1007 1008 1009 1010
 *
 * @param $name
 *   The vocabulary's machine name.
 *
Dries's avatar