taxonomy.module 41 KB
Newer Older
Dries's avatar
 
Dries committed
1
<?php
Dries's avatar
 
Dries committed
2

Dries's avatar
 
Dries committed
3 4 5 6 7
/**
 * @file
 * Enables the organization of content into categories.
 */

8
use Drupal\Core\Entity\FieldableDatabaseStorageController;
9
use Drupal\Core\Entity\EntityInterface;
10
use Drupal\Core\Field\FieldDefinitionInterface;
11 12
use Drupal\field\FieldInterface;
use Drupal\field\FieldInstanceInterface;
13
use Drupal\file\FileInterface;
14 15 16
use Drupal\node\Entity\Node;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
17
use Drupal\taxonomy\VocabularyInterface;
18

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
/**
 * 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;

34 35 36 37 38 39 40 41
/**
 * 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().
 */

42
/**
43
 * Implements hook_help().
44 45 46 47 48 49
 */
function taxonomy_help($path, $arg) {
  switch ($path) {
    case 'admin/help#taxonomy':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
50
      $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>';
51 52 53
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating vocabularies') . '</dt>';
54
      $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'))));
55
      $output .= '<ul><li>' . t('<em>vocabulary</em>: Music') . '</li>';
56 57 58 59 60 61 62 63 64
      $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>';
65
      $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>';
66
      $output .= '<dt>' . t('Classifying content') . '</dt>';
67
      $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>';
68 69 70 71 72 73 74
      $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':
75
      $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>';
76
      return $output;
77
    case 'admin/structure/taxonomy/manage/%':
78
      $vocabulary = entity_load('taxonomy_vocabulary', $arg[4]);
79
      switch ($vocabulary->hierarchy) {
80
        case TAXONOMY_HIERARCHY_DISABLED:
81
          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>';
82
        case TAXONOMY_HIERARCHY_SINGLE:
83
          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>';
84
        case TAXONOMY_HIERARCHY_MULTIPLE:
85
          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>';
86 87 88 89
      }
  }
}

Dries's avatar
 
Dries committed
90
/**
91
 * Implements hook_permission().
Dries's avatar
 
Dries committed
92
 */
93
function taxonomy_permission() {
94
  $permissions = array(
95
    'administer taxonomy' => array(
96
      'title' => t('Administer vocabularies and terms'),
97
    ),
98
  );
99
  foreach (entity_load_multiple('taxonomy_vocabulary') as $vocabulary) {
100
    $permissions += array(
101
      'edit terms in ' . $vocabulary->id() => array(
102 103 104 105
        'title' => t('Edit terms in %vocabulary', array('%vocabulary' => $vocabulary->name)),
      ),
    );
    $permissions += array(
106
       'delete terms in ' . $vocabulary->id() => array(
107
         'title' => t('Delete terms from %vocabulary', array('%vocabulary' => $vocabulary->name)),
108 109 110 111
      ),
    );
  }
  return $permissions;
Kjartan's avatar
Kjartan committed
112
}
Dries's avatar
 
Dries committed
113

114 115 116 117 118
/**
 * Implements hook_entity_bundle_info().
 */
function taxonomy_entity_bundle_info() {
  $bundles = array();
119
  foreach (taxonomy_vocabulary_get_names() as $id) {
120
    $config = \Drupal::config('taxonomy.vocabulary.' . $id);
121
    $bundles['taxonomy_term'][$id]['label'] = $config->get('name');
122
  }
123
  return $bundles;
124 125
}

126
/**
127
 * Entity URI callback.
128
 */
129 130
function taxonomy_term_uri($term) {
  return array(
131
    'path' => 'taxonomy/term/' . $term->id(),
132
  );
133 134
}

135 136 137 138 139
/**
 * Implements hook_field_extra_fields().
 */
function taxonomy_field_extra_fields() {
  $return = array();
140
  foreach (entity_get_bundles('taxonomy_term') as $bundle => $bundle_info) {
141
    $return['taxonomy_term'][$bundle] = array(
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
      '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,
        ),
160 161 162 163 164 165 166
      ),
    );
  }

  return $return;
}

167 168 169 170 171 172 173
/**
 * 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.
 *
174 175
 * @param $tid
 *   The term ID.
176 177
 * @param $pager
 *   Boolean to indicate whether a pager should be used.
178 179 180
 * @param $limit
 *   Integer. The maximum number of nodes to find.
 *   Set to FALSE for no limit.
181
 * @param $order
182 183 184 185 186
 *   An array of fields and directions.
 *
 * @return
 *   An array of nids matching the query.
 */
187
function taxonomy_select_nodes($tid, $pager = TRUE, $limit = FALSE, $order = array('t.sticky' => 'DESC', 't.created' => 'DESC')) {
188
  if (!\Drupal::config('taxonomy.settings')->get('maintain_index_table')) {
189 190 191 192
    return array();
  }
  $query = db_select('taxonomy_index', 't');
  $query->addTag('node_access');
193
  $query->addMetaData('base_table', 'taxonomy_index');
194
  $query->condition('tid', $tid);
195 196 197 198
  if ($pager) {
    $count_query = clone $query;
    $count_query->addExpression('COUNT(t.nid)');

199
    $query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender');
200 201 202
    if ($limit !== FALSE) {
      $query = $query->limit($limit);
    }
203 204 205
    $query->setCountQuery($count_query);
  }
  else {
206 207 208
    if ($limit !== FALSE) {
      $query->range(0, $limit);
    }
209 210 211 212 213 214 215 216 217 218 219 220 221
  }
  $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();
}

222
/**
223
 * Implements hook_theme().
224 225 226
 */
function taxonomy_theme() {
  return array(
227 228 229 230
    'taxonomy_term' => array(
      'render element' => 'elements',
      'template' => 'taxonomy-term',
    ),
231 232 233
  );
}

Dries's avatar
 
Dries committed
234
/**
235
 * Implements hook_menu().
Dries's avatar
 
Dries committed
236
 */
237
function taxonomy_menu() {
238
  $items['admin/structure/taxonomy'] = array(
239 240
    'title' => 'Taxonomy',
    'description' => 'Manage tagging, categorization, and classification of your content.',
241
    'route_name' => 'taxonomy.vocabulary_list',
242
  );
243
  $items['admin/structure/taxonomy/list'] = array(
244
    'title' => 'List',
245 246
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
247
  $items['admin/structure/taxonomy/add'] = array(
248
    'route_name' => 'taxonomy.vocabulary_add',
249
    'type' => MENU_SIBLING_LOCAL_TASK,
250 251
  );

252
  $items['taxonomy/term/%taxonomy_term'] = array(
253
    'title' => 'Taxonomy term',
254 255
    'title callback' => 'taxonomy_term_title',
    'title arguments' => array(2),
256
    'route_name' => 'taxonomy.term_page',
257
  );
258 259 260 261
  $items['taxonomy/term/%taxonomy_term/feed'] = array(
    'title' => 'Taxonomy term',
    'title callback' => 'taxonomy_term_title',
    'title arguments' => array(2),
262
    'route_name' => 'taxonomy.term_feed',
263 264
    'type' => MENU_CALLBACK,
  );
265

266
  $items['admin/structure/taxonomy/manage/%taxonomy_vocabulary'] = array(
267
    'route_name' => 'taxonomy.overview_terms',
268
    'title callback' => 'entity_page_label',
269
    'title arguments' => array(4),
270
  );
271
  $items['admin/structure/taxonomy/manage/%taxonomy_vocabulary/list'] = array(
272
    'title' => 'List',
273
    'type' => MENU_DEFAULT_LOCAL_TASK,
274
  );
275
  $items['admin/structure/taxonomy/manage/%taxonomy_vocabulary/edit'] = array(
276
    'title' => 'Edit',
277
    'route_name' => 'taxonomy.vocabulary_edit',
278
    'type' => MENU_LOCAL_TASK,
279
  );
280

Dries's avatar
 
Dries committed
281 282
  return $items;
}
Dries's avatar
 
Dries committed
283

284 285 286 287 288 289
/**
 * Implements hook_admin_paths().
 */
function taxonomy_admin_paths() {
  $paths = array(
    'taxonomy/term/*/edit' => TRUE,
290
    'taxonomy/term/*/delete' => TRUE,
291 292
    'taxonomy/term/*/translations' => TRUE,
    'taxonomy/term/*/translations/*' => TRUE,
293 294 295 296
  );
  return $paths;
}

297
/**
298
 * Checks and updates the hierarchy flag of a vocabulary.
299
 *
300
 * Checks the current parents of all terms in a vocabulary and updates the
301
 * vocabulary's hierarchy setting to the lowest possible level. If no term
302 303 304 305 306
 * 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.
307
 *
308
 * @param \Drupal\taxonomy\VocabularyInterface $vocabulary
309
 *   A taxonomy vocabulary entity.
310 311
 * @param $changed_term
 *   An array of the term structure that was updated.
312 313 314
 *
 * @return
 *   An integer that represents the level of the vocabulary's hierarchy.
315
 */
316
function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $changed_term) {
317
  $tree = taxonomy_get_tree($vocabulary->id());
318
  $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
319
  foreach ($tree as $term) {
320
    // Update the changed term with the new parent value before comparison.
321
    if ($term->tid == $changed_term['tid']) {
322
      $term = (object) $changed_term;
323 324 325 326
      $term->parents = $term->parent;
    }
    // Check this term's parent count.
    if (count($term->parents) > 1) {
327
      $hierarchy = TAXONOMY_HIERARCHY_MULTIPLE;
328 329
      break;
    }
330
    elseif (count($term->parents) == 1 && !isset($term->parents[0])) {
331
      $hierarchy = TAXONOMY_HIERARCHY_SINGLE;
332 333
    }
  }
334 335
  if ($hierarchy != $vocabulary->hierarchy) {
    $vocabulary->hierarchy = $hierarchy;
336
    $vocabulary->save();
337 338 339 340 341
  }

  return $hierarchy;
}

342
/**
343
 * Generates an array which displays a term detail page.
344
 *
345
 * @param \Drupal\taxonomy\Entity\Term $term
346 347
 *   A taxonomy term object.
 * @param string $view_mode
348
 *   View mode, e.g. 'full', 'teaser'...
349
 * @param string $langcode
350 351
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
352
 *
353
 * @return array
354
 *   A $page element suitable for use by drupal_page_render().
355
 */
356 357
function taxonomy_term_view(Term $term, $view_mode = 'full', $langcode = NULL) {
  return entity_view($term, $view_mode, $langcode);
358
}
359

360 361
 /**
 * Constructs a drupal_render() style array from an array of loaded terms.
362
 *
363
 * @param array $terms
364
 *   An array of taxonomy terms as returned by entity_load_multiple('taxonomy_term').
365 366 367 368 369 370
 * @param string $view_mode
 *   View mode, e.g. 'full', 'teaser'...
 * @param string $langcode
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
 *
371 372
 * @return array
 *   An array in the format expected by drupal_render().
373
 */
374 375
function taxonomy_term_view_multiple(array $terms, $view_mode = 'full', $langcode = NULL) {
  return entity_view_multiple($terms, $view_mode, $langcode);
376 377
}

378 379 380 381 382 383 384 385 386 387 388 389 390 391
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function taxonomy_theme_suggestions_taxonomy_term(array $variables) {
  $suggestions = array();

  $term = $variables['elements']['#term'];

  $suggestions[] = 'taxonomy_term__' . $term->bundle();
  $suggestions[] = 'taxonomy_term__' . $term->id();

  return $suggestions;
}

392
/**
393 394 395 396 397 398 399 400 401 402 403 404
 * Prepares variables for taxonomy term templates.
 *
 * Default template: taxonomy-term.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the taxonomy term and any
 *     fields attached to the term. Properties used:
 *     - #term: The term object.
 *     - #view_mode: The current view mode for this taxonomy term, e.g.
 *       'full' or 'teaser'.
 *   - attributes: HTML attributes for the containing element.
405 406 407 408 409 410
 */
function template_preprocess_taxonomy_term(&$variables) {
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  $variables['term'] = $variables['elements']['#term'];
  $term = $variables['term'];

411
  $uri = $term->uri();
412
  $variables['url']  = url($uri['path'], $uri['options']);
413 414 415
  // We use name here because that is what appears in the UI.
  $variables['name'] = check_plain($term->label());
  $variables['page'] = $variables['view_mode'] == 'full' && taxonomy_term_is_page($term);
416 417

  // Helpful $content variable for templates.
418
  $variables['content'] = array();
419 420 421 422 423 424 425 426
  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.
427
  field_attach_preprocess($term, $variables['content'], $variables);
428

429
  // Gather classes, and clean up name so there are no underscores.
430
  $variables['attributes']['class'][] = 'taxonomy-term';
431
  $vocabulary_name_css = str_replace('_', '-', $term->bundle());
432
  $variables['attributes']['class'][] = 'vocabulary-' . $vocabulary_name_css;
433 434 435
}

/**
436
 * Returns whether the current page is the page of the passed-in term.
437
 *
438
 * @param \Drupal\taxonomy\Entity\Term $term
439
 *   A taxonomy term entity.
440
 */
441
function taxonomy_term_is_page(Term $term) {
442 443 444 445 446 447
  $request = \Drupal::request();
  if ($request->attributes->has('taxonomy_term')) {
    $page_term = $request->attributes->get('taxonomy_term');
    return $page_term->id() == $term->id();
  }
  return FALSE;
448 449
}

450
/**
451
 * Clear all static cache variables for terms.
452 453
 */
function taxonomy_terms_static_reset() {
454
  \Drupal::entityManager()->getStorageController('taxonomy_term')->resetCache();
455 456
}

457 458
/**
 * Clear all static cache variables for vocabularies.
459
 *
460
 * @param $ids
461
 *   An array of ids to reset in entity controller cache.
462
 */
463
function taxonomy_vocabulary_static_reset(array $ids = NULL) {
464
  \Drupal::entityManager()->getStorageController('taxonomy_vocabulary')->resetCache($ids);
465 466
}

467 468 469
/**
 * Get names for all taxonomy vocabularies.
 *
470 471
 * @return array
 *   A list of existing vocabulary IDs.
472 473
 */
function taxonomy_vocabulary_get_names() {
474 475 476
  $names = &drupal_static(__FUNCTION__);

  if (!isset($names)) {
477 478 479 480 481 482
    $names = array();
    $config_names = config_get_storage_names_with_prefix('taxonomy.vocabulary.');
    foreach ($config_names as $config_name) {
      $id = substr($config_name, strlen('taxonomy.vocabulary.'));
      $names[$id] = $id;
    }
483
  }
484

485 486 487
  return $names;
}

Dries's avatar
 
Dries committed
488
/**
489 490 491 492 493 494
 * Finds all parents of a given term ID.
 *
 * @param $tid
 *   A taxonomy term ID.
 *
 * @return
495 496
 *   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
497
 */
498
function taxonomy_term_load_parents($tid) {
499 500 501
  $parents = &drupal_static(__FUNCTION__, array());

  if ($tid && !isset($parents[$tid])) {
502
    $tids = \Drupal::entityManager()->getStorageController('taxonomy_term')->loadParents($tid);
503
    $parents[$tid] = entity_load_multiple('taxonomy_term', $tids);
Kjartan's avatar
Kjartan committed
504
  }
505 506

  return isset($parents[$tid]) ? $parents[$tid] : array();
Kjartan's avatar
Kjartan committed
507
}
Dries's avatar
 
Dries committed
508

Dries's avatar
 
Dries committed
509 510 511
/**
 * Find all ancestors of a given term ID.
 */
512
function taxonomy_term_load_parents_all($tid) {
513 514 515 516 517 518
  $cache = &drupal_static(__FUNCTION__, array());

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

Dries's avatar
 
Dries committed
519
  $parents = array();
520
  if ($term = entity_load('taxonomy_term', $tid)) {
521
    $parents[] = $term;
Dries's avatar
 
Dries committed
522
    $n = 0;
523
    while ($parent = taxonomy_term_load_parents($parents[$n]->id())) {
Dries's avatar
 
Dries committed
524 525 526 527
      $parents = array_merge($parents, $parent);
      $n++;
    }
  }
528 529 530

  $cache[$tid] = $parents;

Dries's avatar
 
Dries committed
531 532 533
  return $parents;
}

Dries's avatar
 
Dries committed
534
/**
535 536 537 538 539 540 541 542
 * 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
543 544
 *   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
545
 */
546
function taxonomy_term_load_children($tid, $vid = NULL) {
547 548 549
  $children = &drupal_static(__FUNCTION__, array());

  if ($tid && !isset($children[$tid])) {
550
    $tids = \Drupal::entityManager()->getStorageController('taxonomy_term')->loadChildren($tid);
551
    $children[$tid] = entity_load_multiple('taxonomy_term', $tids);
Kjartan's avatar
Kjartan committed
552
  }
553 554

  return isset($children[$tid]) ? $children[$tid] : array();
Kjartan's avatar
Kjartan committed
555
}
Dries's avatar
 
Dries committed
556

Dries's avatar
 
Dries committed
557 558 559 560
/**
 * Create a hierarchical representation of a vocabulary.
 *
 * @param $vid
561
 *   The vocabulary ID to generate the tree for.
Dries's avatar
 
Dries committed
562 563 564
 * @param $parent
 *   The term ID under which to generate the tree. If 0, generate the tree
 *   for the entire vocabulary.
Dries's avatar
 
Dries committed
565 566
 * @param $max_depth
 *   The number of levels of the tree to return. Leave NULL to return all levels.
567 568 569 570 571
 * @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.
572
 *
Dries's avatar
 
Dries committed
573 574 575
 * @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.
576 577
 *   Results are statically cached. Term objects will be partial or complete
 *   depending on the $load_entities parameter.
Dries's avatar
 
Dries committed
578
 */
579
function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
580
  $children = &drupal_static(__FUNCTION__, array());
581 582
  $parents = &drupal_static(__FUNCTION__ . ':parents', array());
  $terms = &drupal_static(__FUNCTION__ . ':terms', array());
583

584 585
  // We cache trees, so it's not CPU-intensive to call taxonomy_get_tree() on a
  // term and its children, too.
Dries's avatar
 
Dries committed
586 587
  if (!isset($children[$vid])) {
    $children[$vid] = array();
588 589
    $parents[$vid] = array();
    $terms[$vid] = array();
Dries's avatar
 
Dries committed
590

591
    $result = \Drupal::entityManager()->getStorageController('taxonomy_term')->loadTree($vid);
592 593 594 595 596

    foreach ($result as $term) {
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
Dries's avatar
 
Dries committed
597 598
    }
  }
Dries's avatar
 
Dries committed
599

600 601 602
  // Load full entities, if necessary. The entity controller statically
  // caches the results.
  if ($load_entities) {
603
    $term_entities = entity_load_multiple('taxonomy_term', array_keys($terms[$vid]));
604 605
  }

606
  $max_depth = (!isset($max_depth)) ? count($children[$vid]) : $max_depth;
607
  $tree = array();
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627

  // 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];
628
        if (isset($parents[$vid][$load_entities ? $term->id() : $term->tid])) {
629 630
          // Clone the term so that the depth attribute remains correct
          // in the event of multiple parents.
631 632 633 634
          $term = clone $term;
        }
        $term->depth = $depth;
        unset($term->parent);
635 636
        $tid = $load_entities ? $term->id() : $term->tid;
        $term->parents = $parents[$vid][$tid];
637
        $tree[] = $term;
638
        if (!empty($children[$vid][$tid])) {
639 640 641 642 643
          $has_children = TRUE;

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

          // Reset pointers for child lists because we step in there more often
          // with multi parents.
648
          reset($children[$vid][$tid]);
649 650 651 652 653 654 655 656 657 658
          // 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]);
Dries's avatar
 
Dries committed
659
      }
Dries's avatar
 
Dries committed
660
    }
Kjartan's avatar
Kjartan committed
661
  }
Dries's avatar
 
Dries committed
662

663
  return $tree;
Kjartan's avatar
Kjartan committed
664
}
Dries's avatar
 
Dries committed
665

Dries's avatar
 
Dries committed
666
/**
Dries's avatar
 
Dries committed
667
 * Try to map a string to an existing term, as for glossary use.
Dries's avatar
 
Dries committed
668
 *
Dries's avatar
 
Dries committed
669 670 671
 * Provides a case-insensitive and trimmed mapping, to maximize the
 * likelihood of a successful match.
 *
672
 * @param $name
Dries's avatar
 
Dries committed
673
 *   Name of the term to search for.
674 675
 * @param $vocabulary
 *   (optional) Vocabulary machine name to limit the search. Defaults to NULL.
Dries's avatar
 
Dries committed
676 677 678
 *
 * @return
 *   An array of matching term objects.
Dries's avatar
 
Dries committed
679
 */
680
function taxonomy_term_load_multiple_by_name($name, $vocabulary = NULL) {
681
  $values = array('name' => trim($name));
682 683 684
  if (isset($vocabulary)) {
    $vocabularies = taxonomy_vocabulary_get_names();
    if (isset($vocabularies[$vocabulary])){
685
      $values['vid'] = $vocabulary;
686 687 688 689 690 691
    }
    else {
      // Return an empty array when filtering by a non-existing vocabulary.
      return array();
    }
  }
692
  return entity_load_multiple_by_properties('taxonomy_term', $values);
Dries's avatar
 
Dries committed
693 694
}

695 696 697 698 699 700 701
/**
 * 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.
 *
702
 * @see entity_load_multiple()
703
 * @see \Drupal\Core\Entity\Query\EntityQueryInterface
704
 *
705 706
 * @deprecated use entity_load_multiple('taxonomy_term', $tids) instead.
 *
707 708
 * @param array $tids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
709
 *
710
 * @return array
711 712
 *   An array of taxonomy term entities, indexed by tid. When no results are
 *   found, an empty array is returned.
713
 */
714 715
function taxonomy_term_load_multiple(array $tids = NULL) {
  return entity_load_multiple('taxonomy_term', $tids);
716
}
717

718
/**
719
 * Loads multiple taxonomy vocabularies based on certain conditions.
720 721 722 723 724
 *
 * 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.
 *
725
 * @see entity_load_multiple()
726
 *
727 728
 * @deprecated use entity_load_multiple('taxonomy_vocabulary', $vids) instead.
 *
729 730
 * @param array $vids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
731
 *
732
 * @return array
733 734
 *  An array of vocabulary objects, indexed by vid.
 */
735 736
function taxonomy_vocabulary_load_multiple(array $vids = NULL) {
  return entity_load_multiple('taxonomy_vocabulary', $vids);
737
}
738

739
/**
740
 * Sorts vocabularies by its weight and label.
741
 *
742
 * @param array $vocabularies
743
 *   An array of \Drupal\taxonomy\Entity\Vocabulary objects.
744
 */
745 746 747 748
function taxonomy_vocabulary_sort(array &$vocabularies = array()) {
  // @todo Remove error suppressing when http://drupal.org/node/1799600 is
  // fixed.
  @uasort($vocabularies, 'Drupal\Core\Config\Entity\ConfigEntityBase::sort');
749 750
}

751
/**
752
 * Return the taxonomy vocabulary entity matching a vocabulary ID.
753
 *
754 755
 * @deprecated use entity_load('taxonomy_vocabulary', $vid) instead.
 *
756 757
 * @param int $vid
 *   The vocabulary's ID.
758
 *
759
 * @return \Drupal\taxonomy\Entity\Vocabulary|null
760
 *   The taxonomy vocabulary entity, if exists, NULL otherwise. Results are
761
 *   statically cached.
762
 */
763 764
function taxonomy_vocabulary_load($vid) {
  return entity_load('taxonomy_vocabulary', $vid);
765 766
}

Dries's avatar
 
Dries committed
767
/**
768
 * Return the taxonomy term entity matching a term ID.
769
 *
770 771
 * @deprecated use entity_load('taxonomy_term', $tid) instead.
 *
772 773 774
 * @param $tid
 *   A term's ID
 *
775
 * @return \Drupal\taxonomy\Entity\Term|null
776
 *   A taxonomy term entity, or NULL if the term was not found. Results are
777
 *   statically cached.
Dries's avatar
 
Dries committed
778
 */
779
function taxonomy_term_load($tid) {
780
  if (!is_numeric($tid)) {
781
    return NULL;
782
  }
783
  return entity_load('taxonomy_term', $tid);
784 785
}

786 787 788 789 790 791 792 793 794
/**
 * Implements hook_file_download_access().
 */
function taxonomy_file_download_access($field, EntityInterface $entity, FileInterface $file) {
  if ($entity->entityType() == 'taxonomy_term') {
    return $entity->access('view');
  }
}

795
/**
796
 * Implodes a list of tags of a certain vocabulary into a string.
797 798
 *
 * @see drupal_explode_tags()
799 800 801 802 803
 */
function taxonomy_implode_tags($tags, $vid = NULL) {
  $typed_tags = array();
  foreach ($tags as $tag) {
    // Extract terms belonging to the vocabulary in question.
804
    if (!isset($vid) || $tag->bundle() == $vid) {
805
      // Make sure we have a completed loaded taxonomy term.
806
      if ($tag instanceof EntityInterface && $label = $tag->label()) {
807
        // Commas and quotes in tag names are special cases, so encode 'em.
808 809
        if (strpos($label, ',') !== FALSE || strpos($label, '"') !== FALSE) {
          $typed_tags[] = '"' . str_replace('"', '""', $label) . '"';
810 811
        }
        else {
812
          $typed_tags[] = $label;
813
        }
814 815 816
      }
    }
  }
817
  return implode(', ', $typed_tags);
818
}
819

820
/**
821
 * Implements hook_field_info().
822 823 824
 *
 * Field settings:
 * - allowed_values: a list array of one or more vocabulary trees:
825
 *   - vocabulary: a vocabulary machine name.
826 827 828 829 830 831 832
 *   - parent: a term ID of a term whose children are allowed. This should be
 *     '0' if all terms in a vocabulary are allowed. The allowed values do not
 *     include the parent term.
 *
 */
function taxonomy_field_info() {
  return array(
833 834
    'taxonomy_term_reference' => array(
      'label' => t('Term reference'),
835 836
      'description' => t('This field stores a reference to a taxonomy term.'),
      'default_widget' => 'options_select',
837
      'default_formatter' => 'taxonomy_term_reference_link',
838
      'class' => 'Drupal\taxonomy\Type\TaxonomyTermReferenceItem',
839
      'settings' => array(
840
        'options_list_callback' => NULL,
841 842
        'allowed_values' => array(
          array(
843
            'vocabulary' => '',
844 845 846 847
            'parent' => '0',
          ),
        ),
      ),
848
      'list_class' => '\Drupal\taxonomy\Plugin\Field\FieldType\TaxonomyTermReferenceFieldItemList',
849 850 851 852 853
    ),
  );
}

/**
854
 * Implements hook_field_widget_info_alter().
855 856
 */
function taxonomy_field_widget_info_alter(&$info) {
857 858
  $info['options_select']['field_types'][] = 'taxonomy_term_reference';
  $info['options_buttons']['field_types'][] = 'taxonomy_term_reference';
859 860
}

861 862 863
/**
 * Implements hook_options_list().
 */
864 865 866