summaryrefslogtreecommitdiff
path: root/kolab.org/www/drupal-7.26/modules/taxonomy
diff options
context:
space:
mode:
Diffstat (limited to 'kolab.org/www/drupal-7.26/modules/taxonomy')
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy-term.tpl.php54
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.admin.inc984
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.api.php231
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.css13
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.info15
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.install917
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.js40
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.module2048
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.pages.inc181
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.test1980
-rw-r--r--kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.tokens.inc189
11 files changed, 6652 insertions, 0 deletions
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy-term.tpl.php b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy-term.tpl.php
new file mode 100644
index 0000000..a225c3a
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy-term.tpl.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to display a term.
+ *
+ * Available variables:
+ * - $name: (deprecated) The unsanitized name of the term. Use $term_name
+ * instead.
+ * - $content: An array of items for the content of the term (fields and
+ * description). Use render($content) to print them all, or print a subset
+ * such as render($content['field_example']). Use
+ * hide($content['field_example']) to temporarily suppress the printing of a
+ * given element.
+ * - $term_url: Direct URL of the current term.
+ * - $term_name: Name of the current term.
+ * - $classes: String of classes that can be used to style contextually through
+ * CSS. It can be manipulated through the variable $classes_array from
+ * preprocess functions. The default values can be one or more of the following:
+ * - taxonomy-term: The current template type, i.e., "theming hook".
+ * - vocabulary-[vocabulary-name]: The vocabulary to which the term belongs to.
+ * For example, if the term is a "Tag" it would result in "vocabulary-tag".
+ *
+ * Other variables:
+ * - $term: Full term object. Contains data that may not be safe.
+ * - $view_mode: View mode, e.g. 'full', 'teaser'...
+ * - $page: Flag for the full page state.
+ * - $classes_array: Array of html class attribute values. It is flattened
+ * into a string within the variable $classes.
+ * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in
+ * teaser listings.
+ * - $id: Position of the term. Increments each time it's output.
+ * - $is_front: Flags true when presented in the front page.
+ * - $logged_in: Flags true when the current user is a logged-in member.
+ * - $is_admin: Flags true when the current user is an administrator.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_taxonomy_term()
+ * @see template_process()
+ *
+ * @ingroup themeable
+ */
+?>
+<div id="taxonomy-term-<?php print $term->tid; ?>" class="<?php print $classes; ?>">
+
+ <?php if (!$page): ?>
+ <h2><a href="<?php print $term_url; ?>"><?php print $term_name; ?></a></h2>
+ <?php endif; ?>
+
+ <div class="content">
+ <?php print render($content); ?>
+ </div>
+
+</div>
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.admin.inc b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.admin.inc
new file mode 100644
index 0000000..828fde0
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.admin.inc
@@ -0,0 +1,984 @@
+<?php
+
+/**
+ * @file
+ * Administrative page callbacks for the taxonomy module.
+ */
+
+/**
+ * Form builder to list and manage vocabularies.
+ *
+ * @ingroup forms
+ * @see taxonomy_overview_vocabularies_submit()
+ * @see theme_taxonomy_overview_vocabularies()
+ */
+function taxonomy_overview_vocabularies($form) {
+ $vocabularies = taxonomy_get_vocabularies();
+ $form['#tree'] = TRUE;
+ foreach ($vocabularies as $vocabulary) {
+ $form[$vocabulary->vid]['#vocabulary'] = $vocabulary;
+ $form[$vocabulary->vid]['name'] = array('#markup' => check_plain($vocabulary->name));
+ $form[$vocabulary->vid]['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight for @title', array('@title' => $vocabulary->name)),
+ '#title_display' => 'invisible',
+ '#delta' => 10,
+ '#default_value' => $vocabulary->weight,
+ );
+ $form[$vocabulary->vid]['edit'] = array('#type' => 'link', '#title' => t('edit vocabulary'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name/edit");
+ $form[$vocabulary->vid]['list'] = array('#type' => 'link', '#title' => t('list terms'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name");
+ $form[$vocabulary->vid]['add'] = array('#type' => 'link', '#title' => t('add terms'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name/add");
+ }
+
+ // Only make this form include a submit button and weight if more than one
+ // vocabulary exists.
+ if (count($vocabularies) > 1) {
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+ }
+ elseif (isset($vocabulary)) {
+ unset($form[$vocabulary->vid]['weight']);
+ }
+ return $form;
+}
+
+/**
+ * Submit handler for vocabularies overview. Updates changed vocabulary weights.
+ *
+ * @see taxonomy_overview_vocabularies()
+ */
+function taxonomy_overview_vocabularies_submit($form, &$form_state) {
+ foreach ($form_state['values'] as $vid => $vocabulary) {
+ if (is_numeric($vid) && $form[$vid]['#vocabulary']->weight != $form_state['values'][$vid]['weight']) {
+ $form[$vid]['#vocabulary']->weight = $form_state['values'][$vid]['weight'];
+ taxonomy_vocabulary_save($form[$vid]['#vocabulary']);
+ }
+ }
+ drupal_set_message(t('The configuration options have been saved.'));
+}
+
+/**
+ * Returns HTML for the vocabulary overview form as a sortable list of vocabularies.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @see taxonomy_overview_vocabularies()
+ * @ingroup themeable
+ */
+function theme_taxonomy_overview_vocabularies($variables) {
+ $form = $variables['form'];
+
+ $rows = array();
+
+ foreach (element_children($form) as $key) {
+ if (isset($form[$key]['name'])) {
+ $vocabulary = &$form[$key];
+
+ $row = array();
+ $row[] = drupal_render($vocabulary['name']);
+ if (isset($vocabulary['weight'])) {
+ $vocabulary['weight']['#attributes']['class'] = array('vocabulary-weight');
+ $row[] = drupal_render($vocabulary['weight']);
+ }
+ $row[] = drupal_render($vocabulary['edit']);
+ $row[] = drupal_render($vocabulary['list']);
+ $row[] = drupal_render($vocabulary['add']);
+ $rows[] = array('data' => $row, 'class' => array('draggable'));
+ }
+ }
+
+ $header = array(t('Vocabulary name'));
+ if (isset($form['actions'])) {
+ $header[] = t('Weight');
+ drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'vocabulary-weight');
+ }
+ $header[] = array('data' => t('Operations'), 'colspan' => '3');
+ return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No vocabularies available. <a href="@link">Add vocabulary</a>.', array('@link' => url('admin/structure/taxonomy/add'))), 'attributes' => array('id' => 'taxonomy'))) . drupal_render_children($form);
+}
+
+/**
+ * Form builder for the vocabulary editing form.
+ *
+ * @ingroup forms
+ * @see taxonomy_form_vocabulary_submit()
+ * @see taxonomy_form_vocabulary_validate()
+ */
+function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
+ // During initial form build, add the entity to the form state for use
+ // during form building and processing. During a rebuild, use what is in the
+ // form state.
+ if (!isset($form_state['vocabulary'])) {
+ $vocabulary = is_object($edit) ? $edit : (object) $edit;
+ $defaults = array(
+ 'name' => '',
+ 'machine_name' => '',
+ 'description' => '',
+ 'hierarchy' => 0,
+ 'weight' => 0,
+ );
+ foreach ($defaults as $key => $value) {
+ if (!isset($vocabulary->$key)) {
+ $vocabulary->$key = $value;
+ }
+ }
+ $form_state['vocabulary'] = $vocabulary;
+ }
+ else {
+ $vocabulary = $form_state['vocabulary'];
+ }
+
+ // @todo Legacy support. Modules are encouraged to access the entity using
+ // $form_state. Remove in Drupal 8.
+ $form['#vocabulary'] = $form_state['vocabulary'];
+
+ // Check whether we need a deletion confirmation form.
+ if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
+ return taxonomy_vocabulary_confirm_delete($form, $form_state, $form_state['values']['vid']);
+ }
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name'),
+ '#default_value' => $vocabulary->name,
+ '#maxlength' => 255,
+ '#required' => TRUE,
+ );
+ $form['machine_name'] = array(
+ '#type' => 'machine_name',
+ '#default_value' => $vocabulary->machine_name,
+ '#maxlength' => 255,
+ '#machine_name' => array(
+ 'exists' => 'taxonomy_vocabulary_machine_name_load',
+ ),
+ );
+ $form['old_machine_name'] = array(
+ '#type' => 'value',
+ '#value' => $vocabulary->machine_name,
+ );
+ $form['description'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Description'),
+ '#default_value' => $vocabulary->description,
+ );
+ // Set the hierarchy to "multiple parents" by default. This simplifies the
+ // vocabulary form and standardizes the term form.
+ $form['hierarchy'] = array(
+ '#type' => 'value',
+ '#value' => '0',
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+ if (isset($vocabulary->vid)) {
+ $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+ $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid);
+ $form['module'] = array('#type' => 'value', '#value' => $vocabulary->module);
+ }
+ $form['#validate'][] = 'taxonomy_form_vocabulary_validate';
+
+ return $form;
+}
+
+/**
+ * Form validation handler for taxonomy_form_vocabulary().
+ *
+ * Makes sure that the machine name of the vocabulary is not in the
+ * disallowed list (names that conflict with menu items, such as 'list'
+ * and 'add').
+ *
+ * @see taxonomy_form_vocabulary()
+ * @see taxonomy_form_vocabulary_submit()
+ */
+function taxonomy_form_vocabulary_validate($form, &$form_state) {
+ // During the deletion there is no 'machine_name' key
+ if (isset($form_state['values']['machine_name'])) {
+ // Do not allow machine names to conflict with taxonomy path arguments.
+ $machine_name = $form_state['values']['machine_name'];
+ $disallowed = array('add', 'list');
+ if (in_array($machine_name, $disallowed)) {
+ form_set_error('machine_name', t('The machine-readable name cannot be "add" or "list".'));
+ }
+ }
+}
+
+/**
+ * Form submission handler for taxonomy_form_vocabulary().
+ *
+ * @see taxonomy_form_vocabulary()
+ * @see taxonomy_form_vocabulary_validate()
+ */
+function taxonomy_form_vocabulary_submit($form, &$form_state) {
+ if ($form_state['triggering_element']['#value'] == t('Delete')) {
+ // Rebuild the form to confirm vocabulary deletion.
+ $form_state['rebuild'] = TRUE;
+ $form_state['confirm_delete'] = TRUE;
+ return;
+ }
+
+ $vocabulary = $form_state['vocabulary'];
+ entity_form_submit_build_entity('taxonomy_vocabulary', $vocabulary, $form, $form_state);
+
+ switch (taxonomy_vocabulary_save($vocabulary)) {
+ case SAVED_NEW:
+ drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
+ watchdog('taxonomy', 'Created new vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
+ break;
+
+ case SAVED_UPDATED:
+ drupal_set_message(t('Updated vocabulary %name.', array('%name' => $vocabulary->name)));
+ watchdog('taxonomy', 'Updated vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
+ break;
+ }
+
+ $form_state['values']['vid'] = $vocabulary->vid;
+ $form_state['vid'] = $vocabulary->vid;
+ $form_state['redirect'] = 'admin/structure/taxonomy';
+}
+
+/**
+ * Form builder for the taxonomy terms overview.
+ *
+ * Display a tree of all the terms in a vocabulary, with options to edit
+ * each one. The form is made drag and drop by the theme function.
+ *
+ * @ingroup forms
+ * @see taxonomy_overview_terms_submit()
+ * @see theme_taxonomy_overview_terms()
+ */
+function taxonomy_overview_terms($form, &$form_state, $vocabulary) {
+ global $pager_page_array, $pager_total, $pager_total_items;
+
+ // Check for confirmation forms.
+ if (isset($form_state['confirm_reset_alphabetical'])) {
+ return taxonomy_vocabulary_confirm_reset_alphabetical($form, $form_state, $vocabulary->vid);
+ }
+
+ $form['#vocabulary'] = $vocabulary;
+ $form['#tree'] = TRUE;
+ $form['#parent_fields'] = FALSE;
+
+ $page = isset($_GET['page']) ? $_GET['page'] : 0;
+ $page_increment = variable_get('taxonomy_terms_per_page_admin', 100); // Number of terms per page.
+ $page_entries = 0; // Elements shown on this page.
+ $before_entries = 0; // Elements at the root level before this page.
+ $after_entries = 0; // Elements at the root level after this page.
+ $root_entries = 0; // Elements at the root level on this page.
+
+ // Terms from previous and next pages are shown if the term tree would have
+ // been cut in the middle. Keep track of how many extra terms we show on each
+ // page of terms.
+ $back_step = NULL;
+ $forward_step = 0;
+
+ // An array of the terms to be displayed on this page.
+ $current_page = array();
+
+ $delta = 0;
+ $term_deltas = array();
+ $tree = taxonomy_get_tree($vocabulary->vid);
+ $term = current($tree);
+ do {
+ // In case this tree is completely empty.
+ if (empty($term)) {
+ break;
+ }
+ $delta++;
+ // Count entries before the current page.
+ if ($page && ($page * $page_increment) > $before_entries && !isset($back_step)) {
+ $before_entries++;
+ continue;
+ }
+ // Count entries after the current page.
+ elseif ($page_entries > $page_increment && isset($complete_tree)) {
+ $after_entries++;
+ continue;
+ }
+
+ // Do not let a term start the page that is not at the root.
+ if (isset($term->depth) && ($term->depth > 0) && !isset($back_step)) {
+ $back_step = 0;
+ while ($pterm = prev($tree)) {
+ $before_entries--;
+ $back_step++;
+ if ($pterm->depth == 0) {
+ prev($tree);
+ continue 2; // Jump back to the start of the root level parent.
+ }
+ }
+ }
+ $back_step = isset($back_step) ? $back_step : 0;
+
+ // Continue rendering the tree until we reach the a new root item.
+ if ($page_entries >= $page_increment + $back_step + 1 && $term->depth == 0 && $root_entries > 1) {
+ $complete_tree = TRUE;
+ // This new item at the root level is the first item on the next page.
+ $after_entries++;
+ continue;
+ }
+ if ($page_entries >= $page_increment + $back_step) {
+ $forward_step++;
+ }
+
+ // Finally, if we've gotten down this far, we're rendering a term on this page.
+ $page_entries++;
+ $term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0;
+ $key = 'tid:' . $term->tid . ':' . $term_deltas[$term->tid];
+
+ // Keep track of the first term displayed on this page.
+ if ($page_entries == 1) {
+ $form['#first_tid'] = $term->tid;
+ }
+ // Keep a variable to make sure at least 2 root elements are displayed.
+ if ($term->parents[0] == 0) {
+ $root_entries++;
+ }
+ $current_page[$key] = $term;
+ } while ($term = next($tree));
+
+ // Because we didn't use a pager query, set the necessary pager variables.
+ $total_entries = $before_entries + $page_entries + $after_entries;
+ $pager_total_items[0] = $total_entries;
+ $pager_page_array[0] = $page;
+ $pager_total[0] = ceil($total_entries / $page_increment);
+
+ // If this form was already submitted once, it's probably hit a validation
+ // error. Ensure the form is rebuilt in the same order as the user submitted.
+ if (!empty($form_state['input'])) {
+ $order = array_flip(array_keys($form_state['input'])); // Get the $_POST order.
+ $current_page = array_merge($order, $current_page); // Update our form with the new order.
+ foreach ($current_page as $key => $term) {
+ // Verify this is a term for the current page and set at the current depth.
+ if (is_array($form_state['input'][$key]) && is_numeric($form_state['input'][$key]['tid'])) {
+ $current_page[$key]->depth = $form_state['input'][$key]['depth'];
+ }
+ else {
+ unset($current_page[$key]);
+ }
+ }
+ }
+
+ // Build the actual form.
+ foreach ($current_page as $key => $term) {
+ // Save the term for the current page so we don't have to load it a second time.
+ $form[$key]['#term'] = (array) $term;
+ if (isset($term->parents)) {
+ $form[$key]['#term']['parent'] = $term->parent = $term->parents[0];
+ unset($form[$key]['#term']['parents'], $term->parents);
+ }
+
+ $form[$key]['view'] = array('#type' => 'link', '#title' => $term->name, '#href' => "taxonomy/term/$term->tid");
+ if ($vocabulary->hierarchy < 2 && count($tree) > 1) {
+ $form['#parent_fields'] = TRUE;
+ $form[$key]['tid'] = array(
+ '#type' => 'hidden',
+ '#value' => $term->tid
+ );
+ $form[$key]['parent'] = array(
+ '#type' => 'hidden',
+ // Yes, default_value on a hidden. It needs to be changeable by the javascript.
+ '#default_value' => $term->parent,
+ );
+ $form[$key]['depth'] = array(
+ '#type' => 'hidden',
+ // Same as above, the depth is modified by javascript, so it's a default_value.
+ '#default_value' => $term->depth,
+ );
+ $form[$key]['weight'] = array(
+ '#type' => 'weight',
+ '#delta' => $delta,
+ '#title_display' => 'invisible',
+ '#title' => t('Weight for added term'),
+ '#default_value' => $term->weight,
+ );
+ }
+ $form[$key]['edit'] = array('#type' => 'link', '#title' => t('edit'), '#href' => 'taxonomy/term/' . $term->tid . '/edit', '#options' => array('query' => drupal_get_destination()));
+ }
+
+ $form['#total_entries'] = $total_entries;
+ $form['#page_increment'] = $page_increment;
+ $form['#page_entries'] = $page_entries;
+ $form['#back_step'] = $back_step;
+ $form['#forward_step'] = $forward_step;
+ $form['#empty_text'] = t('No terms available. <a href="@link">Add term</a>.', array('@link' => url('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add')));
+
+ if ($vocabulary->hierarchy < 2 && count($tree) > 1) {
+ $form['actions'] = array('#type' => 'actions', '#tree' => FALSE);
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save')
+ );
+ $form['actions']['reset_alphabetical'] = array(
+ '#type' => 'submit',
+ '#value' => t('Reset to alphabetical')
+ );
+ $form_state['redirect'] = array($_GET['q'], (isset($_GET['page']) ? array('query' => array('page' => $_GET['page'])) : array()));
+ }
+
+ return $form;
+}
+
+/**
+ * Submit handler for terms overview form.
+ *
+ * Rather than using a textfield or weight field, this form depends entirely
+ * upon the order of form elements on the page to determine new weights.
+ *
+ * Because there might be hundreds or thousands of taxonomy terms that need to
+ * be ordered, terms are weighted from 0 to the number of terms in the
+ * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
+ * lowest to highest, but are not necessarily sequential. Numbers may be skipped
+ * when a term has children so that reordering is minimal when a child is
+ * added or removed from a term.
+ *
+ * @see taxonomy_overview_terms()
+ */
+function taxonomy_overview_terms_submit($form, &$form_state) {
+ if ($form_state['triggering_element']['#value'] == t('Reset to alphabetical')) {
+ // Execute the reset action.
+ if ($form_state['values']['reset_alphabetical'] === TRUE) {
+ return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
+ }
+ // Rebuild the form to confirm the reset action.
+ $form_state['rebuild'] = TRUE;
+ $form_state['confirm_reset_alphabetical'] = TRUE;
+ return;
+ }
+
+ // Sort term order based on weight.
+ uasort($form_state['values'], 'drupal_sort_weight');
+
+ $vocabulary = $form['#vocabulary'];
+ $hierarchy = 0; // Update the current hierarchy type as we go.
+
+ $changed_terms = array();
+ $tree = taxonomy_get_tree($vocabulary->vid);
+
+ if (empty($tree)) {
+ return;
+ }
+
+ // Build a list of all terms that need to be updated on previous pages.
+ $weight = 0;
+ $term = (array) $tree[0];
+ while ($term['tid'] != $form['#first_tid']) {
+ if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
+ $term['parent'] = $term['parents'][0];
+ $term['weight'] = $weight;
+ $changed_terms[$term['tid']] = $term;
+ }
+ $weight++;
+ $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
+ $term = (array) $tree[$weight];
+ }
+
+ // Renumber the current page weights and assign any new parents.
+ $level_weights = array();
+ foreach ($form_state['values'] as $tid => $values) {
+ if (isset($form[$tid]['#term'])) {
+ $term = $form[$tid]['#term'];
+ // Give terms at the root level a weight in sequence with terms on previous pages.
+ if ($values['parent'] == 0 && $term['weight'] != $weight) {
+ $term['weight'] = $weight;
+ $changed_terms[$term['tid']] = $term;
+ }
+ // Terms not at the root level can safely start from 0 because they're all on this page.
+ elseif ($values['parent'] > 0) {
+ $level_weights[$values['parent']] = isset($level_weights[$values['parent']]) ? $level_weights[$values['parent']] + 1 : 0;
+ if ($level_weights[$values['parent']] != $term['weight']) {
+ $term['weight'] = $level_weights[$values['parent']];
+ $changed_terms[$term['tid']] = $term;
+ }
+ }
+ // Update any changed parents.
+ if ($values['parent'] != $term['parent']) {
+ $term['parent'] = $values['parent'];
+ $changed_terms[$term['tid']] = $term;
+ }
+ $hierarchy = $term['parent'] != 0 ? 1 : $hierarchy;
+ $weight++;
+ }
+ }
+
+ // Build a list of all terms that need to be updated on following pages.
+ for ($weight; $weight < count($tree); $weight++) {
+ $term = (array) $tree[$weight];
+ if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
+ $term['parent'] = $term['parents'][0];
+ $term['weight'] = $weight;
+ $changed_terms[$term['tid']] = $term;
+ }
+ $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
+ }
+
+ // Save all updated terms.
+ foreach ($changed_terms as $changed) {
+ $term = (object) $changed;
+ // Update term_hierachy and term_data directly since we don't have a
+ // fully populated term object to save.
+ db_update('taxonomy_term_hierarchy')
+ ->fields(array('parent' => $term->parent))
+ ->condition('tid', $term->tid, '=')
+ ->execute();
+
+ db_update('taxonomy_term_data')
+ ->fields(array('weight' => $term->weight))
+ ->condition('tid', $term->tid, '=')
+ ->execute();
+ }
+
+ // Update the vocabulary hierarchy to flat or single hierarchy.
+ if ($vocabulary->hierarchy != $hierarchy) {
+ $vocabulary->hierarchy = $hierarchy;
+ taxonomy_vocabulary_save($vocabulary);
+ }
+ drupal_set_message(t('The configuration options have been saved.'));
+}
+
+/**
+ * Returns HTML for a terms overview form as a sortable list of terms.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @see taxonomy_overview_terms()
+ * @ingroup themeable
+ */
+function theme_taxonomy_overview_terms($variables) {
+ $form = $variables['form'];
+
+ $page_increment = $form['#page_increment'];
+ $page_entries = $form['#page_entries'];
+ $back_step = $form['#back_step'];
+ $forward_step = $form['#forward_step'];
+
+ // Add drag and drop if parent fields are present in the form.
+ if ($form['#parent_fields']) {
+ drupal_add_tabledrag('taxonomy', 'match', 'parent', 'term-parent', 'term-parent', 'term-id', FALSE);
+ drupal_add_tabledrag('taxonomy', 'depth', 'group', 'term-depth', NULL, NULL, FALSE);
+ drupal_add_js(drupal_get_path('module', 'taxonomy') . '/taxonomy.js');
+ drupal_add_js(array('taxonomy' => array('backStep' => $back_step, 'forwardStep' => $forward_step)), 'setting');
+ drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
+ }
+ drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'term-weight');
+
+ $errors = form_get_errors() != FALSE ? form_get_errors() : array();
+ $rows = array();
+ foreach (element_children($form) as $key) {
+ if (isset($form[$key]['#term'])) {
+ $term = &$form[$key];
+
+ $row = array();
+ $row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', array('size' => $term['#term']['depth'])) : ''). drupal_render($term['view']);
+ if ($form['#parent_fields']) {
+ $term['tid']['#attributes']['class'] = array('term-id');
+ $term['parent']['#attributes']['class'] = array('term-parent');
+ $term['depth']['#attributes']['class'] = array('term-depth');
+ $row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']);
+ }
+ $term['weight']['#attributes']['class'] = array('term-weight');
+ $row[] = drupal_render($term['weight']);
+ $row[] = drupal_render($term['edit']);
+ $row = array('data' => $row);
+ $rows[$key] = $row;
+ }
+ }
+
+ // Add necessary classes to rows.
+ $row_position = 0;
+ foreach ($rows as $key => $row) {
+ $rows[$key]['class'] = array();
+ if (isset($form['#parent_fields'])) {
+ $rows[$key]['class'][] = 'draggable';
+ }
+
+ // Add classes that mark which terms belong to previous and next pages.
+ if ($row_position < $back_step || $row_position >= $page_entries - $forward_step) {
+ $rows[$key]['class'][] = 'taxonomy-term-preview';
+ }
+
+ if ($row_position !== 0 && $row_position !== count($rows) - 1) {
+ if ($row_position == $back_step - 1 || $row_position == $page_entries - $forward_step - 1) {
+ $rows[$key]['class'][] = 'taxonomy-term-divider-top';
+ }
+ elseif ($row_position == $back_step || $row_position == $page_entries - $forward_step) {
+ $rows[$key]['class'][] = 'taxonomy-term-divider-bottom';
+ }
+ }
+
+ // Add an error class if this row contains a form error.
+ foreach ($errors as $error_key => $error) {
+ if (strpos($error_key, $key) === 0) {
+ $rows[$key]['class'][] = 'error';
+ }
+ }
+ $row_position++;
+ }
+
+ if (empty($rows)) {
+ $rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '3'));
+ }
+
+ $header = array(t('Name'), t('Weight'), t('Operations'));
+ $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'taxonomy')));
+ $output .= drupal_render_children($form);
+ $output .= theme('pager');
+
+ return $output;
+}
+
+/**
+ * Form function for the term edit form.
+ *
+ * @ingroup forms
+ * @see taxonomy_form_term_submit()
+ */
+function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = NULL) {
+ // During initial form build, add the term entity to the form state for use
+ // during form building and processing. During a rebuild, use what is in the
+ // form state.
+ if (!isset($form_state['term'])) {
+ $term = is_object($edit) ? $edit : (object) $edit;
+ if (!isset($vocabulary) && isset($term->vid)) {
+ $vocabulary = taxonomy_vocabulary_load($term->vid);
+ }
+ $defaults = array(
+ 'name' => '',
+ 'description' => '',
+ 'format' => NULL,
+ 'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL,
+ 'tid' => NULL,
+ 'weight' => 0,
+ );
+ foreach ($defaults as $key => $value) {
+ if (!isset($term->$key)) {
+ $term->$key = $value;
+ }
+ }
+ $form_state['term'] = $term;
+ }
+ else {
+ $term = $form_state['term'];
+ if (!isset($vocabulary) && isset($term->vid)) {
+ $vocabulary = taxonomy_vocabulary_load($term->vid);
+ }
+ }
+
+ $parent = array_keys(taxonomy_get_parents($term->tid));
+ $form['#term'] = (array) $term;
+ $form['#term']['parent'] = $parent;
+ $form['#vocabulary'] = $vocabulary;
+
+ // Check for confirmation forms.
+ if (isset($form_state['confirm_delete'])) {
+ return array_merge($form, taxonomy_term_confirm_delete($form, $form_state, $term->tid));
+ }
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Name'),
+ '#default_value' => $term->name,
+ '#maxlength' => 255,
+ '#required' => TRUE,
+ '#weight' => -5,
+ );
+ $form['description'] = array(
+ '#type' => 'text_format',
+ '#title' => t('Description'),
+ '#default_value' => $term->description,
+ '#format' => $term->format,
+ '#weight' => 0,
+ );
+
+ $form['vocabulary_machine_name'] = array(
+ '#type' => 'value',
+ '#value' => isset($term->vocabulary_machine_name) ? $term->vocabulary_machine_name : $vocabulary->name,
+ );
+
+ $langcode = entity_language('taxonomy_term', $term);
+ field_attach_form('taxonomy_term', $term, $form, $form_state, $langcode);
+
+ $form['relations'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Relations'),
+ '#collapsible' => TRUE,
+ '#collapsed' => $vocabulary->hierarchy < 2,
+ '#weight' => 10,
+ );
+
+ // taxonomy_get_tree and taxonomy_get_parents may contain large numbers of
+ // items so we check for taxonomy_override_selector before loading the
+ // full vocabulary. Contrib modules can then intercept before
+ // hook_form_alter to provide scalable alternatives.
+ if (!variable_get('taxonomy_override_selector', FALSE)) {
+ $parent = array_keys(taxonomy_get_parents($term->tid));
+ $children = taxonomy_get_tree($vocabulary->vid, $term->tid);
+
+ // A term can't be the child of itself, nor of its children.
+ foreach ($children as $child) {
+ $exclude[] = $child->tid;
+ }
+ $exclude[] = $term->tid;
+
+ $tree = taxonomy_get_tree($vocabulary->vid);
+ $options = array('<' . t('root') . '>');
+ if (empty($parent)) {
+ $parent = array(0);
+ }
+ foreach ($tree as $item) {
+ if (!in_array($item->tid, $exclude)) {
+ $options[$item->tid] = str_repeat('-', $item->depth) . $item->name;
+ }
+ }
+ $form['relations']['parent'] = array(
+ '#type' => 'select',
+ '#title' => t('Parent terms'),
+ '#options' => $options,
+ '#default_value' => $parent,
+ '#multiple' => TRUE,
+ );
+
+ }
+ $form['relations']['weight'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Weight'),
+ '#size' => 6,
+ '#default_value' => $term->weight,
+ '#description' => t('Terms are displayed in ascending order by weight.'),
+ '#required' => TRUE,
+ );
+ $form['vid'] = array(
+ '#type' => 'value',
+ '#value' => $vocabulary->vid,
+ );
+ $form['tid'] = array(
+ '#type' => 'value',
+ '#value' => $term->tid,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#weight' => 5,
+ );
+
+ if ($term->tid) {
+ $form['actions']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete'),
+ '#access' => user_access("delete terms in $vocabulary->vid") || user_access('administer taxonomy'),
+ '#weight' => 10,
+ );
+ }
+ else {
+ $form_state['redirect'] = $_GET['q'];
+ }
+
+ return $form;
+}
+
+/**
+ * Validation handler for the term form.
+ *
+ * @see taxonomy_form_term()
+ */
+function taxonomy_form_term_validate($form, &$form_state) {
+ entity_form_field_validate('taxonomy_term', $form, $form_state);
+
+ // Ensure numeric values.
+ if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
+ form_set_error('weight', t('Weight value must be numeric.'));
+ }
+}
+
+/**
+ * Submit handler to insert or update a term.
+ *
+ * @see taxonomy_form_term()
+ */
+function taxonomy_form_term_submit($form, &$form_state) {
+ if ($form_state['triggering_element']['#value'] == t('Delete')) {
+ // Execute the term deletion.
+ if ($form_state['values']['delete'] === TRUE) {
+ return taxonomy_term_confirm_delete_submit($form, $form_state);
+ }
+ // Rebuild the form to confirm term deletion.
+ $form_state['rebuild'] = TRUE;
+ $form_state['confirm_delete'] = TRUE;
+ return;
+ }
+
+ $term = taxonomy_form_term_submit_build_taxonomy_term($form, $form_state);
+
+ $status = taxonomy_term_save($term);
+ switch ($status) {
+ case SAVED_NEW:
+ drupal_set_message(t('Created new term %term.', array('%term' => $term->name)));
+ watchdog('taxonomy', 'Created new term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
+ break;
+ case SAVED_UPDATED:
+ drupal_set_message(t('Updated term %term.', array('%term' => $term->name)));
+ watchdog('taxonomy', 'Updated term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
+ // Clear the page and block caches to avoid stale data.
+ cache_clear_all();
+ break;
+ }
+
+ $current_parent_count = count($form_state['values']['parent']);
+ $previous_parent_count = count($form['#term']['parent']);
+ // Root doesn't count if it's the only parent.
+ if ($current_parent_count == 1 && isset($form_state['values']['parent'][0])) {
+ $current_parent_count = 0;
+ $form_state['values']['parent'] = array();
+ }
+
+ // If the number of parents has been reduced to one or none, do a check on the
+ // parents of every term in the vocabulary value.
+ if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
+ taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
+ }
+ // If we've increased the number of parents and this is a single or flat
+ // hierarchy, update the vocabulary immediately.
+ elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']->hierarchy < 2) {
+ $form['#vocabulary']->hierarchy = $current_parent_count == 1 ? 1 : 2;
+ taxonomy_vocabulary_save($form['#vocabulary']);
+ }
+
+ $form_state['values']['tid'] = $term->tid;
+ $form_state['tid'] = $term->tid;
+}
+
+/**
+ * Updates the form state's term entity by processing this submission's values.
+ */
+function taxonomy_form_term_submit_build_taxonomy_term($form, &$form_state) {
+ $term = $form_state['term'];
+ entity_form_submit_build_entity('taxonomy_term', $term, $form, $form_state);
+
+ // Convert text_format field into values expected by taxonomy_term_save().
+ $description = $form_state['values']['description'];
+ $term->description = $description['value'];
+ $term->format = $description['format'];
+ return $term;
+}
+
+/**
+ * Form builder for the term delete form.
+ *
+ * @ingroup forms
+ * @see taxonomy_term_confirm_delete_submit()
+ */
+function taxonomy_term_confirm_delete($form, &$form_state, $tid) {
+ $term = taxonomy_term_load($tid);
+
+ // Always provide entity id in the same form key as in the entity edit form.
+ $form['tid'] = array('#type' => 'value', '#value' => $tid);
+
+ $form['#term'] = $term;
+ $form['type'] = array('#type' => 'value', '#value' => 'term');
+ $form['name'] = array('#type' => 'value', '#value' => $term->name);
+ $form['vocabulary_machine_name'] = array('#type' => 'value', '#value' => $term->vocabulary_machine_name);
+ $form['delete'] = array('#type' => 'value', '#value' => TRUE);
+ return confirm_form($form,
+ t('Are you sure you want to delete the term %title?',
+ array('%title' => $term->name)),
+ 'admin/structure/taxonomy',
+ t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
+ t('Delete'),
+ t('Cancel'));
+}
+
+/**
+ * Submit handler to delete a term after confirmation.
+ *
+ * @see taxonomy_term_confirm_delete()
+ */
+function taxonomy_term_confirm_delete_submit($form, &$form_state) {
+ taxonomy_term_delete($form_state['values']['tid']);
+ taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
+ drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name'])));
+ watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
+ $form_state['redirect'] = 'admin/structure/taxonomy';
+ cache_clear_all();
+ return;
+}
+
+/**
+ * Form builder for the vocabulary delete confirmation form.
+ *
+ * @ingroup forms
+ * @see taxonomy_vocabulary_confirm_delete_submit()
+ */
+function taxonomy_vocabulary_confirm_delete($form, &$form_state, $vid) {
+ $vocabulary = taxonomy_vocabulary_load($vid);
+
+ // Always provide entity id in the same form key as in the entity edit form.
+ $form['vid'] = array('#type' => 'value', '#value' => $vid);
+
+ $form['#vocabulary'] = $vocabulary;
+ $form['#id'] = 'taxonomy_vocabulary_confirm_delete';
+ $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
+ $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
+ $form['#submit'] = array('taxonomy_vocabulary_confirm_delete_submit');
+ return confirm_form($form,
+ t('Are you sure you want to delete the vocabulary %title?',
+ array('%title' => $vocabulary->name)),
+ 'admin/structure/taxonomy',
+ t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
+ t('Delete'),
+ t('Cancel'));
+}
+
+/**
+ * Submit handler to delete a vocabulary after confirmation.
+ *
+ * @see taxonomy_vocabulary_confirm_delete()
+ */
+function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
+ $status = taxonomy_vocabulary_delete($form_state['values']['vid']);
+ drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
+ watchdog('taxonomy', 'Deleted vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
+ $form_state['redirect'] = 'admin/structure/taxonomy';
+ cache_clear_all();
+ return;
+}
+
+/**
+ * Form builder to confirm resetting a vocabulary to alphabetical order.
+ *
+ * @ingroup forms
+ * @see taxonomy_vocabulary_confirm_reset_alphabetical_submit()
+ */
+function taxonomy_vocabulary_confirm_reset_alphabetical($form, &$form_state, $vid) {
+ $vocabulary = taxonomy_vocabulary_load($vid);
+
+ $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
+ $form['vid'] = array('#type' => 'value', '#value' => $vid);
+ $form['machine_name'] = array('#type' => 'value', '#value' => $vocabulary->machine_name);
+ $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
+ $form['reset_alphabetical'] = array('#type' => 'value', '#value' => TRUE);
+ return confirm_form($form,
+ t('Are you sure you want to reset the vocabulary %title to alphabetical order?',
+ array('%title' => $vocabulary->name)),
+ 'admin/structure/taxonomy/' . $vocabulary->machine_name,
+ t('Resetting a vocabulary will discard all custom ordering and sort items alphabetically.'),
+ t('Reset to alphabetical'),
+ t('Cancel'));
+}
+
+/**
+ * Submit handler to reset a vocabulary to alphabetical order after confirmation.
+ *
+ * @see taxonomy_vocabulary_confirm_reset_alphabetical()
+ */
+function taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, &$form_state) {
+ db_update('taxonomy_term_data')
+ ->fields(array('weight' => 0))
+ ->condition('vid', $form_state['values']['vid'])
+ ->execute();
+ drupal_set_message(t('Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name'])));
+ watchdog('taxonomy', 'Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
+ $form_state['redirect'] = 'admin/structure/taxonomy/' . $form_state['values']['machine_name'];
+}
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.api.php b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.api.php
new file mode 100644
index 0000000..b9c23db
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.api.php
@@ -0,0 +1,231 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Taxonomy module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Act on taxonomy vocabularies when loaded.
+ *
+ * Modules implementing this hook can act on the vocabulary objects before they
+ * are returned by taxonomy_vocabulary_load_multiple().
+ *
+ * @param $vocabulary
+ * An array of taxonomy vocabulary objects.
+ */
+function hook_taxonomy_vocabulary_load($vocabularies) {
+ $result = db_select('mytable', 'm')
+ ->fields('m', array('vid', 'foo'))
+ ->condition('m.vid', array_keys($vocabularies), 'IN')
+ ->execute();
+ foreach ($result as $record) {
+ $vocabularies[$record->vid]->foo = $record->foo;
+ }
+}
+
+/**
+ * Act on taxonomy vocabularies before they are saved.
+ *
+ * Modules implementing this hook can act on the vocabulary object before it is
+ * inserted or updated.
+ *
+ * @param $vocabulary
+ * A taxonomy vocabulary object.
+ */
+function hook_taxonomy_vocabulary_presave($vocabulary) {
+ $vocabulary->foo = 'bar';
+}
+
+/**
+ * Act on taxonomy vocabularies when inserted.
+ *
+ * Modules implementing this hook can act on the vocabulary object when saved
+ * to the database.
+ *
+ * @param $vocabulary
+ * A taxonomy vocabulary object.
+ */
+function hook_taxonomy_vocabulary_insert($vocabulary) {
+ if ($vocabulary->machine_name == 'my_vocabulary') {
+ $vocabulary->weight = 100;
+ }
+}
+
+/**
+ * Act on taxonomy vocabularies when updated.
+ *
+ * Modules implementing this hook can act on the vocabulary object when updated.
+ *
+ * @param $vocabulary
+ * A taxonomy vocabulary object.
+ */
+function hook_taxonomy_vocabulary_update($vocabulary) {
+ db_update('mytable')
+ ->fields(array('foo' => $vocabulary->foo))
+ ->condition('vid', $vocabulary->vid)
+ ->execute();
+}
+
+/**
+ * Respond to the deletion of taxonomy vocabularies.
+ *
+ * Modules implementing this hook can respond to the deletion of taxonomy
+ * vocabularies from the database.
+ *
+ * @param $vocabulary
+ * A taxonomy vocabulary object.
+ */
+function hook_taxonomy_vocabulary_delete($vocabulary) {
+ db_delete('mytable')
+ ->condition('vid', $vocabulary->vid)
+ ->execute();
+}
+
+/**
+ * Act on taxonomy terms when loaded.
+ *
+ * Modules implementing this hook can act on the term objects returned by
+ * taxonomy_term_load_multiple().
+ *
+ * For performance reasons, information to be added to term objects should be
+ * loaded in a single query for all terms where possible.
+ *
+ * Since terms are stored and retrieved from cache during a page request, avoid
+ * altering properties provided by the {taxonomy_term_data} table, since this
+ * may affect the way results are loaded from cache in subsequent calls.
+ *
+ * @param $terms
+ * An array of term objects, indexed by tid.
+ */
+function hook_taxonomy_term_load($terms) {
+ $result = db_select('mytable', 'm')
+ ->fields('m', array('tid', 'foo'))
+ ->condition('m.tid', array_keys($terms), 'IN')
+ ->execute();
+ foreach ($result as $record) {
+ $terms[$record->tid]->foo = $record->foo;
+ }
+}
+
+/**
+ * Act on taxonomy terms before they are saved.
+ *
+ * Modules implementing this hook can act on the term object before it is
+ * inserted or updated.
+ *
+ * @param $term
+ * A term object.
+ */
+function hook_taxonomy_term_presave($term) {
+ $term->foo = 'bar';
+}
+
+/**
+ * Act on taxonomy terms when inserted.
+ *
+ * Modules implementing this hook can act on the term object when saved to
+ * the database.
+ *
+ * @param $term
+ * A taxonomy term object.
+ */
+function hook_taxonomy_term_insert($term) {
+ db_insert('mytable')
+ ->fields(array(
+ 'tid' => $term->tid,
+ 'foo' => $term->foo,
+ ))
+ ->execute();
+}
+
+/**
+ * Act on taxonomy terms when updated.
+ *
+ * Modules implementing this hook can act on the term object when updated.
+ *
+ * @param $term
+ * A taxonomy term object.
+ */
+function hook_taxonomy_term_update($term) {
+ db_update('mytable')
+ ->fields(array('foo' => $term->foo))
+ ->condition('tid', $term->tid)
+ ->execute();
+}
+
+/**
+ * Respond to the deletion of taxonomy terms.
+ *
+ * Modules implementing this hook can respond to the deletion of taxonomy
+ * terms from the database.
+ *
+ * @param $term
+ * A taxonomy term object.
+ */
+function hook_taxonomy_term_delete($term) {
+ db_delete('mytable')
+ ->condition('tid', $term->tid)
+ ->execute();
+}
+
+/**
+ * Act on a taxonomy term that is being assembled before rendering.
+ *
+ * The module may add elements to $term->content prior to rendering. The
+ * structure of $term->content is a renderable array as expected by
+ * drupal_render().
+ *
+ * @param $term
+ * The term that is being assembled for rendering.
+ * @param $view_mode
+ * The $view_mode parameter from taxonomy_term_view().
+ * @param $langcode
+ * The language code used for rendering.
+ *
+ * @see hook_entity_view()
+ */
+function hook_taxonomy_term_view($term, $view_mode, $langcode) {
+ $term->content['my_additional_field'] = array(
+ '#markup' => $additional_field,
+ '#weight' => 10,
+ '#theme' => 'mymodule_my_additional_field',
+ );
+}
+
+/**
+ * Alter the results of taxonomy_term_view().
+ *
+ * This hook is called after the content has been assembled in a structured
+ * array and may be used for doing processing which requires that the complete
+ * taxonomy term content structure has been built.
+ *
+ * If the module wishes to act on the rendered HTML of the term rather than the
+ * structured content array, it may use this hook to add a #post_render
+ * callback. Alternatively, it could also implement
+ * hook_preprocess_taxonomy_term(). See drupal_render() and theme()
+ * documentation respectively for details.
+ *
+ * @param $build
+ * A renderable array representing the node content.
+ *
+ * @see hook_entity_view_alter()
+ */
+function hook_taxonomy_term_view_alter(&$build) {
+ if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
+ // Change its weight.
+ $build['an_additional_field']['#weight'] = -10;
+ }
+
+ // Add a #post_render callback to act on the rendered HTML of the term.
+ $build['#post_render'][] = 'my_module_node_post_render';
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.css b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.css
new file mode 100644
index 0000000..36cd641
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.css
@@ -0,0 +1,13 @@
+
+tr.taxonomy-term-preview {
+ background-color: #EEE;
+}
+tr.taxonomy-term-divider-top {
+ border-bottom: none;
+}
+tr.taxonomy-term-divider-bottom {
+ border-top: 1px dotted #CCC;
+}
+.taxonomy-term-description {
+ margin: 5px 0 20px;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.info b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.info
new file mode 100644
index 0000000..81e93f8
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.info
@@ -0,0 +1,15 @@
+name = Taxonomy
+description = Enables the categorization of content.
+package = Core
+version = VERSION
+core = 7.x
+dependencies[] = options
+files[] = taxonomy.module
+files[] = taxonomy.test
+configure = admin/structure/taxonomy
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.install b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.install
new file mode 100644
index 0000000..e3603e1
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.install
@@ -0,0 +1,917 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the taxonomy module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function taxonomy_uninstall() {
+ // Remove variables.
+ variable_del('taxonomy_override_selector');
+ variable_del('taxonomy_terms_per_page_admin');
+ // Remove taxonomy_term bundles.
+ $vocabularies = db_query("SELECT machine_name FROM {taxonomy_vocabulary}")->fetchCol();
+ foreach ($vocabularies as $vocabulary) {
+ field_attach_delete_bundle('taxonomy_term', $vocabulary);
+ }
+}
+
+/**
+ * Implements hook_schema().
+ */
+function taxonomy_schema() {
+ $schema['taxonomy_term_data'] = array(
+ 'description' => 'Stores term information.',
+ 'fields' => array(
+ 'tid' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique term ID.',
+ ),
+ 'vid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The {taxonomy_vocabulary}.vid of the vocabulary to which the term is assigned.',
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The term name.',
+ 'translatable' => TRUE,
+ ),
+ 'description' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => 'A description of the term.',
+ 'translatable' => TRUE,
+ ),
+ 'format' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ 'description' => 'The {filter_format}.format of the description.',
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The weight of this term in relation to other terms.',
+ ),
+ ),
+ 'primary key' => array('tid'),
+ 'foreign keys' => array(
+ 'vocabulary' => array(
+ 'table' => 'taxonomy_vocabulary',
+ 'columns' => array('vid' => 'vid'),
+ ),
+ ),
+ 'indexes' => array(
+ 'taxonomy_tree' => array('vid', 'weight', 'name'),
+ 'vid_name' => array('vid', 'name'),
+ 'name' => array('name'),
+ ),
+ );
+
+ $schema['taxonomy_term_hierarchy'] = array(
+ 'description' => 'Stores the hierarchical relationship between terms.',
+ 'fields' => array(
+ 'tid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Primary Key: The {taxonomy_term_data}.tid of the term.',
+ ),
+ 'parent' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => "Primary Key: The {taxonomy_term_data}.tid of the term's parent. 0 indicates no parent.",
+ ),
+ ),
+ 'indexes' => array(
+ 'parent' => array('parent'),
+ ),
+ 'foreign keys' => array(
+ 'taxonomy_term_data' => array(
+ 'table' => 'taxonomy_term_data',
+ 'columns' => array('tid' => 'tid'),
+ ),
+ ),
+ 'primary key' => array('tid', 'parent'),
+ );
+
+ $schema['taxonomy_vocabulary'] = array(
+ 'description' => 'Stores vocabulary information.',
+ 'fields' => array(
+ 'vid' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique vocabulary ID.',
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Name of the vocabulary.',
+ 'translatable' => TRUE,
+ ),
+ 'machine_name' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The vocabulary machine name.',
+ ),
+ 'description' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => 'Description of the vocabulary.',
+ 'translatable' => TRUE,
+ ),
+ 'hierarchy' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => 'The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)',
+ ),
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The module which created the vocabulary.',
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The weight of this vocabulary in relation to other vocabularies.',
+ ),
+ ),
+ 'primary key' => array('vid'),
+ 'indexes' => array(
+ 'list' => array('weight', 'name'),
+ ),
+ 'unique keys' => array(
+ 'machine_name' => array('machine_name'),
+ ),
+ );
+
+ $schema['taxonomy_index'] = array(
+ 'description' => 'Maintains denormalized information about node/term relationships.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The {node}.nid this record tracks.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'tid' => array(
+ 'description' => 'The term ID.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sticky' => array(
+ 'description' => 'Boolean indicating whether the node is sticky.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'created' => array(
+ 'description' => 'The Unix timestamp when the node was created.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default'=> 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'term_node' => array('tid', 'sticky', 'created'),
+ 'nid' => array('nid'),
+ ),
+ 'foreign keys' => array(
+ 'tracked_node' => array(
+ 'table' => 'node',
+ 'columns' => array('nid' => 'nid'),
+ ),
+ 'term' => array(
+ 'table' => 'taxonomy_term_data',
+ 'columns' => array('tid' => 'tid'),
+ ),
+ ),
+ );
+
+ return $schema;
+}
+
+/**
+ * Implements hook_field_schema().
+ */
+function taxonomy_field_schema($field) {
+ return array(
+ 'columns' => array(
+ 'tid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
+ ),
+ 'indexes' => array(
+ 'tid' => array('tid'),
+ ),
+ 'foreign keys' => array(
+ 'tid' => array(
+ 'table' => 'taxonomy_term_data',
+ 'columns' => array('tid' => 'tid'),
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function taxonomy_update_dependencies() {
+ // taxonomy_update_7004() migrates taxonomy term data to fields and therefore
+ // must run after all Field modules have been enabled, which happens in
+ // system_update_7027().
+ $dependencies['taxonomy'][7004] = array(
+ 'system' => 7027,
+ );
+
+ return $dependencies;
+}
+
+/**
+ * Utility function: get the list of vocabularies directly from the database.
+ *
+ * This function is valid for a database schema version 7002.
+ *
+ * @ingroup update_api
+ */
+function _update_7002_taxonomy_get_vocabularies() {
+ return db_query('SELECT v.* FROM {taxonomy_vocabulary} v ORDER BY v.weight, v.name')->fetchAllAssoc('vid', PDO::FETCH_OBJ);
+}
+
+/**
+ * Rename taxonomy tables.
+ */
+function taxonomy_update_7001() {
+ db_rename_table('term_data', 'taxonomy_term_data');
+ db_rename_table('term_hierarchy', 'taxonomy_term_hierarchy');
+ db_rename_table('term_node', 'taxonomy_term_node');
+ db_rename_table('term_relation', 'taxonomy_term_relation');
+ db_rename_table('term_synonym', 'taxonomy_term_synonym');
+ db_rename_table('vocabulary', 'taxonomy_vocabulary');
+ db_rename_table('vocabulary_node_types', 'taxonomy_vocabulary_node_type');
+}
+
+/**
+ * Add {vocabulary}.machine_name column.
+ */
+function taxonomy_update_7002() {
+ $field = array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The vocabulary machine name.',
+ );
+
+ db_add_field('taxonomy_vocabulary', 'machine_name', $field);
+
+ // Do a direct query here, rather than calling taxonomy_get_vocabularies(),
+ // in case Taxonomy module is disabled.
+ $vids = db_query('SELECT vid FROM {taxonomy_vocabulary}')->fetchCol();
+ foreach ($vids as $vid) {
+ $machine_name = 'vocabulary_' . $vid;
+ db_update('taxonomy_vocabulary')
+ ->fields(array('machine_name' => $machine_name))
+ ->condition('vid', $vid)
+ ->execute();
+ }
+
+ // The machine_name unique key can only be added after we ensure the
+ // machine_name column contains unique values.
+ db_add_unique_key('taxonomy_vocabulary', 'machine_name', array('machine_name'));
+}
+
+/**
+ * Remove the related terms setting from vocabularies.
+ *
+ * This setting has not been used since Drupal 6. The {taxonomy_relations} table
+ * itself is retained to allow for data to be upgraded.
+ */
+function taxonomy_update_7003() {
+ db_drop_field('taxonomy_vocabulary', 'relations');
+}
+
+/**
+ * Move taxonomy vocabulary associations for nodes to fields and field instances.
+ */
+function taxonomy_update_7004() {
+ $taxonomy_index = array(
+ 'description' => 'Maintains denormalized information about node/term relationships.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The {node}.nid this record tracks.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'tid' => array(
+ 'description' => 'The term ID.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sticky' => array(
+ 'description' => 'Boolean indicating whether the node is sticky.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'created' => array(
+ 'description' => 'The Unix timestamp when the node was created.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default'=> 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'term_node' => array('tid', 'sticky', 'created'),
+ 'nid' => array('nid'),
+ ),
+ 'foreign keys' => array(
+ 'tracked_node' => array(
+ 'table' => 'node',
+ 'columns' => array('nid' => 'nid'),
+ ),
+ 'term' => array(
+ 'table' => 'taxonomy_term_data',
+ 'columns' => array('tid' => 'tid'),
+ ),
+ ),
+ );
+ db_create_table('taxonomy_index', $taxonomy_index);
+
+ // Use an inline version of Drupal 6 taxonomy_get_vocabularies() here since
+ // we can no longer rely on $vocabulary->nodes from the API function.
+ $result = db_query('SELECT v.*, n.type FROM {taxonomy_vocabulary} v LEFT JOIN {taxonomy_vocabulary_node_type} n ON v.vid = n.vid ORDER BY v.weight, v.name');
+ $vocabularies = array();
+ foreach ($result as $record) {
+ // If no node types are associated with a vocabulary, the LEFT JOIN will
+ // return a NULL value for type.
+ if (isset($record->type)) {
+ $node_types[$record->vid][$record->type] = $record->type;
+ unset($record->type);
+ $record->nodes = $node_types[$record->vid];
+ }
+ elseif (!isset($record->nodes)) {
+ $record->nodes = array();
+ }
+ $vocabularies[$record->vid] = $record;
+ }
+
+ foreach ($vocabularies as $vocabulary) {
+ $field_name = 'taxonomy_' . $vocabulary->machine_name;
+ $field = array(
+ 'field_name' => $field_name,
+ 'module' => 'taxonomy',
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => $vocabulary->multiple || $vocabulary->tags ? FIELD_CARDINALITY_UNLIMITED : 1,
+ 'settings' => array(
+ 'required' => $vocabulary->required ? TRUE : FALSE,
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ _update_7000_field_create_field($field);
+
+ foreach ($vocabulary->nodes as $bundle) {
+ $instance = array(
+ 'label' => $vocabulary->name,
+ 'field_name' => $field_name,
+ 'bundle' => $bundle,
+ 'entity_type' => 'node',
+ 'settings' => array(),
+ 'description' => $vocabulary->help,
+ 'required' => $vocabulary->required,
+ 'widget' => array(),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ 'teaser' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ ),
+ );
+ if ($vocabulary->tags) {
+ $instance['widget'] = array(
+ 'type' => 'taxonomy_autocomplete',
+ 'module' => 'taxonomy',
+ 'settings' => array(
+ 'size' => 60,
+ 'autocomplete_path' => 'taxonomy/autocomplete',
+ ),
+ );
+ }
+ else {
+ $instance['widget'] = array(
+ 'type' => 'select',
+ 'module' => 'options',
+ 'settings' => array(),
+ );
+ }
+ _update_7000_field_create_instance($field, $instance);
+ }
+ }
+
+ // Some contrib projects stored term node associations without regard for the
+ // selections in the taxonomy_vocabulary_node_types table, or have more terms
+ // for a single node than the vocabulary allowed. We construct the
+ // taxonomyextra field to store all the extra stuff.
+
+ // Allowed values for this extra vocabs field is every vocabulary.
+ $allowed_values = array();
+ foreach (_update_7002_taxonomy_get_vocabularies() as $vocabulary) {
+ $allowed_values[] = array(
+ 'vocabulary' => $vocabulary->machine_name,
+ 'parent' => 0,
+ );
+ }
+
+ $field_name = 'taxonomyextra';
+ $field = array(
+ 'field_name' => $field_name,
+ 'module' => 'taxonomy',
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'required' => FALSE,
+ 'allowed_values' => $allowed_values,
+ ),
+ );
+ _update_7000_field_create_field($field);
+
+ foreach (_update_7000_node_get_types() as $bundle) {
+ $instance = array(
+ 'label' => 'Taxonomy upgrade extras',
+ 'field_name' => $field_name,
+ 'entity_type' => 'node',
+ 'bundle' => $bundle->type,
+ 'settings' => array(),
+ 'description' => 'Debris left over after upgrade from Drupal 6',
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ 'module' => 'taxonomy',
+ 'settings' => array(),
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ 'teaser' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ ),
+ );
+ _update_7000_field_create_instance($field, $instance);
+ }
+
+ $fields = array('help', 'multiple', 'required', 'tags');
+ foreach ($fields as $field) {
+ db_drop_field('taxonomy_vocabulary', $field);
+ }
+}
+
+/**
+ * Migrate {taxonomy_term_node} table to field storage.
+ *
+ * @todo: This function can possibly be made much faster by wrapping a
+ * transaction around all the inserts.
+ */
+function taxonomy_update_7005(&$sandbox) {
+ // $sandbox contents:
+ // - total: The total number of term_node relationships to migrate.
+ // - count: The number of term_node relationships that have been
+ // migrated so far.
+ // - last: The db_query_range() offset to use when querying
+ // term_node; this field is incremented in quantities of $batch
+ // (1000) but at the end of each call to this function, last and
+ // count are the same.
+ // - vocabularies: An associative array mapping vocabulary id and node
+ // type to field name. If a voc id/node type pair does not appear
+ // in this array but a term_node relationship exists mapping a
+ // term in voc id to node of that type, the relationship is
+ // assigned to the taxonomymyextra field which allows terms of all
+ // vocabularies.
+ // - cursor[values], cursor[deltas]: The contents of $values and
+ // $deltas at the end of the previous call to this function. These
+ // need to be preserved across calls because a single batch of
+ // 1000 rows from term_node may end in the middle of the terms for
+ // a single node revision.
+ //
+ // $values is the array of values about to be/most recently inserted
+ // into the SQL data table for the taxonomy_term_reference
+ // field. Before $values is constructed for each record, the
+ // $values from the previous insert is checked to see if the two
+ // records are for the same node revision id; this enables knowing
+ // when to reset the delta counters which are incremented across all
+ // terms for a single field on a single revision, but reset for each
+ // new field and revision.
+ //
+ // $deltas is an associative array mapping field name to the number
+ // of term references stored so far for the current revision, which
+ // provides the delta value for each term reference data insert. The
+ // deltas are reset for each new revision.
+
+ $conditions = array(
+ 'type' => 'taxonomy_term_reference',
+ 'deleted' => 0,
+ );
+ $field_info = _update_7000_field_read_fields($conditions, 'field_name');
+
+ // This is a multi-pass update. On the first call we need to initialize some
+ // variables.
+ if (!isset($sandbox['total'])) {
+ $sandbox['last'] = 0;
+ $sandbox['count'] = 0;
+
+ // Run the same joins as the query that is used later to retrieve the
+ // term_node data, this ensures that bad records in that table - for
+ // tids which aren't in taxonomy_term_data or nids which aren't in {node}
+ // are not included in the count.
+ $sandbox['total'] = db_query('SELECT COUNT(*) FROM {taxonomy_term_data} td INNER JOIN {taxonomy_term_node} tn ON td.tid = tn.tid INNER JOIN {node} n ON tn.nid = n.nid LEFT JOIN {node} n2 ON tn.vid = n2.vid')->fetchField();
+
+ // Use an inline version of Drupal 6 taxonomy_get_vocabularies() here since
+ // we can no longer rely on $vocabulary->nodes from the API function.
+ $result = db_query('SELECT v.vid, v.machine_name, n.type FROM {taxonomy_vocabulary} v INNER JOIN {taxonomy_vocabulary_node_type} n ON v.vid = n.vid');
+ $vocabularies = array();
+ foreach ($result as $record) {
+
+ // If no node types are associated with a vocabulary, the LEFT JOIN will
+ // return a NULL value for type.
+ if (isset($record->type)) {
+ $vocabularies[$record->vid][$record->type] = 'taxonomy_'. $record->machine_name;
+ }
+ }
+
+ if (!empty($vocabularies)) {
+ $sandbox['vocabularies'] = $vocabularies;
+ }
+
+ db_create_table('taxonomy_update_7005', array(
+ 'description' => 'Stores temporary data for taxonomy_update_7005.',
+ 'fields' => array(
+ 'n' => array(
+ 'description' => 'Preserve order.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'vocab_id' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'tid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'nid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'vid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'default' => NULL,
+ ),
+ 'type' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'created' => array(
+ 'type' => 'int',
+ 'not null' => FALSE,
+ ),
+ 'sticky' => array(
+ 'type' => 'int',
+ 'not null' => FALSE,
+ ),
+ 'status' => array(
+ 'type' => 'int',
+ 'not null' => FALSE,
+ ),
+ 'is_current' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
+ ),
+ 'primary key' => array('n'),
+ ));
+
+ // Query selects all revisions at once and processes them in revision and
+ // term weight order.
+ $query = db_select('taxonomy_term_data', 'td');
+ // We are migrating term-node relationships. If there are none for a
+ // term, we do not need the term_data row.
+ $query->join('taxonomy_term_node', 'tn', 'td.tid = tn.tid');
+ // If a term-node relationship exists for a nid that does not exist, we
+ // cannot migrate it as we have no node to relate it to; thus we do not
+ // need that row from term_node.
+ $query->join('node', 'n', 'tn.nid = n.nid');
+ // If the current term-node relationship is for the current revision of
+ // the node, this left join will match and is_current will be non-NULL
+ // (we also get the current sticky and created in this case). This
+ // tells us whether to insert into the current data tables in addition
+ // to the revision data tables.
+ $query->leftJoin('node', 'n2', 'tn.vid = n2.vid');
+ $query->addField('td', 'vid', 'vocab_id');
+ $query->addField('td', 'tid');
+ $query->addField('tn', 'nid');
+ $query->addField('tn', 'vid');
+ $query->addField('n', 'type');
+ $query->addField('n2', 'created');
+ $query->addField('n2', 'sticky');
+ $query->addField('n2', 'status');
+ $query->addField('n2', 'nid', 'is_current');
+ // This query must return a consistent ordering across multiple calls.
+ // We need them ordered by node vid (since we use that to decide when
+ // to reset the delta counters) and by term weight so they appear
+ // within each node in weight order. However, tn.vid,td.weight is not
+ // guaranteed to be unique, so we add tn.tid as an additional sort key
+ // because tn.tid,tn.vid is the primary key of the D6 term_node table
+ // and so is guaranteed unique. Unfortunately it also happens to be in
+ // the wrong order which is less efficient, but c'est la vie.
+ $query->orderBy('tn.vid');
+ $query->orderBy('td.weight');
+ $query->orderBy('tn.tid');
+
+ // Work around a bug in the PostgreSQL driver that would result in fatal
+ // errors when this subquery is used in the insert query below. See
+ // https://drupal.org/node/2057693.
+ $fields = &$query->getFields();
+ unset($fields['td.weight']);
+ unset($fields['tn.tid']);
+
+ db_insert('taxonomy_update_7005')
+ ->from($query)
+ ->execute();
+ }
+ else {
+ // We do each pass in batches of 1000.
+ $batch = 1000;
+
+ $result = db_query_range('SELECT vocab_id, tid, nid, vid, type, created, sticky, status, is_current FROM {taxonomy_update_7005} ORDER BY n', $sandbox['last'], $batch);
+ if (isset($sandbox['cursor'])) {
+ $values = $sandbox['cursor']['values'];
+ $deltas = $sandbox['cursor']['deltas'];
+ }
+ else {
+ $deltas = array();
+ }
+ foreach ($result as $record) {
+ $sandbox['count'] += 1;
+
+ // Use the valid field for this vocabulary and node type or use the
+ // overflow vocabulary if there is no valid field.
+ $field_name = isset($sandbox['vocabularies'][$record->vocab_id][$record->type]) ? $sandbox['vocabularies'][$record->vocab_id][$record->type] : 'taxonomyextra';
+ $field = $field_info[$field_name];
+
+ // Start deltas from 0, and increment by one for each term attached to a
+ // node.
+ if (!isset($deltas[$field_name])) {
+ $deltas[$field_name] = 0;
+ }
+
+ if (isset($values)) {
+
+ // If the last inserted revision_id is the same as the current record,
+ // use the previous deltas to calculate the next delta.
+ if ($record->vid == $values[2]) {
+
+ // For limited cardinality fields, the delta must not be allowed to
+ // exceed the cardinality during the update. So ensure that the
+ // delta about to be inserted is within this limit.
+ // @see field_default_validate().
+ if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ($deltas[$field_name] + 1) > $field['cardinality']) {
+
+ // For excess values of a single-term vocabulary, switch over to
+ // the overflow field.
+ $field_name = 'taxonomyextra';
+ $field = $field_info[$field_name];
+ if (!isset($deltas[$field_name])) {
+ $deltas[$field_name] = 0;
+ }
+ }
+ }
+ else {
+
+ // When the record is a new revision, empty the deltas array.
+ $deltas = array($field_name => 0);
+ }
+ }
+
+ // Table and column found in the field's storage details. During upgrades,
+ // it's always SQL.
+ $table_name = "field_data_{$field_name}";
+ $revision_name = "field_revision_{$field_name}";
+ $value_column = $field_name . '_tid';
+
+ // Column names and values in field storage are the same for current and
+ // revision.
+ $columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'language', 'delta', $value_column);
+ $values = array('node', $record->nid, $record->vid, $record->type, LANGUAGE_NONE, $deltas[$field_name]++, $record->tid);
+
+ // Insert rows into the revision table.
+ db_insert($revision_name)->fields($columns)->values($values)->execute();
+
+ // is_current column is a node ID if this revision is also current.
+ if ($record->is_current) {
+ db_insert($table_name)->fields($columns)->values($values)->execute();
+ // Only insert a record in the taxonomy index if the node is published.
+ if ($record->status) {
+ // Update the {taxonomy_index} table.
+ db_insert('taxonomy_index')
+ ->fields(array('nid', 'tid', 'sticky', 'created',))
+ ->values(array($record->nid, $record->tid, $record->sticky, $record->created))
+ ->execute();
+ }
+ }
+ }
+
+ // Store the set of inserted values and the current revision's deltas in the
+ // sandbox.
+ $sandbox['cursor'] = array(
+ 'values' => $values,
+ 'deltas' => $deltas,
+ );
+ $sandbox['last'] += $batch;
+ }
+
+ if ($sandbox['count'] < $sandbox['total']) {
+ $sandbox['#finished'] = FALSE;
+ }
+ else {
+ db_drop_table('taxonomy_vocabulary_node_type');
+ db_drop_table('taxonomy_term_node');
+
+ // If there are no vocabs, we're done.
+ db_drop_table('taxonomy_update_7005');
+ $sandbox['#finished'] = TRUE;
+
+ // Determine necessity of taxonomyextras field.
+ $field = $field_info['taxonomyextra'];
+ $revision_name = 'field_revision_' . $field['field_name'];
+ $node_types = db_select($revision_name)->distinct()->fields($revision_name, array('bundle'))
+ ->execute()->fetchCol();
+
+ if (empty($node_types)) {
+ // Delete the overflow field if there are no rows in the revision table.
+ _update_7000_field_delete_field('taxonomyextra');
+ }
+ else {
+ // Remove instances which are not actually used.
+ $bundles = db_query('SELECT bundle FROM {field_config_instance} WHERE field_name = :field_name', array(':field_name' => 'taxonomyextra'))->fetchCol();
+ $bundles = array_diff($bundles, $node_types);
+ foreach ($bundles as $bundle) {
+ _update_7000_field_delete_instance('taxonomyextra', 'node', $bundle);
+ }
+ }
+ }
+}
+
+/**
+ * Add {taxonomy_term_data}.format column.
+ */
+function taxonomy_update_7006() {
+ db_add_field('taxonomy_term_data', 'format', array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'description' => 'The {filter_format}.format of the description.',
+ ));
+}
+
+/**
+ * Add index on {taxonomy_term_data}.name column to speed up taxonomy_get_term_by_name().
+ */
+function taxonomy_update_7007() {
+ db_add_index('taxonomy_term_data', 'name', array('name'));
+}
+
+/**
+ * Change the weight columns to normal int.
+ */
+function taxonomy_update_7008() {
+ db_drop_index('taxonomy_term_data', 'taxonomy_tree');
+ db_change_field('taxonomy_term_data', 'weight', 'weight', array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The weight of this term in relation to other terms.',
+ ), array(
+ 'indexes' => array(
+ 'taxonomy_tree' => array('vid', 'weight', 'name'),
+ ),
+ ));
+
+ db_drop_index('taxonomy_vocabulary', 'list');
+ db_change_field('taxonomy_vocabulary', 'weight', 'weight', array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The weight of this vocabulary in relation to other vocabularies.',
+ ), array(
+ 'indexes' => array(
+ 'list' => array('weight', 'name'),
+ ),
+ ));
+}
+
+/**
+ * Change {taxonomy_term_data}.format into varchar.
+ */
+function taxonomy_update_7009() {
+ db_change_field('taxonomy_term_data', 'format', 'format', array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ 'description' => 'The {filter_format}.format of the description.',
+ ));
+}
+
+/**
+ * Change {taxonomy_index}.created to support signed int.
+*/
+function taxonomy_update_7010() {
+ db_change_field('taxonomy_index', 'created', 'created', array(
+ 'description' => 'The Unix timestamp when the node was created.',
+ 'type' => 'int',
+ 'unsigned' => FALSE,
+ 'not null' => TRUE,
+ 'default'=> 0,
+ ));
+}
+
+/**
+ * @addtogroup updates-7.x-extra
+ * @{
+ */
+
+/**
+ * Drop unpublished nodes from the index.
+ */
+function taxonomy_update_7011() {
+ $nids = db_query('SELECT nid from {node} WHERE status = :status', array(':status' => NODE_NOT_PUBLISHED))->fetchCol();
+ if (!empty($nids)) {
+ db_delete('taxonomy_index')
+ ->condition('nid', $nids)
+ ->execute();
+ }
+}
+
+/**
+ * @} End of "addtogroup updates-7.x-extra".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.js b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.js
new file mode 100644
index 0000000..1a0c790
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.js
@@ -0,0 +1,40 @@
+(function ($) {
+
+/**
+ * Move a block in the blocks table from one region to another via select list.
+ *
+ * This behavior is dependent on the tableDrag behavior, since it uses the
+ * objects initialized in that behavior to update the row.
+ */
+Drupal.behaviors.termDrag = {
+ attach: function (context, settings) {
+ var table = $('#taxonomy', context);
+ var tableDrag = Drupal.tableDrag.taxonomy; // Get the blocks tableDrag object.
+ var rows = $('tr', table).length;
+
+ // When a row is swapped, keep previous and next page classes set.
+ tableDrag.row.prototype.onSwap = function (swappedRow) {
+ $('tr.taxonomy-term-preview', table).removeClass('taxonomy-term-preview');
+ $('tr.taxonomy-term-divider-top', table).removeClass('taxonomy-term-divider-top');
+ $('tr.taxonomy-term-divider-bottom', table).removeClass('taxonomy-term-divider-bottom');
+
+ if (settings.taxonomy.backStep) {
+ for (var n = 0; n < settings.taxonomy.backStep; n++) {
+ $(table[0].tBodies[0].rows[n]).addClass('taxonomy-term-preview');
+ }
+ $(table[0].tBodies[0].rows[settings.taxonomy.backStep - 1]).addClass('taxonomy-term-divider-top');
+ $(table[0].tBodies[0].rows[settings.taxonomy.backStep]).addClass('taxonomy-term-divider-bottom');
+ }
+
+ if (settings.taxonomy.forwardStep) {
+ for (var n = rows - settings.taxonomy.forwardStep - 1; n < rows - 1; n++) {
+ $(table[0].tBodies[0].rows[n]).addClass('taxonomy-term-preview');
+ }
+ $(table[0].tBodies[0].rows[rows - settings.taxonomy.forwardStep - 2]).addClass('taxonomy-term-divider-top');
+ $(table[0].tBodies[0].rows[rows - settings.taxonomy.forwardStep - 1]).addClass('taxonomy-term-divider-bottom');
+ }
+ };
+ }
+};
+
+})(jQuery);
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.module b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.module
new file mode 100644
index 0000000..7ad28e9
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.module
@@ -0,0 +1,2048 @@
+<?php
+
+/**
+ * @file
+ * Enables the organization of content into categories.
+ */
+
+/**
+ * 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().
+ */
+
+/**
+ * Implements hook_help().
+ */
+function taxonomy_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#taxonomy':
+ $output = '';
+ $output .= '<h3>' . t('About') . '</h3>';
+ $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>';
+ $output .= '<h3>' . t('Uses') . '</h3>';
+ $output .= '<dl>';
+ $output .= '<dt>' . t('Creating vocabularies') . '</dt>';
+ $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'))));
+ $output .= '<ul><li>' . t('<em>vocabulary</em>: Music') . '</li>';
+ $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>';
+ $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 "Add existing field" section on the manage fields page.', array('@ctedit' => url('admin/structure/types'))) . '</dd>';
+ $output .= '<dt>' . t('Classifying content') . '</dt>';
+ $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>';
+ $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':
+ $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>';
+ return $output;
+ case 'admin/structure/taxonomy/%':
+ $vocabulary = taxonomy_vocabulary_machine_name_load($arg[3]);
+ switch ($vocabulary->hierarchy) {
+ case 0:
+ 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>';
+ case 1:
+ 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>';
+ case 2:
+ 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>';
+ }
+ }
+}
+
+/**
+ * Implements hook_permission().
+ */
+function taxonomy_permission() {
+ $permissions = array(
+ 'administer taxonomy' => array(
+ 'title' => t('Administer vocabularies and terms'),
+ ),
+ );
+ foreach (taxonomy_get_vocabularies() as $vocabulary) {
+ $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(
+ 'title' => t('Delete terms from %vocabulary', array('%vocabulary' => $vocabulary->name)),
+ ),
+ );
+ }
+ return $permissions;
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function taxonomy_entity_info() {
+ $return = array(
+ 'taxonomy_term' => array(
+ 'label' => t('Taxonomy term'),
+ 'controller class' => 'TaxonomyTermController',
+ 'base table' => 'taxonomy_term_data',
+ 'uri callback' => 'taxonomy_term_uri',
+ 'fieldable' => TRUE,
+ 'entity keys' => array(
+ 'id' => 'tid',
+ 'bundle' => 'vocabulary_machine_name',
+ 'label' => 'name',
+ ),
+ 'bundle keys' => array(
+ 'bundle' => 'machine_name',
+ ),
+ 'bundles' => array(),
+ 'view modes' => array(
+ // @todo View mode for display as a field (when attached to nodes etc).
+ 'full' => array(
+ 'label' => t('Taxonomy term page'),
+ 'custom settings' => FALSE,
+ ),
+ ),
+ ),
+ );
+ foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
+ $return['taxonomy_term']['bundles'][$machine_name] = array(
+ 'label' => $vocabulary->name,
+ 'admin' => array(
+ 'path' => 'admin/structure/taxonomy/%taxonomy_vocabulary_machine_name',
+ 'real path' => 'admin/structure/taxonomy/' . $machine_name,
+ 'bundle argument' => 3,
+ 'access arguments' => array('administer taxonomy'),
+ ),
+ );
+ }
+ $return['taxonomy_vocabulary'] = array(
+ 'label' => t('Taxonomy vocabulary'),
+ 'controller class' => 'TaxonomyVocabularyController',
+ 'base table' => 'taxonomy_vocabulary',
+ 'entity keys' => array(
+ 'id' => 'vid',
+ 'label' => 'name',
+ ),
+ 'fieldable' => FALSE,
+ );
+
+ return $return;
+}
+
+/**
+ * Implements callback_entity_info_uri().
+ */
+function taxonomy_term_uri($term) {
+ return array(
+ 'path' => 'taxonomy/term/' . $term->tid,
+ );
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function taxonomy_field_extra_fields() {
+ $return = array();
+ $info = entity_get_info('taxonomy_term');
+ foreach (array_keys($info['bundles']) as $bundle) {
+ $return['taxonomy_term'][$bundle] = array(
+ '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,
+ ),
+ ),
+ );
+ }
+
+ return $return;
+}
+
+/**
+ * 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.
+ *
+ * @param $tid
+ * The term ID.
+ * @param $pager
+ * Boolean to indicate whether a pager should be used.
+ * @param $limit
+ * Integer. The maximum number of nodes to find.
+ * Set to FALSE for no limit.
+ * @param $order
+ * An array of fields and directions.
+ *
+ * @return
+ * An array of nids matching the query.
+ */
+function taxonomy_select_nodes($tid, $pager = TRUE, $limit = FALSE, $order = array('t.sticky' => 'DESC', 't.created' => 'DESC')) {
+ if (!variable_get('taxonomy_maintain_index_table', TRUE)) {
+ return array();
+ }
+ $query = db_select('taxonomy_index', 't');
+ $query->addTag('node_access');
+ $query->condition('tid', $tid);
+ if ($pager) {
+ $count_query = clone $query;
+ $count_query->addExpression('COUNT(t.nid)');
+
+ $query = $query->extend('PagerDefault');
+ if ($limit !== FALSE) {
+ $query = $query->limit($limit);
+ }
+ $query->setCountQuery($count_query);
+ }
+ else {
+ if ($limit !== FALSE) {
+ $query->range(0, $limit);
+ }
+ }
+ $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();
+}
+
+/**
+ * Implements hook_theme().
+ */
+function taxonomy_theme() {
+ return array(
+ 'taxonomy_overview_vocabularies' => array(
+ 'render element' => 'form',
+ ),
+ 'taxonomy_overview_terms' => array(
+ 'render element' => 'form',
+ ),
+ 'taxonomy_term' => array(
+ 'render element' => 'elements',
+ 'template' => 'taxonomy-term',
+ ),
+ );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function taxonomy_menu() {
+ $items['admin/structure/taxonomy'] = array(
+ 'title' => 'Taxonomy',
+ 'description' => 'Manage tagging, categorization, and classification of your content.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('taxonomy_overview_vocabularies'),
+ 'access arguments' => array('administer taxonomy'),
+ 'file' => 'taxonomy.admin.inc',
+ );
+ $items['admin/structure/taxonomy/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/structure/taxonomy/add'] = array(
+ 'title' => 'Add vocabulary',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('taxonomy_form_vocabulary'),
+ 'access arguments' => array('administer taxonomy'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'taxonomy.admin.inc',
+ );
+
+ $items['taxonomy/term/%taxonomy_term'] = array(
+ 'title' => 'Taxonomy term',
+ 'title callback' => 'taxonomy_term_title',
+ 'title arguments' => array(2),
+ 'page callback' => 'taxonomy_term_page',
+ 'page arguments' => array(2),
+ 'access arguments' => array('access content'),
+ 'file' => 'taxonomy.pages.inc',
+ );
+ $items['taxonomy/term/%taxonomy_term/view'] = array(
+ 'title' => 'View',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+ $items['taxonomy/term/%taxonomy_term/edit'] = array(
+ 'title' => 'Edit',
+ 'page callback' => 'drupal_get_form',
+ // 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),
+ 'access callback' => 'taxonomy_term_edit_access',
+ 'access arguments' => array(2),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 10,
+ 'file' => 'taxonomy.admin.inc',
+ );
+ $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,
+ 'file' => 'taxonomy.pages.inc',
+ );
+ $items['taxonomy/autocomplete'] = array(
+ 'title' => 'Autocomplete taxonomy',
+ 'page callback' => 'taxonomy_autocomplete',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'taxonomy.pages.inc',
+ );
+
+ $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name'] = array(
+ 'title callback' => 'entity_label',
+ 'title arguments' => array('taxonomy_vocabulary', 3),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('taxonomy_overview_terms', 3),
+ 'access arguments' => array('administer taxonomy'),
+ 'file' => 'taxonomy.admin.inc',
+ );
+ $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -20,
+ );
+ $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/edit'] = array(
+ 'title' => 'Edit',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('taxonomy_form_vocabulary', 3),
+ 'access arguments' => array('administer taxonomy'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => -10,
+ 'file' => 'taxonomy.admin.inc',
+ );
+
+ $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add'] = array(
+ 'title' => 'Add term',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('taxonomy_form_term', array(), 3),
+ 'access arguments' => array('administer taxonomy'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'taxonomy.admin.inc',
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function taxonomy_admin_paths() {
+ $paths = array(
+ 'taxonomy/term/*/edit' => TRUE,
+ );
+ return $paths;
+}
+
+/**
+ * Return edit access for a given term.
+ */
+function taxonomy_term_edit_access($term) {
+ return user_access("edit terms in $term->vid") || user_access('administer taxonomy');
+}
+
+/**
+ * Returns the sanitized name of a vocabulary.
+ *
+ * Deprecated. This function was previously used as a menu item title callback
+ * but has been replaced by using entity_label() (which does not
+ * sanitize the title, since the menu system does that automatically). In
+ * Drupal 7, use that function for title callbacks, and call check_plain()
+ * directly if you need a sanitized title.
+ */
+function taxonomy_admin_vocabulary_title_callback($vocabulary) {
+ return check_plain($vocabulary->name);
+}
+
+/**
+ * Saves a vocabulary.
+ *
+ * @param $vocabulary
+ * A vocabulary object with the following properties:
+ * - vid: (optional) The ID of the vocabulary (omit if creating a new
+ * vocabulary; only use to update an existing vocabulary).
+ * - name: The human-readable name of the vocabulary.
+ * - machine_name: The machine name of the vocabulary.
+ * - description: (optional) The vocabulary's description.
+ * - hierarchy: The hierarchy level of the vocabulary.
+ * - module: (optional) The module altering the vocabulary.
+ * - weight: (optional) The weight of this vocabulary in relation to other
+ * vocabularies.
+ * - original: (optional) The original vocabulary object before any changes
+ * are applied.
+ * - old_machine_name: (optional) The original machine name of the
+ * vocabulary.
+ *
+ * @return
+ * Status constant indicating whether the vocabulary was inserted (SAVED_NEW)
+ * or updated (SAVED_UPDATED).
+ */
+function taxonomy_vocabulary_save($vocabulary) {
+ // Prevent leading and trailing spaces in vocabulary names.
+ if (!empty($vocabulary->name)) {
+ $vocabulary->name = trim($vocabulary->name);
+ }
+ // Load the stored entity, if any.
+ if (!empty($vocabulary->vid)) {
+ if (!isset($vocabulary->original)) {
+ $vocabulary->original = entity_load_unchanged('taxonomy_vocabulary', $vocabulary->vid);
+ }
+ // Make sure machine name changes are easily detected.
+ // @todo: Remove in Drupal 8, as it is deprecated by directly reading from
+ // $vocabulary->original.
+ $vocabulary->old_machine_name = $vocabulary->original->machine_name;
+ }
+
+ if (!isset($vocabulary->module)) {
+ $vocabulary->module = 'taxonomy';
+ }
+
+ module_invoke_all('taxonomy_vocabulary_presave', $vocabulary);
+ module_invoke_all('entity_presave', $vocabulary, 'taxonomy_vocabulary');
+
+ if (!empty($vocabulary->vid) && !empty($vocabulary->name)) {
+ $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid');
+ taxonomy_vocabulary_static_reset(array($vocabulary->vid));
+ if ($vocabulary->old_machine_name != $vocabulary->machine_name) {
+ field_attach_rename_bundle('taxonomy_term', $vocabulary->old_machine_name, $vocabulary->machine_name);
+ }
+ module_invoke_all('taxonomy_vocabulary_update', $vocabulary);
+ module_invoke_all('entity_update', $vocabulary, 'taxonomy_vocabulary');
+ }
+ elseif (empty($vocabulary->vid)) {
+ $status = drupal_write_record('taxonomy_vocabulary', $vocabulary);
+ taxonomy_vocabulary_static_reset();
+ field_attach_create_bundle('taxonomy_term', $vocabulary->machine_name);
+ module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
+ module_invoke_all('entity_insert', $vocabulary, 'taxonomy_vocabulary');
+ }
+
+ unset($vocabulary->original);
+ cache_clear_all();
+
+ return $status;
+}
+
+/**
+ * Delete a vocabulary.
+ *
+ * @param $vid
+ * A vocabulary ID.
+ * @return
+ * Constant indicating items were deleted.
+ */
+function taxonomy_vocabulary_delete($vid) {
+ $vocabulary = taxonomy_vocabulary_load($vid);
+
+ $transaction = db_transaction();
+ try {
+ // Only load terms without a parent, child terms will get deleted too.
+ $result = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid = :vid AND th.parent = 0', array(':vid' => $vid))->fetchCol();
+ foreach ($result as $tid) {
+ taxonomy_term_delete($tid);
+ }
+ db_delete('taxonomy_vocabulary')
+ ->condition('vid', $vid)
+ ->execute();
+
+ field_attach_delete_bundle('taxonomy_term', $vocabulary->machine_name);
+ module_invoke_all('taxonomy_vocabulary_delete', $vocabulary);
+ module_invoke_all('entity_delete', $vocabulary, 'taxonomy_vocabulary');
+
+ // Load all Taxonomy module fields and delete those which use only this
+ // vocabulary.
+ $taxonomy_fields = field_read_fields(array('module' => 'taxonomy'));
+ foreach ($taxonomy_fields as $field_name => $taxonomy_field) {
+ $modified_field = FALSE;
+ // Term reference fields may reference terms from more than one
+ // vocabulary.
+ foreach ($taxonomy_field['settings']['allowed_values'] as $key => $allowed_value) {
+ if ($allowed_value['vocabulary'] == $vocabulary->machine_name) {
+ unset($taxonomy_field['settings']['allowed_values'][$key]);
+ $modified_field = TRUE;
+ }
+ }
+ if ($modified_field) {
+ if (empty($taxonomy_field['settings']['allowed_values'])) {
+ field_delete_field($field_name);
+ }
+ else {
+ // Update the field definition with the new allowed values.
+ field_update_field($taxonomy_field);
+ }
+ }
+ }
+
+ cache_clear_all();
+ taxonomy_vocabulary_static_reset();
+
+ return SAVED_DELETED;
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception('taxonomy', $e);
+ throw $e;
+ }
+}
+
+/**
+ * Implements hook_taxonomy_vocabulary_update().
+ */
+function taxonomy_taxonomy_vocabulary_update($vocabulary) {
+ // Reflect machine name changes in the definitions of existing 'taxonomy'
+ // fields.
+ if (!empty($vocabulary->old_machine_name) && $vocabulary->old_machine_name != $vocabulary->machine_name) {
+ $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) {
+ if ($value['vocabulary'] == $vocabulary->old_machine_name) {
+ $value['vocabulary'] = $vocabulary->machine_name;
+ $update = TRUE;
+ }
+ }
+ if ($update) {
+ field_update_field($field);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Checks and updates the hierarchy flag of a vocabulary.
+ *
+ * Checks the current parents of all terms in a vocabulary and updates the
+ * vocabulary's hierarchy setting to the lowest possible level. If no term
+ * has parent terms then the vocabulary will be given a hierarchy of 0.
+ * If any term has a single parent then the vocabulary will be given a
+ * hierarchy of 1. If any term has multiple parents then the vocabulary
+ * will be given a hierarchy of 2.
+ *
+ * @param $vocabulary
+ * A vocabulary object.
+ * @param $changed_term
+ * An array of the term structure that was updated.
+ *
+ * @return
+ * An integer that represents the level of the vocabulary's hierarchy.
+ */
+function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
+ $tree = taxonomy_get_tree($vocabulary->vid);
+ $hierarchy = 0;
+ foreach ($tree as $term) {
+ // Update the changed term with the new parent value before comparison.
+ if ($term->tid == $changed_term['tid']) {
+ $term = (object) $changed_term;
+ $term->parents = $term->parent;
+ }
+ // Check this term's parent count.
+ if (count($term->parents) > 1) {
+ $hierarchy = 2;
+ break;
+ }
+ elseif (count($term->parents) == 1 && !isset($term->parents[0])) {
+ $hierarchy = 1;
+ }
+ }
+ if ($hierarchy != $vocabulary->hierarchy) {
+ $vocabulary->hierarchy = $hierarchy;
+ taxonomy_vocabulary_save($vocabulary);
+ }
+
+ return $hierarchy;
+}
+
+/**
+ * Saves a term object to the database.
+ *
+ * @param $term
+ * The taxonomy term object with the following properties:
+ * - vid: The ID of the vocabulary the term is assigned to.
+ * - name: The name of the term.
+ * - tid: (optional) The unique ID for the term being saved. If $term->tid is
+ * empty or omitted, a new term will be inserted.
+ * - description: (optional) The term's description.
+ * - format: (optional) The text format for the term's description.
+ * - weight: (optional) The weight of this term in relation to other terms
+ * within the same vocabulary.
+ * - parent: (optional) The parent term(s) for this term. This can be a single
+ * term ID or an array of term IDs. A value of 0 means this term does not
+ * have any parents. When omitting this variable during an update, the
+ * existing hierarchy for the term remains unchanged.
+ * - vocabulary_machine_name: (optional) The machine name of the vocabulary
+ * the term is assigned to. If not given, this value will be set
+ * automatically by loading the vocabulary based on $term->vid.
+ * - original: (optional) The original taxonomy term object before any changes
+ * were applied. When omitted, the unchanged taxonomy term object is
+ * loaded from the database and stored in this property.
+ * Since a taxonomy term is an entity, any fields contained in the term object
+ * are saved alongside the term object.
+ *
+ * @return
+ * 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.
+ */
+function taxonomy_term_save($term) {
+ // Prevent leading and trailing spaces in term names.
+ $term->name = trim($term->name);
+ if (!isset($term->vocabulary_machine_name)) {
+ $vocabulary = taxonomy_vocabulary_load($term->vid);
+ $term->vocabulary_machine_name = $vocabulary->machine_name;
+ }
+
+ // Load the stored entity, if any.
+ if (!empty($term->tid) && !isset($term->original)) {
+ $term->original = entity_load_unchanged('taxonomy_term', $term->tid);
+ }
+
+ field_attach_presave('taxonomy_term', $term);
+ module_invoke_all('taxonomy_term_presave', $term);
+ module_invoke_all('entity_presave', $term, 'taxonomy_term');
+
+ if (empty($term->tid)) {
+ $op = 'insert';
+ $status = drupal_write_record('taxonomy_term_data', $term);
+ field_attach_insert('taxonomy_term', $term);
+ if (!isset($term->parent)) {
+ $term->parent = array(0);
+ }
+ }
+ else {
+ $op = 'update';
+ $status = drupal_write_record('taxonomy_term_data', $term, 'tid');
+ field_attach_update('taxonomy_term', $term);
+ if (isset($term->parent)) {
+ db_delete('taxonomy_term_hierarchy')
+ ->condition('tid', $term->tid)
+ ->execute();
+ }
+ }
+
+ if (isset($term->parent)) {
+ if (!is_array($term->parent)) {
+ $term->parent = array($term->parent);
+ }
+ $query = db_insert('taxonomy_term_hierarchy')
+ ->fields(array('tid', 'parent'));
+ foreach ($term->parent as $parent) {
+ if (is_array($parent)) {
+ foreach ($parent as $tid) {
+ $query->values(array(
+ 'tid' => $term->tid,
+ 'parent' => $tid
+ ));
+ }
+ }
+ else {
+ $query->values(array(
+ 'tid' => $term->tid,
+ 'parent' => $parent
+ ));
+ }
+ }
+ $query->execute();
+ }
+
+ // Reset the taxonomy term static variables.
+ taxonomy_terms_static_reset();
+
+ // Invoke the taxonomy hooks.
+ module_invoke_all("taxonomy_term_$op", $term);
+ module_invoke_all("entity_$op", $term, 'taxonomy_term');
+ unset($term->original);
+
+ return $status;
+}
+
+/**
+ * Delete a term.
+ *
+ * @param $tid
+ * The term ID.
+ * @return
+ * Status constant indicating deletion.
+ */
+function taxonomy_term_delete($tid) {
+ $transaction = db_transaction();
+ try {
+ $tids = array($tid);
+ while ($tids) {
+ $children_tids = $orphans = array();
+ foreach ($tids as $tid) {
+ // See if any of the term's children are about to be become orphans:
+ if ($children = taxonomy_get_children($tid)) {
+ foreach ($children as $child) {
+ // If the term has multiple parents, we don't delete it.
+ $parents = taxonomy_get_parents($child->tid);
+ if (count($parents) == 1) {
+ $orphans[] = $child->tid;
+ }
+ }
+ }
+
+ if ($term = taxonomy_term_load($tid)) {
+ db_delete('taxonomy_term_data')
+ ->condition('tid', $tid)
+ ->execute();
+ db_delete('taxonomy_term_hierarchy')
+ ->condition('tid', $tid)
+ ->execute();
+
+ field_attach_delete('taxonomy_term', $term);
+ module_invoke_all('taxonomy_term_delete', $term);
+ module_invoke_all('entity_delete', $term, 'taxonomy_term');
+ taxonomy_terms_static_reset();
+ }
+ }
+
+ $tids = $orphans;
+ }
+ return SAVED_DELETED;
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception('taxonomy', $e);
+ throw $e;
+ }
+}
+
+/**
+ * Generates an array which displays a term detail page.
+ *
+ * @param term
+ * A taxonomy term object.
+ * @return
+ * A $page element suitable for use by drupal_page_render().
+ */
+function taxonomy_term_show($term) {
+ return taxonomy_term_view_multiple(array($term->tid => $term), 'full');
+}
+
+/**
+ * Constructs a drupal_render() style array from an array of loaded terms.
+ *
+ * @param $terms
+ * An array of taxonomy terms as returned by taxonomy_term_load_multiple().
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser'...
+ * @param $weight
+ * An integer representing the weight of the first taxonomy term in the list.
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ *
+ * @return
+ * An array in the format expected by drupal_render().
+ */
+function taxonomy_term_view_multiple($terms, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
+ field_attach_prepare_view('taxonomy_term', $terms, $view_mode, $langcode);
+ entity_prepare_view('taxonomy_term', $terms, $langcode);
+ $build = array();
+ foreach ($terms as $term) {
+ $build['taxonomy_terms'][$term->tid] = taxonomy_term_view($term, $view_mode, $langcode);
+ $build['taxonomy_terms'][$term->tid]['#weight'] = $weight;
+ $weight++;
+ }
+ $build['taxonomy_terms']['#sorted'] = TRUE;
+ return $build;
+}
+
+/**
+ * Builds a structured array representing the term's content.
+ *
+ * The content built for the taxonomy term (field values, file attachments or
+ * other term components) will vary depending on the $view_mode parameter.
+ *
+ * Drupal core defines the following view modes for terms, with the following
+ * default use cases:
+ * - full (default): term is displayed on its own page (taxonomy/term/123)
+ * Contributed modules might define additional view modes, or use existing
+ * view modes in additional contexts.
+ *
+ * @param $term
+ * A taxonomy term object.
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ */
+function taxonomy_term_build_content($term, $view_mode = 'full', $langcode = NULL) {
+ if (!isset($langcode)) {
+ $langcode = $GLOBALS['language_content']->language;
+ }
+
+ // Remove previously built content, if exists.
+ $term->content = array();
+
+ // Allow modules to change the view mode.
+ $context = array(
+ 'entity_type' => 'taxonomy_term',
+ 'entity' => $term,
+ 'langcode' => $langcode,
+ );
+ drupal_alter('entity_view_mode', $view_mode, $context);
+
+ // Add the term description if the term has one and it is visible.
+ $type = 'taxonomy_term';
+ $entity_ids = entity_extract_ids($type, $term);
+ $settings = field_view_mode_settings($type, $entity_ids[2]);
+ $fields = field_extra_fields_get_display($type, $entity_ids[2], $view_mode);
+ if (!empty($term->description) && isset($fields['description']) && $fields['description']['visible']) {
+ $term->content['description'] = array(
+ '#markup' => check_markup($term->description, $term->format, '', TRUE),
+ '#weight' => $fields['description']['weight'],
+ '#prefix' => '<div class="taxonomy-term-description">',
+ '#suffix' => '</div>',
+ );
+ }
+
+ // Build fields content.
+ // In case of a multiple view, taxonomy_term_view_multiple() already ran the
+ // 'prepare_view' step. An internal flag prevents the operation from running
+ // twice.
+ field_attach_prepare_view('taxonomy_term', array($term->tid => $term), $view_mode, $langcode);
+ entity_prepare_view('taxonomy_term', array($term->tid => $term), $langcode);
+ $term->content += field_attach_view('taxonomy_term', $term, $view_mode, $langcode);
+
+ // Allow modules to make their own additions to the taxonomy term.
+ module_invoke_all('taxonomy_term_view', $term, $view_mode, $langcode);
+ module_invoke_all('entity_view', $term, 'taxonomy_term', $view_mode, $langcode);
+
+ // Make sure the current view mode is stored if no module has already
+ // populated the related key.
+ $term->content += array('#view_mode' => $view_mode);
+}
+
+/**
+ * Generate an array for rendering the given term.
+ *
+ * @param $term
+ * A term object.
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ *
+ * @return
+ * An array as expected by drupal_render().
+ */
+function taxonomy_term_view($term, $view_mode = 'full', $langcode = NULL) {
+ if (!isset($langcode)) {
+ $langcode = $GLOBALS['language_content']->language;
+ }
+
+ // Populate $term->content with a render() array.
+ taxonomy_term_build_content($term, $view_mode, $langcode);
+ $build = $term->content;
+
+ // We don't need duplicate rendering info in $term->content.
+ unset($term->content);
+
+ $build += array(
+ '#theme' => 'taxonomy_term',
+ '#term' => $term,
+ '#view_mode' => $view_mode,
+ '#language' => $langcode,
+ );
+
+ $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css';
+
+ // Allow modules to modify the structured taxonomy term.
+ $type = 'taxonomy_term';
+ drupal_alter(array('taxonomy_term_view', 'entity_view'), $build, $type);
+
+ 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'];
+
+ $uri = entity_uri('taxonomy_term', $term);
+ $variables['term_url'] = url($uri['path'], $uri['options']);
+ $variables['term_name'] = check_plain($term->name);
+ $variables['page'] = $variables['view_mode'] == 'full' && taxonomy_term_is_page($term);
+
+ // Flatten the term object's member fields.
+ $variables = array_merge((array) $term, $variables);
+
+ // Helpful $content variable for templates.
+ $variables['content'] = array();
+ 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);
+
+ // Gather classes, and clean up name so there are no underscores.
+ $vocabulary_name_css = str_replace('_', '-', $term->vocabulary_machine_name);
+ $variables['classes_array'][] = 'vocabulary-' . $vocabulary_name_css;
+
+ $variables['theme_hook_suggestions'][] = 'taxonomy_term__' . $term->vocabulary_machine_name;
+ $variables['theme_hook_suggestions'][] = 'taxonomy_term__' . $term->tid;
+}
+
+/**
+ * Returns whether the current page is the page of the passed-in term.
+ *
+ * @param $term
+ * A term object.
+ */
+function taxonomy_term_is_page($term) {
+ $page_term = menu_get_object('taxonomy_term', 2);
+ return (!empty($page_term) ? $page_term->tid == $term->tid : FALSE);
+}
+
+/**
+ * Clear all static cache variables for terms.
+ */
+function taxonomy_terms_static_reset() {
+ drupal_static_reset('taxonomy_term_count_nodes');
+ drupal_static_reset('taxonomy_get_tree');
+ drupal_static_reset('taxonomy_get_tree:parents');
+ drupal_static_reset('taxonomy_get_tree:terms');
+ drupal_static_reset('taxonomy_get_parents');
+ drupal_static_reset('taxonomy_get_parents_all');
+ drupal_static_reset('taxonomy_get_children');
+ entity_get_controller('taxonomy_term')->resetCache();
+}
+
+/**
+ * Clear all static cache variables for vocabularies.
+ *
+ * @param $ids
+ * An array of ids to reset in entity controller cache.
+ */
+function taxonomy_vocabulary_static_reset($ids = NULL) {
+ drupal_static_reset('taxonomy_vocabulary_get_names');
+ entity_get_controller('taxonomy_vocabulary')->resetCache($ids);
+}
+
+/**
+ * Return an array of all vocabulary objects.
+ *
+ * @return
+ * An array of all vocabulary objects, indexed by vid.
+ */
+function taxonomy_get_vocabularies() {
+ return taxonomy_vocabulary_load_multiple(FALSE, array());
+}
+
+/**
+ * Get names for all taxonomy vocabularies.
+ *
+ * @return
+ * 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.
+ */
+function taxonomy_vocabulary_get_names() {
+ $names = &drupal_static(__FUNCTION__);
+
+ if (!isset($names)) {
+ $names = db_query('SELECT name, machine_name, vid FROM {taxonomy_vocabulary}')->fetchAllAssoc('machine_name');
+ }
+
+ return $names;
+}
+
+/**
+ * Finds all parents of a given term ID.
+ *
+ * @param $tid
+ * A taxonomy term ID.
+ *
+ * @return
+ * An array of term objects which are the parents of the term $tid, or an
+ * empty array if parents are not found.
+ */
+function taxonomy_get_parents($tid) {
+ $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);
+ }
+
+ return isset($parents[$tid]) ? $parents[$tid] : array();
+}
+
+/**
+ * Find all ancestors of a given term ID.
+ */
+function taxonomy_get_parents_all($tid) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ if (isset($cache[$tid])) {
+ return $cache[$tid];
+ }
+
+ $parents = array();
+ if ($term = taxonomy_term_load($tid)) {
+ $parents[] = $term;
+ $n = 0;
+ while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
+ $parents = array_merge($parents, $parent);
+ $n++;
+ }
+ }
+
+ $cache[$tid] = $parents;
+
+ return $parents;
+}
+
+/**
+ * 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
+ * An array of term objects that are the children of the term $tid, or an
+ * empty array when no children exist.
+ */
+function taxonomy_get_children($tid, $vid = 0) {
+ $children = &drupal_static(__FUNCTION__, array());
+
+ if ($tid && !isset($children[$tid])) {
+ $query = db_select('taxonomy_term_data', 't');
+ $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
+ $query->addField('t', 'tid');
+ $query->condition('h.parent', $tid);
+ if ($vid) {
+ $query->condition('t.vid', $vid);
+ }
+ $query->addTag('term_access');
+ $query->orderBy('t.weight');
+ $query->orderBy('t.name');
+ $tids = $query->execute()->fetchCol();
+ $children[$tid] = taxonomy_term_load_multiple($tids);
+ }
+
+ return isset($children[$tid]) ? $children[$tid] : array();
+}
+
+/**
+ * Create a hierarchical representation of a vocabulary.
+ *
+ * @param $vid
+ * Which vocabulary to generate the tree for.
+ * @param $parent
+ * The term ID under which to generate the tree. If 0, generate the tree
+ * for the entire vocabulary.
+ * @param $max_depth
+ * The number of levels of the tree to return. Leave NULL to return all levels.
+ * @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.
+ *
+ * @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.
+ * Results are statically cached. Term objects will be partial or complete
+ * depending on the $load_entities parameter.
+ */
+function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
+ $children = &drupal_static(__FUNCTION__, array());
+ $parents = &drupal_static(__FUNCTION__ . ':parents', array());
+ $terms = &drupal_static(__FUNCTION__ . ':terms', array());
+
+ // We cache trees, so it's not CPU-intensive to call taxonomy_get_tree() on a
+ // term and its children, too.
+ if (!isset($children[$vid])) {
+ $children[$vid] = array();
+ $parents[$vid] = array();
+ $terms[$vid] = array();
+
+ $query = db_select('taxonomy_term_data', 't');
+ $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
+ $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;
+ }
+ }
+
+ // 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]));
+ }
+
+ $max_depth = (!isset($max_depth)) ? count($children[$vid]) : $max_depth;
+ $tree = array();
+
+ // 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];
+ if (isset($parents[$vid][$term->tid])) {
+ // Clone the term so that the depth attribute remains correct
+ // in the event of multiple parents.
+ $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]);
+ }
+ }
+ }
+
+ return $tree;
+}
+
+/**
+ * Try to map a string to an existing term, as for glossary use.
+ *
+ * Provides a case-insensitive and trimmed mapping, to maximize the
+ * likelihood of a successful match.
+ *
+ * @param $name
+ * Name of the term to search for.
+ * @param $vocabulary
+ * (optional) Vocabulary machine name to limit the search. Defaults to NULL.
+ *
+ * @return
+ * An array of matching term objects.
+ */
+function taxonomy_get_term_by_name($name, $vocabulary = NULL) {
+ $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);
+}
+
+/**
+ * Controller class for taxonomy terms.
+ *
+ * This extends the DrupalDefaultEntityController class. Only alteration is
+ * that we match the condition on term name case-independently.
+ */
+class TaxonomyTermController extends DrupalDefaultEntityController {
+
+ protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+ $query = parent::buildQuery($ids, $conditions, $revision_id);
+ $query->addTag('translatable');
+ $query->addTag('term_access');
+ // When name is passed as a condition use LIKE.
+ if (isset($conditions['name'])) {
+ $query_conditions = &$query->conditions();
+ foreach ($query_conditions as $key => $condition) {
+ if (is_array($condition) && $condition['field'] == 'base.name') {
+ $query_conditions[$key]['operator'] = 'LIKE';
+ $query_conditions[$key]['value'] = db_like($query_conditions[$key]['value']);
+ }
+ }
+ }
+ // Add the machine name field from the {taxonomy_vocabulary} table.
+ $query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid');
+ $query->addField('v', 'machine_name', 'vocabulary_machine_name');
+ return $query;
+ }
+
+ protected function cacheGet($ids, $conditions = array()) {
+ $terms = parent::cacheGet($ids, $conditions);
+ // Name matching is case insensitive, note that with some collations
+ // LOWER() and drupal_strtolower() may return different results.
+ foreach ($terms as $term) {
+ $term_values = (array) $term;
+ if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) {
+ unset($terms[$term->tid]);
+ }
+ }
+ return $terms;
+ }
+}
+
+/**
+ * Controller class for taxonomy vocabularies.
+ *
+ * This extends the DrupalDefaultEntityController class, adding required
+ * special handling for taxonomy vocabulary objects.
+ */
+class TaxonomyVocabularyController extends DrupalDefaultEntityController {
+
+ protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+ $query = parent::buildQuery($ids, $conditions, $revision_id);
+ $query->addTag('translatable');
+ $query->orderBy('base.weight');
+ $query->orderBy('base.name');
+ return $query;
+ }
+}
+
+/**
+ * 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.
+ *
+ * @see entity_load()
+ * @see EntityFieldQuery
+ *
+ * @param $tids
+ * An array of taxonomy term IDs.
+ * @param $conditions
+ * (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
+ * EntityFieldQuery to retrieve a list of entity IDs loadable by
+ * this function.
+ *
+ * @return
+ * An array of term objects, indexed by tid. When no results are found, an
+ * empty array is returned.
+ *
+ * @todo Remove $conditions in Drupal 8.
+ */
+function taxonomy_term_load_multiple($tids = array(), $conditions = array()) {
+ return entity_load('taxonomy_term', $tids, $conditions);
+}
+
+/**
+ * Load multiple taxonomy vocabularies based on certain conditions.
+ *
+ * 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.
+ *
+ * @see entity_load()
+ *
+ * @param $vids
+ * An array of taxonomy vocabulary IDs, or FALSE to load all vocabularies.
+ * @param $conditions
+ * An array of conditions to add to the query.
+ *
+ * @return
+ * An array of vocabulary objects, indexed by vid.
+ */
+function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) {
+ return entity_load('taxonomy_vocabulary', $vids, $conditions);
+}
+
+/**
+ * Return the vocabulary object matching a vocabulary ID.
+ *
+ * @param $vid
+ * The vocabulary's ID.
+ *
+ * @return
+ * The vocabulary object with all of its metadata, if exists, FALSE otherwise.
+ * Results are statically cached.
+ *
+ * @see taxonomy_vocabulary_machine_name_load()
+ */
+function taxonomy_vocabulary_load($vid) {
+ $vocabularies = taxonomy_vocabulary_load_multiple(array($vid));
+ return reset($vocabularies);
+}
+
+/**
+ * Return the vocabulary object matching a vocabulary machine name.
+ *
+ * @param $name
+ * The vocabulary's machine name.
+ *
+ * @return
+ * The vocabulary object with all of its metadata, if exists, FALSE otherwise.
+ * Results are statically cached.
+ *
+ * @see taxonomy_vocabulary_load()
+ */
+function taxonomy_vocabulary_machine_name_load($name) {
+ $vocabularies = taxonomy_vocabulary_load_multiple(NULL, array('machine_name' => $name));
+ return reset($vocabularies);
+}
+
+/**
+ * Return the term object matching a term ID.
+ *
+ * @param $tid
+ * A term's ID
+ *
+ * @return
+ * A taxonomy term object, or FALSE if the term was not found. Results are
+ * statically cached.
+ */
+function taxonomy_term_load($tid) {
+ if (!is_numeric($tid)) {
+ return FALSE;
+ }
+ $term = taxonomy_term_load_multiple(array($tid), array());
+ return $term ? $term[$tid] : FALSE;
+}
+
+/**
+ * Helper function for array_map purposes.
+ */
+function _taxonomy_get_tid_from_term($term) {
+ return $term->tid;
+}
+
+/**
+ * Implodes a list of tags of a certain vocabulary into a string.
+ *
+ * @see drupal_explode_tags()
+ */
+function taxonomy_implode_tags($tags, $vid = NULL) {
+ $typed_tags = array();
+ foreach ($tags as $tag) {
+ // Extract terms belonging to the vocabulary in question.
+ if (!isset($vid) || $tag->vid == $vid) {
+ // Make sure we have a completed loaded taxonomy term.
+ if (isset($tag->name)) {
+ // Commas and quotes in tag names are special cases, so encode 'em.
+ if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
+ $typed_tags[] = '"' . str_replace('"', '""', $tag->name) . '"';
+ }
+ else {
+ $typed_tags[] = $tag->name;
+ }
+ }
+ }
+ }
+ return implode(', ', $typed_tags);
+}
+
+/**
+ * Implements hook_field_info().
+ *
+ * Field settings:
+ * - allowed_values: a list array of one or more vocabulary trees:
+ * - vocabulary: a vocabulary machine name.
+ * - 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(
+ 'taxonomy_term_reference' => array(
+ 'label' => t('Term reference'),
+ 'description' => t('This field stores a reference to a taxonomy term.'),
+ 'default_widget' => 'options_select',
+ 'default_formatter' => 'taxonomy_term_reference_link',
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => '',
+ 'parent' => '0',
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_widget_info().
+ */
+function taxonomy_field_widget_info() {
+ return array(
+ 'taxonomy_autocomplete' => array(
+ 'label' => t('Autocomplete term widget (tagging)'),
+ 'field types' => array('taxonomy_term_reference'),
+ 'settings' => array(
+ 'size' => 60,
+ 'autocomplete_path' => 'taxonomy/autocomplete',
+ ),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_widget_info_alter().
+ */
+function taxonomy_field_widget_info_alter(&$info) {
+ $info['options_select']['field types'][] = 'taxonomy_term_reference';
+ $info['options_buttons']['field types'][] = 'taxonomy_term_reference';
+}
+
+/**
+ * Implements hook_options_list().
+ */
+function taxonomy_options_list($field, $instance, $entity_type, $entity) {
+ $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'taxonomy_allowed_values';
+ return $function($field);
+}
+
+/**
+ * Implements hook_field_validate().
+ *
+ * Taxonomy field settings allow for either a single vocabulary ID, multiple
+ * vocabulary IDs, or sub-trees of a vocabulary to be specified as allowed
+ * values, although only the first of these is supported via the field UI.
+ * Confirm that terms entered as values meet at least one of these conditions.
+ *
+ * Possible error codes:
+ * - 'taxonomy_term_illegal_value': The value is not part of the list of allowed values.
+ */
+function taxonomy_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
+ // Build an array of existing term IDs so they can be loaded with
+ // taxonomy_term_load_multiple();
+ foreach ($items as $delta => $item) {
+ if (!empty($item['tid']) && $item['tid'] != 'autocreate') {
+ $tids[] = $item['tid'];
+ }
+ }
+ if (!empty($tids)) {
+ $terms = taxonomy_term_load_multiple($tids);
+
+ // Check each existing item to ensure it can be found in the
+ // allowed values for this field.
+ foreach ($items as $delta => $item) {
+ $validate = TRUE;
+ if (!empty($item['tid']) && $item['tid'] != 'autocreate') {
+ $validate = FALSE;
+ foreach ($field['settings']['allowed_values'] as $settings) {
+ // If no parent is specified, check if the term is in the vocabulary.
+ if (isset($settings['vocabulary']) && empty($settings['parent'])) {
+ if ($settings['vocabulary'] == $terms[$item['tid']]->vocabulary_machine_name) {
+ $validate = TRUE;
+ break;
+ }
+ }
+ // If a parent is specified, then to validate it must appear in the
+ // array returned by taxonomy_get_parents_all().
+ elseif (!empty($settings['parent'])) {
+ $ancestors = taxonomy_get_parents_all($item['tid']);
+ foreach ($ancestors as $ancestor) {
+ if ($ancestor->tid == $settings['parent']) {
+ $validate = TRUE;
+ break 2;
+ }
+ }
+ }
+ }
+ }
+ if (!$validate) {
+ $errors[$field['field_name']][$langcode][$delta][] = array(
+ 'error' => 'taxonomy_term_reference_illegal_value',
+ 'message' => t('%name: illegal value.', array('%name' => $instance['label'])),
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_field_is_empty().
+ */
+function taxonomy_field_is_empty($item, $field) {
+ if (!is_array($item) || (empty($item['tid']) && (string) $item['tid'] !== '0')) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function taxonomy_field_formatter_info() {
+ return array(
+ 'taxonomy_term_reference_link' => array(
+ 'label' => t('Link'),
+ 'field types' => array('taxonomy_term_reference'),
+ ),
+ 'taxonomy_term_reference_plain' => array(
+ 'label' => t('Plain text'),
+ 'field types' => array('taxonomy_term_reference'),
+ ),
+ 'taxonomy_term_reference_rss_category' => array(
+ 'label' => t('RSS category'),
+ 'field types' => array('taxonomy_term_reference'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ */
+function taxonomy_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+ $element = array();
+
+ // Terms whose tid is 'autocreate' do not exist
+ // yet and $item['taxonomy_term'] is not set. Theme such terms as
+ // just their name.
+
+ switch ($display['type']) {
+ case 'taxonomy_term_reference_link':
+ foreach ($items as $delta => $item) {
+ if ($item['tid'] == 'autocreate') {
+ $element[$delta] = array(
+ '#markup' => check_plain($item['name']),
+ );
+ }
+ else {
+ $term = $item['taxonomy_term'];
+ $uri = entity_uri('taxonomy_term', $term);
+ $element[$delta] = array(
+ '#type' => 'link',
+ '#title' => $term->name,
+ '#href' => $uri['path'],
+ '#options' => $uri['options'],
+ );
+ }
+ }
+ break;
+
+ case 'taxonomy_term_reference_plain':
+ foreach ($items as $delta => $item) {
+ $name = ($item['tid'] != 'autocreate' ? $item['taxonomy_term']->name : $item['name']);
+ $element[$delta] = array(
+ '#markup' => check_plain($name),
+ );
+ }
+ break;
+
+ case 'taxonomy_term_reference_rss_category':
+ foreach ($items as $delta => $item) {
+ $entity->rss_elements[] = array(
+ 'key' => 'category',
+ 'value' => $item['tid'] != 'autocreate' ? $item['taxonomy_term']->name : $item['name'],
+ 'attributes' => array(
+ 'domain' => $item['tid'] != 'autocreate' ? url('taxonomy/term/' . $item['tid'], array('absolute' => TRUE)) : '',
+ ),
+ );
+ }
+ break;
+ }
+
+ return $element;
+}
+
+/**
+ * Returns the set of valid terms for a taxonomy field.
+ *
+ * @param $field
+ * The field definition.
+ * @return
+ * The array of valid terms for this field, keyed by term id.
+ */
+function taxonomy_allowed_values($field) {
+ $options = array();
+ foreach ($field['settings']['allowed_values'] as $tree) {
+ if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
+ if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'])) {
+ foreach ($terms as $term) {
+ $options[$term->tid] = str_repeat('-', $term->depth) . $term->name;
+ }
+ }
+ }
+ }
+ return $options;
+}
+
+/**
+ * Implements hook_field_formatter_prepare_view().
+ *
+ * This preloads all taxonomy terms for multiple loaded objects at once and
+ * unsets values for invalid terms that do not exist.
+ */
+function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
+ $tids = array();
+
+ // Collect every possible term attached to any of the fieldable entities.
+ foreach ($entities as $id => $entity) {
+ foreach ($items[$id] as $delta => $item) {
+ // Force the array key to prevent duplicates.
+ if ($item['tid'] != 'autocreate') {
+ $tids[$item['tid']] = $item['tid'];
+ }
+ }
+ }
+ if ($tids) {
+ $terms = taxonomy_term_load_multiple($tids);
+
+ // Iterate through the fieldable entities again to attach the loaded term data.
+ foreach ($entities as $id => $entity) {
+ $rekey = FALSE;
+
+ foreach ($items[$id] as $delta => $item) {
+ // Check whether the taxonomy term field instance value could be loaded.
+ if (isset($terms[$item['tid']])) {
+ // Replace the instance value with the term data.
+ $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']];
+ }
+ // Terms to be created are not in $terms, but are still legitimate.
+ elseif ($item['tid'] == 'autocreate') {
+ // Leave the item in place.
+ }
+ // Otherwise, unset the instance value, since the term does not exist.
+ else {
+ unset($items[$id][$delta]);
+ $rekey = TRUE;
+ }
+ }
+
+ if ($rekey) {
+ // Rekey the items array.
+ $items[$id] = array_values($items[$id]);
+ }
+ }
+ }
+}
+
+/**
+ * Title callback for term pages.
+ *
+ * @param $term
+ * A term object.
+ *
+ * @return
+ * The term name to be used as the page title.
+ */
+function taxonomy_term_title($term) {
+ return $term->name;
+}
+
+/**
+ * Implements hook_field_widget_form().
+ */
+function taxonomy_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
+ $tags = array();
+ foreach ($items as $item) {
+ $tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']);
+ }
+
+ $element += array(
+ '#type' => 'textfield',
+ '#default_value' => taxonomy_implode_tags($tags),
+ '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $field['field_name'],
+ '#size' => $instance['widget']['settings']['size'],
+ '#maxlength' => 1024,
+ '#element_validate' => array('taxonomy_autocomplete_validate'),
+ );
+
+ return $element;
+}
+
+/**
+ * Form element validate handler for taxonomy term autocomplete element.
+ */
+function taxonomy_autocomplete_validate($element, &$form_state) {
+ // Autocomplete widgets do not send their tids in the form, so we must detect
+ // them here and process them independently.
+ $value = array();
+ if ($tags = $element['#value']) {
+ // Collect candidate vocabularies.
+ $field = field_widget_field($element, $form_state);
+ $vocabularies = array();
+ foreach ($field['settings']['allowed_values'] as $tree) {
+ if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
+ $vocabularies[$vocabulary->vid] = $vocabulary;
+ }
+ }
+
+ // Translate term names into actual terms.
+ $typed_terms = drupal_explode_tags($tags);
+ foreach ($typed_terms as $typed_term) {
+ // See if the term exists in the chosen vocabulary and return the tid;
+ // otherwise, create a new 'autocreate' term for insert/update.
+ if ($possibilities = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => array_keys($vocabularies)))) {
+ $term = array_pop($possibilities);
+ }
+ else {
+ $vocabulary = reset($vocabularies);
+ $term = array(
+ 'tid' => 'autocreate',
+ 'vid' => $vocabulary->vid,
+ 'name' => $typed_term,
+ 'vocabulary_machine_name' => $vocabulary->machine_name,
+ );
+ }
+ $value[] = (array)$term;
+ }
+ }
+
+ form_set_value($element, $value, $form_state);
+}
+
+/**
+ * Implements hook_field_widget_error().
+ */
+function taxonomy_field_widget_error($element, $error, $form, &$form_state) {
+ form_error($element, $error['message']);
+}
+/**
+ * Implements hook_field_settings_form().
+ */
+function taxonomy_field_settings_form($field, $instance, $has_data) {
+ // Get proper values for 'allowed_values_function', which is a core setting.
+ $vocabularies = taxonomy_get_vocabularies();
+ $options = array();
+ foreach ($vocabularies as $vocabulary) {
+ $options[$vocabulary->machine_name] = $vocabulary->name;
+ }
+ $form['allowed_values'] = array(
+ '#tree' => TRUE,
+ );
+
+ foreach ($field['settings']['allowed_values'] as $delta => $tree) {
+ $form['allowed_values'][$delta]['vocabulary'] = array(
+ '#type' => 'select',
+ '#title' => t('Vocabulary'),
+ '#default_value' => $tree['vocabulary'],
+ '#options' => $options,
+ '#required' => TRUE,
+ '#description' => t('The vocabulary which supplies the options for this field.'),
+ '#disabled' => $has_data,
+ );
+ $form['allowed_values'][$delta]['parent'] = array(
+ '#type' => 'value',
+ '#value' => $tree['parent'],
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Implements hook_rdf_mapping().
+ *
+ * @return array
+ * The rdf mapping for vocabularies and terms.
+ */
+function taxonomy_rdf_mapping() {
+ return array(
+ array(
+ 'type' => 'taxonomy_term',
+ 'bundle' => RDF_DEFAULT_BUNDLE,
+ 'mapping' => array(
+ 'rdftype' => array('skos:Concept'),
+ 'name' => array(
+ 'predicates' => array('rdfs:label', 'skos:prefLabel'),
+ ),
+ 'description' => array(
+ 'predicates' => array('skos:definition'),
+ ),
+ 'vid' => array(
+ 'predicates' => array('skos:inScheme'),
+ 'type' => 'rel',
+ ),
+ 'parent' => array(
+ 'predicates' => array('skos:broader'),
+ 'type' => 'rel',
+ ),
+ ),
+ ),
+ array(
+ 'type' => 'taxonomy_vocabulary',
+ 'bundle' => RDF_DEFAULT_BUNDLE,
+ 'mapping' => array(
+ 'rdftype' => array('skos:ConceptScheme'),
+ 'name' => array(
+ 'predicates' => array('dc:title'),
+ ),
+ 'description' => array(
+ 'predicates' => array('rdfs:comment'),
+ ),
+ ),
+ ),
+ );
+}
+
+/**
+ * @defgroup taxonomy_index Taxonomy indexing
+ * @{
+ * Functions to maintain taxonomy indexing.
+ *
+ * Taxonomy uses default field storage to store canonical relationships
+ * between terms and fieldable entities. However its most common use case
+ * requires listing all content associated with a term or group of terms
+ * sorted by creation date. To avoid slow queries due to joining across
+ * multiple node and field tables with various conditions and order by criteria,
+ * we maintain a denormalized table with all relationships between terms,
+ * published nodes and common sort criteria such as sticky and created.
+ * This is used as a lookup table by taxonomy_select_nodes(). When using other
+ * field storage engines or alternative methods of denormalizing this data
+ * you should set the variable 'taxonomy_maintain_index_table' to FALSE
+ * to avoid unnecessary writes in SQL.
+ */
+
+/**
+ * Implements hook_field_presave().
+ *
+ * Create any new terms defined in a freetagging vocabulary.
+ */
+function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
+ foreach ($items as $delta => $item) {
+ if ($item['tid'] == 'autocreate') {
+ $term = (object) $item;
+ unset($term->tid);
+ taxonomy_term_save($term);
+ $items[$delta]['tid'] = $term->tid;
+ }
+ }
+}
+
+/**
+ * Implements hook_node_insert().
+ */
+function taxonomy_node_insert($node) {
+ // Add taxonomy index entries for the node.
+ taxonomy_build_node_index($node);
+}
+
+/**
+ * Builds and inserts taxonomy index entries for a given node.
+ *
+ * The index lists all terms that are related to a given node entity, and is
+ * therefore maintained at the entity level.
+ *
+ * @param $node
+ * The node object.
+ */
+function taxonomy_build_node_index($node) {
+ // We maintain a denormalized table of term/node relationships, containing
+ // only data for current, published nodes.
+ $status = NULL;
+ if (variable_get('taxonomy_maintain_index_table', TRUE)) {
+ // If a node property is not set in the node object when node_save() is
+ // called, the old value from $node->original is used.
+ if (!empty($node->original)) {
+ $status = (int)(!empty($node->status) || (!isset($node->status) && !empty($node->original->status)));
+ $sticky = (int)(!empty($node->sticky) || (!isset($node->sticky) && !empty($node->original->sticky)));
+ }
+ else {
+ $status = (int)(!empty($node->status));
+ $sticky = (int)(!empty($node->sticky));
+ }
+ }
+ // We only maintain the taxonomy index for published nodes.
+ if ($status) {
+ // Collect a unique list of all the term IDs from all node fields.
+ $tid_all = array();
+ foreach (field_info_instances('node', $node->type) as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ if ($field['module'] == 'taxonomy' && $field['storage']['type'] == 'field_sql_storage') {
+ // If a field value is not set in the node object when node_save() is
+ // called, the old value from $node->original is used.
+ if (isset($node->{$field_name})) {
+ $items = $node->{$field_name};
+ }
+ elseif (isset($node->original->{$field_name})) {
+ $items = $node->original->{$field_name};
+ }
+ else {
+ continue;
+ }
+ foreach (field_available_languages('node', $field) as $langcode) {
+ if (!empty($items[$langcode])) {
+ foreach ($items[$langcode] as $item) {
+ $tid_all[$item['tid']] = $item['tid'];
+ }
+ }
+ }
+ }
+ }
+ // Insert index entries for all the node's terms.
+ if (!empty($tid_all)) {
+ $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created'));
+ foreach ($tid_all as $tid) {
+ $query->values(array(
+ 'nid' => $node->nid,
+ 'tid' => $tid,
+ 'sticky' => $sticky,
+ 'created' => $node->created,
+ ));
+ }
+ $query->execute();
+ }
+ }
+}
+
+/**
+ * Implements hook_node_update().
+ */
+function taxonomy_node_update($node) {
+ // Always rebuild the node's taxonomy index entries on node save.
+ taxonomy_delete_node_index($node);
+ taxonomy_build_node_index($node);
+}
+
+/**
+ * Implements hook_node_delete().
+ */
+function taxonomy_node_delete($node) {
+ // Clean up the {taxonomy_index} table when nodes are deleted.
+ taxonomy_delete_node_index($node);
+}
+
+/**
+ * Deletes taxonomy index entries for a given node.
+ *
+ * @param $node
+ * The node object.
+ */
+function taxonomy_delete_node_index($node) {
+ if (variable_get('taxonomy_maintain_index_table', TRUE)) {
+ db_delete('taxonomy_index')->condition('nid', $node->nid)->execute();
+ }
+}
+
+/**
+ * Implements hook_taxonomy_term_delete().
+ */
+function taxonomy_taxonomy_term_delete($term) {
+ if (variable_get('taxonomy_maintain_index_table', TRUE)) {
+ // Clean up the {taxonomy_index} table when terms are deleted.
+ db_delete('taxonomy_index')->condition('tid', $term->tid)->execute();
+ }
+}
+
+/**
+ * @} End of "defgroup taxonomy_index".
+ */
+
+/**
+ * Implements hook_entity_query_alter().
+ *
+ * Converts EntityFieldQuery instances on taxonomy terms that have an entity
+ * condition on term bundles (vocabulary machine names). Since the vocabulary
+ * machine name is not present in the {taxonomy_term_data} table itself, we have
+ * to convert the bundle condition into a property condition of vocabulary IDs
+ * to match against {taxonomy_term_data}.vid.
+ */
+function taxonomy_entity_query_alter($query) {
+ $conditions = &$query->entityConditions;
+
+ // Alter only taxonomy term queries with bundle conditions.
+ if (isset($conditions['entity_type']) && $conditions['entity_type']['value'] == 'taxonomy_term' && isset($conditions['bundle'])) {
+ // Convert vocabulary machine names to vocabulary IDs.
+ $vocabulary_data = taxonomy_vocabulary_get_names();
+ $vids = array();
+ if (is_array($conditions['bundle']['value'])) {
+ foreach ($conditions['bundle']['value'] as $vocabulary_machine_name) {
+ $vids[] = $vocabulary_data[$vocabulary_machine_name]->vid;
+ }
+ }
+ else {
+ $vocabulary_machine_name = $conditions['bundle']['value'];
+ $vids = $vocabulary_data[$vocabulary_machine_name]->vid;
+ }
+
+ $query->propertyCondition('vid', $vids, $conditions['bundle']['operator']);
+ unset($conditions['bundle']);
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.pages.inc b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.pages.inc
new file mode 100644
index 0000000..975ff12
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.pages.inc
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * @file
+ * Page callbacks for the taxonomy module.
+ */
+
+/**
+ * Menu callback; displays all nodes associated with a term.
+ *
+ * @param $term
+ * The taxonomy term.
+ * @return
+ * The page content.
+ */
+function taxonomy_term_page($term) {
+ // If there is a menu link to this term, the link becomes the last part of
+ // the active trail, and the link name becomes the page title. Thus, we must
+ // explicitly set the page title to be the term title.
+ drupal_set_title($term->name);
+
+ // Build breadcrumb based on the hierarchy of the term.
+ $current = (object) array(
+ 'tid' => $term->tid,
+ );
+ // @todo This overrides any other possible breadcrumb and is a pure hard-coded
+ // presumption. Make this behavior configurable per vocabulary or term.
+ $breadcrumb = array();
+ while ($parents = taxonomy_get_parents($current->tid)) {
+ $current = array_shift($parents);
+ $breadcrumb[] = l($current->name, 'taxonomy/term/' . $current->tid);
+ }
+ $breadcrumb[] = l(t('Home'), NULL);
+ $breadcrumb = array_reverse($breadcrumb);
+ drupal_set_breadcrumb($breadcrumb);
+ drupal_add_feed('taxonomy/term/' . $term->tid . '/feed', 'RSS - ' . $term->name);
+
+ // Set the term path as the canonical URL to prevent duplicate content.
+ $uri = entity_uri('taxonomy_term', $term);
+ drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
+ // Set the non-aliased path as a default shortlink.
+ drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
+
+ // Normally we would call taxonomy_term_show() here, but for backwards
+ // compatibility in Drupal 7 we do not want to do that (it produces different
+ // data structures and HTML markup than what Drupal 7 released with). Calling
+ // taxonomy_term_view() directly provides essentially the same thing, but
+ // allows us to wrap the rendered term in our desired array structure.
+ $build['term_heading'] = array(
+ '#prefix' => '<div class="term-listing-heading">',
+ '#suffix' => '</div>',
+ 'term' => taxonomy_term_view($term, 'full'),
+ );
+
+ if ($nids = taxonomy_select_nodes($term->tid, TRUE, variable_get('default_nodes_main', 10))) {
+ $nodes = node_load_multiple($nids);
+ $build += node_view_multiple($nodes);
+ $build['pager'] = array(
+ '#theme' => 'pager',
+ '#weight' => 5,
+ );
+ }
+ else {
+ $build['no_content'] = array(
+ '#prefix' => '<p>',
+ '#markup' => t('There is currently no content classified with this term.'),
+ '#suffix' => '</p>',
+ );
+ }
+ return $build;
+}
+
+/**
+ * Generate the content feed for a taxonomy term.
+ *
+ * @param $term
+ * The taxonomy term.
+ */
+function taxonomy_term_feed($term) {
+ $channel['link'] = url('taxonomy/term/' . $term->tid, array('absolute' => TRUE));
+ $channel['title'] = variable_get('site_name', 'Drupal') . ' - ' . $term->name;
+ // Only display the description if we have a single term, to avoid clutter and confusion.
+ // HTML will be removed from feed description.
+ $channel['description'] = check_markup($term->description, $term->format, '', TRUE);
+ $nids = taxonomy_select_nodes($term->tid, FALSE, variable_get('feed_default_items', 10));
+
+ node_feed($nids, $channel);
+}
+
+/**
+ * Page callback: Outputs JSON for taxonomy autocomplete suggestions.
+ *
+ * Path: taxonomy/autocomplete
+ *
+ * This callback outputs term name suggestions in response to Ajax requests
+ * made by the taxonomy autocomplete widget for taxonomy term reference
+ * fields. The output is a JSON object of plain-text term suggestions, keyed by
+ * the user-entered value with the completed term name appended. Term names
+ * containing commas are wrapped in quotes.
+ *
+ * For example, suppose the user has entered the string 'red fish, blue' in the
+ * field, and there are two taxonomy terms, 'blue fish' and 'blue moon'. The
+ * JSON output would have the following structure:
+ * @code
+ * {
+ * "red fish, blue fish": "blue fish",
+ * "red fish, blue moon": "blue moon",
+ * };
+ * @endcode
+ *
+ * @param $field_name
+ * The name of the term reference field.
+ * @param $tags_typed
+ * (optional) A comma-separated list of term names entered in the
+ * autocomplete form element. Only the last term is used for autocompletion.
+ * Defaults to '' (an empty string).
+ *
+ * @see taxonomy_menu()
+ * @see taxonomy_field_widget_info()
+ */
+function taxonomy_autocomplete($field_name = '', $tags_typed = '') {
+ // If the request has a '/' in the search text, then the menu system will have
+ // split it into multiple arguments, recover the intended $tags_typed.
+ $args = func_get_args();
+ // Shift off the $field_name argument.
+ array_shift($args);
+ $tags_typed = implode('/', $args);
+
+ // Make sure the field exists and is a taxonomy field.
+ if (!($field = field_info_field($field_name)) || $field['type'] !== 'taxonomy_term_reference') {
+ // Error string. The JavaScript handler will realize this is not JSON and
+ // will display it as debugging information.
+ print t('Taxonomy field @field_name not found.', array('@field_name' => $field_name));
+ exit;
+ }
+
+ // The user enters a comma-separated list of tags. We only autocomplete the last tag.
+ $tags_typed = drupal_explode_tags($tags_typed);
+ $tag_last = drupal_strtolower(array_pop($tags_typed));
+
+ $term_matches = array();
+ if ($tag_last != '') {
+
+ // Part of the criteria for the query come from the field's own settings.
+ $vids = array();
+ $vocabularies = taxonomy_vocabulary_get_names();
+ foreach ($field['settings']['allowed_values'] as $tree) {
+ $vids[] = $vocabularies[$tree['vocabulary']]->vid;
+ }
+
+ $query = db_select('taxonomy_term_data', 't');
+ $query->addTag('translatable');
+ $query->addTag('term_access');
+
+ // Do not select already entered terms.
+ if (!empty($tags_typed)) {
+ $query->condition('t.name', $tags_typed, 'NOT IN');
+ }
+ // Select rows that match by term name.
+ $tags_return = $query
+ ->fields('t', array('tid', 'name'))
+ ->condition('t.vid', $vids)
+ ->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE')
+ ->range(0, 10)
+ ->execute()
+ ->fetchAllKeyed();
+
+ $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
+
+ foreach ($tags_return as $tid => $name) {
+ $n = $name;
+ // Term names containing commas or quotes must be wrapped in quotes.
+ if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
+ $n = '"' . str_replace('"', '""', $name) . '"';
+ }
+ $term_matches[$prefix . $n] = check_plain($name);
+ }
+ }
+
+ drupal_json_output($term_matches);
+}
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.test b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.test
new file mode 100644
index 0000000..665f9ae
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.test
@@ -0,0 +1,1980 @@
+<?php
+
+/**
+ * @file
+ * Tests for taxonomy.module.
+ */
+
+/**
+ * Provides common helper methods for Taxonomy module tests.
+ */
+class TaxonomyWebTestCase extends DrupalWebTestCase {
+
+ /**
+ * Returns a new vocabulary with random properties.
+ */
+ function createVocabulary() {
+ // Create a vocabulary.
+ $vocabulary = new stdClass();
+ $vocabulary->name = $this->randomName();
+ $vocabulary->description = $this->randomName();
+ $vocabulary->machine_name = drupal_strtolower($this->randomName());
+ $vocabulary->help = '';
+ $vocabulary->nodes = array('article' => 'article');
+ $vocabulary->weight = mt_rand(0, 10);
+ taxonomy_vocabulary_save($vocabulary);
+ return $vocabulary;
+ }
+
+ /**
+ * Returns a new term with random properties in vocabulary $vid.
+ */
+ function createTerm($vocabulary) {
+ $term = new stdClass();
+ $term->name = $this->randomName();
+ $term->description = $this->randomName();
+ // Use the first available text format.
+ $term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField();
+ $term->vid = $vocabulary->vid;
+ taxonomy_term_save($term);
+ return $term;
+ }
+
+}
+
+/**
+ * Tests the taxonomy vocabulary interface.
+ */
+class TaxonomyVocabularyFunctionalTest extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy vocabulary interface',
+ 'description' => 'Test the taxonomy vocabulary interface.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy'));
+ $this->drupalLogin($this->admin_user);
+ $this->vocabulary = $this->createVocabulary();
+ }
+
+ /**
+ * Create, edit and delete a vocabulary via the user interface.
+ */
+ function testVocabularyInterface() {
+ // Visit the main taxonomy administration page.
+ $this->drupalGet('admin/structure/taxonomy');
+
+ // Create a new vocabulary.
+ $this->clickLink(t('Add vocabulary'));
+ $edit = array();
+ $machine_name = drupal_strtolower($this->randomName());
+ $edit['name'] = $this->randomName();
+ $edit['description'] = $this->randomName();
+ $edit['machine_name'] = $machine_name;
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertRaw(t('Created new vocabulary %name.', array('%name' => $edit['name'])), 'Vocabulary created successfully.');
+
+ // Edit the vocabulary.
+ $this->drupalGet('admin/structure/taxonomy');
+ $this->assertText($edit['name'], 'Vocabulary found in the vocabulary overview listing.');
+ $this->clickLink(t('edit vocabulary'));
+ $edit = array();
+ $edit['name'] = $this->randomName();
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->drupalGet('admin/structure/taxonomy');
+ $this->assertText($edit['name'], 'Vocabulary found in the vocabulary overview listing.');
+
+ // Try to submit a vocabulary with a duplicate machine name.
+ $edit['machine_name'] = $machine_name;
+ $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
+ $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
+
+ // Try to submit an invalid machine name.
+ $edit['machine_name'] = '!&^%';
+ $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
+ $this->assertText(t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
+
+ // Ensure that vocabulary titles are escaped properly.
+ $edit = array();
+ $edit['name'] = 'Don\'t Panic';
+ $edit['description'] = $this->randomName();
+ $edit['machine_name'] = 'don_t_panic';
+ $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
+
+ $site_name = variable_get('site_name', 'Drupal');
+ $this->drupalGet('admin/structure/taxonomy/don_t_panic');
+ $this->assertTitle(t('Don\'t Panic | @site-name', array('@site-name' => $site_name)));
+ $this->assertNoTitle(t('Don&#039;t Panic | @site-name', array('@site-name' => $site_name)));
+ }
+
+ /**
+ * Changing weights on the vocabulary overview with two or more vocabularies.
+ */
+ function testTaxonomyAdminChangingWeights() {
+ // Create some vocabularies.
+ for ($i = 0; $i < 10; $i++) {
+ $this->createVocabulary();
+ }
+ // Get all vocabularies and change their weights.
+ $vocabularies = taxonomy_get_vocabularies();
+ $edit = array();
+ foreach ($vocabularies as $key => $vocabulary) {
+ $vocabulary->weight = -$vocabulary->weight;
+ $vocabularies[$key]->weight = $vocabulary->weight;
+ $edit[$key . '[weight]'] = $vocabulary->weight;
+ }
+ // Saving the new weights via the interface.
+ $this->drupalPost('admin/structure/taxonomy', $edit, t('Save'));
+
+ // Load the vocabularies from the database.
+ $new_vocabularies = taxonomy_get_vocabularies();
+
+ // Check that the weights are saved in the database correctly.
+ foreach ($vocabularies as $key => $vocabulary) {
+ $this->assertEqual($new_vocabularies[$key]->weight, $vocabularies[$key]->weight, 'The vocabulary weight was changed.');
+ }
+ }
+
+ /**
+ * Test the vocabulary overview with no vocabularies.
+ */
+ function testTaxonomyAdminNoVocabularies() {
+ // Delete all vocabularies.
+ $vocabularies = taxonomy_get_vocabularies();
+ foreach ($vocabularies as $key => $vocabulary) {
+ taxonomy_vocabulary_delete($key);
+ }
+ // Confirm that no vocabularies are found in the database.
+ $this->assertFalse(taxonomy_get_vocabularies(), 'No vocabularies found in the database.');
+ $this->drupalGet('admin/structure/taxonomy');
+ // Check the default message for no vocabularies.
+ $this->assertText(t('No vocabularies available.'), 'No vocabularies were found.');
+ }
+
+ /**
+ * Deleting a vocabulary.
+ */
+ function testTaxonomyAdminDeletingVocabulary() {
+ // Create a vocabulary.
+ $edit = array(
+ 'name' => $this->randomName(),
+ 'machine_name' => drupal_strtolower($this->randomName()),
+ );
+ $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
+ $this->assertText(t('Created new vocabulary'), 'New vocabulary was created.');
+
+ // Check the created vocabulary.
+ $vocabularies = taxonomy_get_vocabularies();
+ $vocabularies_keys = array_keys($vocabularies);
+ $vid = $vocabularies[end($vocabularies_keys)]->vid;
+ entity_get_controller('taxonomy_vocabulary')->resetCache();
+ $vocabulary = taxonomy_vocabulary_load($vid);
+ $this->assertTrue($vocabulary, 'Vocabulary found in database.');
+
+ // Delete the vocabulary.
+ $edit = array();
+ $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit', $edit, t('Delete'));
+ $this->assertRaw(t('Are you sure you want to delete the vocabulary %name?', array('%name' => $vocabulary->name)), '[confirm deletion] Asks for confirmation.');
+ $this->assertText(t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'), '[confirm deletion] Inform that all terms will be deleted.');
+
+ // Confirm deletion.
+ $this->drupalPost(NULL, NULL, t('Delete'));
+ $this->assertRaw(t('Deleted vocabulary %name.', array('%name' => $vocabulary->name)), 'Vocabulary deleted.');
+ entity_get_controller('taxonomy_vocabulary')->resetCache();
+ $this->assertFalse(taxonomy_vocabulary_load($vid), 'Vocabulary is not found in the database.');
+ }
+
+}
+
+/**
+ * Tests for taxonomy vocabulary functions.
+ */
+class TaxonomyVocabularyTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy vocabularies',
+ 'description' => 'Test loading, saving and deleting vocabularies.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy', 'field_test');
+ $admin_user = $this->drupalCreateUser(array('create article content', 'administer taxonomy'));
+ $this->drupalLogin($admin_user);
+ $this->vocabulary = $this->createVocabulary();
+ }
+
+ /**
+ * Ensure that when an invalid vocabulary vid is loaded, it is possible
+ * to load the same vid successfully if it subsequently becomes valid.
+ */
+ function testTaxonomyVocabularyLoadReturnFalse() {
+ // Load a vocabulary that doesn't exist.
+ $vocabularies = taxonomy_get_vocabularies();
+ $vid = count($vocabularies) + 1;
+ $vocabulary = taxonomy_vocabulary_load($vid);
+ // This should not return an object because no such vocabulary exists.
+ $this->assertTrue(empty($vocabulary), 'No object loaded.');
+
+ // Create a new vocabulary.
+ $this->createVocabulary();
+ // Load the vocabulary with the same $vid from earlier.
+ // This should return a vocabulary object since it now matches a real vid.
+ $vocabulary = taxonomy_vocabulary_load($vid);
+ $this->assertTrue(!empty($vocabulary) && is_object($vocabulary), 'Vocabulary is an object.');
+ $this->assertEqual($vocabulary->vid, $vid, 'Valid vocabulary vid is the same as our previously invalid one.');
+ }
+
+ /**
+ * Test deleting a taxonomy that contains terms.
+ */
+ function testTaxonomyVocabularyDeleteWithTerms() {
+ // Delete any existing vocabularies.
+ foreach (taxonomy_get_vocabularies() as $vocabulary) {
+ taxonomy_vocabulary_delete($vocabulary->vid);
+ }
+
+ // Assert that there are no terms left.
+ $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField());
+
+ // Create a new vocabulary and add a few terms to it.
+ $vocabulary = $this->createVocabulary();
+ $terms = array();
+ for ($i = 0; $i < 5; $i++) {
+ $terms[$i] = $this->createTerm($vocabulary);
+ }
+
+ // Set up hierarchy. term 2 is a child of 1 and 4 a child of 1 and 2.
+ $terms[2]->parent = array($terms[1]->tid);
+ taxonomy_term_save($terms[2]);
+ $terms[4]->parent = array($terms[1]->tid, $terms[2]->tid);
+ taxonomy_term_save($terms[4]);
+
+ // Assert that there are now 5 terms.
+ $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField());
+
+ taxonomy_vocabulary_delete($vocabulary->vid);
+
+ // Assert that there are no terms left.
+ $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField());
+ }
+
+ /**
+ * Ensure that the vocabulary static reset works correctly.
+ */
+ function testTaxonomyVocabularyLoadStaticReset() {
+ $original_vocabulary = taxonomy_vocabulary_load($this->vocabulary->vid);
+ $this->assertTrue(is_object($original_vocabulary), 'Vocabulary loaded successfully.');
+ $this->assertEqual($this->vocabulary->name, $original_vocabulary->name, 'Vocabulary loaded successfully.');
+
+ // Change the name and description.
+ $vocabulary = $original_vocabulary;
+ $vocabulary->name = $this->randomName();
+ $vocabulary->description = $this->randomName();
+ taxonomy_vocabulary_save($vocabulary);
+
+ // Load the vocabulary.
+ $new_vocabulary = taxonomy_vocabulary_load($original_vocabulary->vid);
+ $this->assertEqual($new_vocabulary->name, $vocabulary->name);
+ $this->assertEqual($new_vocabulary->name, $vocabulary->name);
+
+ // Delete the vocabulary.
+ taxonomy_vocabulary_delete($this->vocabulary->vid);
+ $vocabularies = taxonomy_get_vocabularies();
+ $this->assertTrue(!isset($vocabularies[$this->vocabulary->vid]), 'The vocabulary was deleted.');
+ }
+
+ /**
+ * Tests for loading multiple vocabularies.
+ */
+ function testTaxonomyVocabularyLoadMultiple() {
+
+ // Delete any existing vocabularies.
+ foreach (taxonomy_get_vocabularies() as $vocabulary) {
+ taxonomy_vocabulary_delete($vocabulary->vid);
+ }
+
+ // Create some vocabularies and assign weights.
+ $vocabulary1 = $this->createVocabulary();
+ $vocabulary1->weight = 0;
+ taxonomy_vocabulary_save($vocabulary1);
+ $vocabulary2 = $this->createVocabulary();
+ $vocabulary2->weight = 1;
+ taxonomy_vocabulary_save($vocabulary2);
+ $vocabulary3 = $this->createVocabulary();
+ $vocabulary3->weight = 2;
+ taxonomy_vocabulary_save($vocabulary3);
+
+ // Fetch the names for all vocabularies, confirm that they are keyed by
+ // machine name.
+ $names = taxonomy_vocabulary_get_names();
+ $this->assertEqual($names[$vocabulary1->machine_name]->name, $vocabulary1->name, 'Vocabulary 1 name found.');
+
+ // Fetch all of the vocabularies using taxonomy_get_vocabularies().
+ // Confirm that the vocabularies are ordered by weight.
+ $vocabularies = taxonomy_get_vocabularies();
+ $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary1->vid, 'Vocabulary was found in the vocabularies array.');
+ $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary2->vid, 'Vocabulary was found in the vocabularies array.');
+ $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary3->vid, 'Vocabulary was found in the vocabularies array.');
+
+ // Fetch the vocabularies with taxonomy_vocabulary_load_multiple(), specifying IDs.
+ // Ensure they are returned in the same order as the original array.
+ $vocabularies = taxonomy_vocabulary_load_multiple(array($vocabulary3->vid, $vocabulary2->vid, $vocabulary1->vid));
+ $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary3->vid, 'Vocabulary loaded successfully by ID.');
+ $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary2->vid, 'Vocabulary loaded successfully by ID.');
+ $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary1->vid, 'Vocabulary loaded successfully by ID.');
+
+ // Fetch vocabulary 1 by name.
+ $vocabulary = current(taxonomy_vocabulary_load_multiple(array(), array('name' => $vocabulary1->name)));
+ $this->assertEqual($vocabulary->vid, $vocabulary1->vid, 'Vocabulary loaded successfully by name.');
+
+ // Fetch vocabulary 1 by name and ID.
+ $this->assertEqual(current(taxonomy_vocabulary_load_multiple(array($vocabulary1->vid), array('name' => $vocabulary1->name)))->vid, $vocabulary1->vid, 'Vocabulary loaded successfully by name and ID.');
+ }
+
+ /**
+ * Tests that machine name changes are properly reflected.
+ */
+ function testTaxonomyVocabularyChangeMachineName() {
+ // Add a field instance to the vocabulary.
+ $field = array(
+ 'field_name' => 'field_test',
+ 'type' => 'test_field',
+ );
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => 'field_test',
+ 'entity_type' => 'taxonomy_term',
+ 'bundle' => $this->vocabulary->machine_name,
+ );
+ field_create_instance($instance);
+
+ // Change the machine name.
+ $new_name = drupal_strtolower($this->randomName());
+ $this->vocabulary->machine_name = $new_name;
+ taxonomy_vocabulary_save($this->vocabulary);
+
+ // Check that the field instance is still attached to the vocabulary.
+ $this->assertTrue(field_info_instance('taxonomy_term', 'field_test', $new_name), 'The bundle name was updated correctly.');
+ }
+
+ /**
+ * Test uninstall and reinstall of the taxonomy module.
+ */
+ function testUninstallReinstall() {
+ // Fields and field instances attached to taxonomy term bundles should be
+ // removed when the module is uninstalled.
+ $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $this->field = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4);
+ $this->field = field_create_field($this->field);
+ $this->instance = array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => 'taxonomy_term',
+ 'bundle' => $this->vocabulary->machine_name,
+ 'label' => $this->randomName() . '_label',
+ );
+ field_create_instance($this->instance);
+
+ module_disable(array('taxonomy'));
+ require_once DRUPAL_ROOT . '/includes/install.inc';
+ drupal_uninstall_modules(array('taxonomy'));
+ module_enable(array('taxonomy'));
+
+ // Now create a vocabulary with the same name. All field instances
+ // connected to this vocabulary name should have been removed when the
+ // module was uninstalled. Creating a new field with the same name and
+ // an instance of this field on the same bundle name should be successful.
+ unset($this->vocabulary->vid);
+ taxonomy_vocabulary_save($this->vocabulary);
+ unset($this->field['id']);
+ field_create_field($this->field);
+ field_create_instance($this->instance);
+ }
+
+}
+
+/**
+ * Unit tests for taxonomy term functions.
+ */
+class TaxonomyTermFunctionTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term unit tests',
+ 'description' => 'Unit tests for taxonomy term functions.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function testTermDelete() {
+ $vocabulary = $this->createVocabulary();
+ $valid_term = $this->createTerm($vocabulary);
+ // Delete a valid term.
+ taxonomy_term_delete($valid_term->tid);
+ $terms = taxonomy_term_load_multiple(array(), array('vid' => $vocabulary->vid));
+ $this->assertTrue(empty($terms), 'Vocabulary is empty after deletion.');
+
+ // Delete an invalid term. Should not throw any notices.
+ taxonomy_term_delete(42);
+ }
+
+ /**
+ * Test a taxonomy with terms that have multiple parents of different depths.
+ */
+ function testTaxonomyVocabularyTree() {
+ // Create a new vocabulary with 6 terms.
+ $vocabulary = $this->createVocabulary();
+ $term = array();
+ for ($i = 0; $i < 6; $i++) {
+ $term[$i] = $this->createTerm($vocabulary);
+ }
+
+ // $term[2] is a child of 1 and 5.
+ $term[2]->parent = array($term[1]->tid, $term[5]->tid);
+ taxonomy_term_save($term[2]);
+ // $term[3] is a child of 2.
+ $term[3]->parent = array($term[2]->tid);
+ taxonomy_term_save($term[3]);
+ // $term[5] is a child of 4.
+ $term[5]->parent = array($term[4]->tid);
+ taxonomy_term_save($term[5]);
+
+ /**
+ * Expected tree:
+ * term[0] | depth: 0
+ * term[1] | depth: 0
+ * -- term[2] | depth: 1
+ * ---- term[3] | depth: 2
+ * term[4] | depth: 0
+ * -- term[5] | depth: 1
+ * ---- term[2] | depth: 2
+ * ------ term[3] | depth: 3
+ */
+ // Count $term[1] parents with $max_depth = 1.
+ $tree = taxonomy_get_tree($vocabulary->vid, $term[1]->tid, 1);
+ $this->assertEqual(1, count($tree), 'We have one parent with depth 1.');
+
+ // Count all vocabulary tree elements.
+ $tree = taxonomy_get_tree($vocabulary->vid);
+ $this->assertEqual(8, count($tree), 'We have all vocabulary tree elements.');
+
+ // Count elements in every tree depth.
+ foreach ($tree as $element) {
+ if (!isset($depth_count[$element->depth])) {
+ $depth_count[$element->depth] = 0;
+ }
+ $depth_count[$element->depth]++;
+ }
+ $this->assertEqual(3, $depth_count[0], 'Three elements in taxonomy tree depth 0.');
+ $this->assertEqual(2, $depth_count[1], 'Two elements in taxonomy tree depth 1.');
+ $this->assertEqual(2, $depth_count[2], 'Two elements in taxonomy tree depth 2.');
+ $this->assertEqual(1, $depth_count[3], 'One element in taxonomy tree depth 3.');
+ }
+
+}
+
+/**
+ * Test for legacy node bug.
+ */
+class TaxonomyLegacyTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Test for legacy node bug.',
+ 'description' => 'Posts an article with a taxonomy term and a date prior to 1970.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy');
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'administer nodes', 'bypass node access'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Test taxonomy functionality with nodes prior to 1970.
+ */
+ function testTaxonomyLegacyNode() {
+ // Posts an article with a taxonomy term and a date prior to 1970.
+ $langcode = LANGUAGE_NONE;
+ $edit = array();
+ $edit['title'] = $this->randomName();
+ $edit['date'] = '1969-01-01 00:00:00 -0500';
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit["field_tags[$langcode]"] = $this->randomName();
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+ // Checks that the node has been saved.
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+ $this->assertEqual($node->created, strtotime($edit['date']), 'Legacy node was saved with the right date.');
+ }
+
+}
+
+/**
+ * Tests for taxonomy term functions.
+ */
+class TaxonomyTermTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term functions and forms.',
+ 'description' => 'Test load, save and delete for taxonomy terms.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy');
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
+ $this->drupalLogin($this->admin_user);
+ $this->vocabulary = $this->createVocabulary();
+
+ $field = array(
+ 'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($field);
+
+ $this->instance = array(
+ 'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance);
+ }
+
+ /**
+ * Test terms in a single and multiple hierarchy.
+ */
+ function testTaxonomyTermHierarchy() {
+ // Create two taxonomy terms.
+ $term1 = $this->createTerm($this->vocabulary);
+ $term2 = $this->createTerm($this->vocabulary);
+
+ // Check that hierarchy is flat.
+ $vocabulary = taxonomy_vocabulary_load($this->vocabulary->vid);
+ $this->assertEqual(0, $vocabulary->hierarchy, 'Vocabulary is flat.');
+
+ // Edit $term2, setting $term1 as parent.
+ $edit = array();
+ $edit['parent[]'] = array($term1->tid);
+ $this->drupalPost('taxonomy/term/' . $term2->tid . '/edit', $edit, t('Save'));
+
+ // Check the hierarchy.
+ $children = taxonomy_get_children($term1->tid);
+ $parents = taxonomy_get_parents($term2->tid);
+ $this->assertTrue(isset($children[$term2->tid]), 'Child found correctly.');
+ $this->assertTrue(isset($parents[$term1->tid]), 'Parent found correctly.');
+
+ // Load and save a term, confirming that parents are still set.
+ $term = taxonomy_term_load($term2->tid);
+ taxonomy_term_save($term);
+ $parents = taxonomy_get_parents($term2->tid);
+ $this->assertTrue(isset($parents[$term1->tid]), 'Parent found correctly.');
+
+ // Create a third term and save this as a parent of term2.
+ $term3 = $this->createTerm($this->vocabulary);
+ $term2->parent = array($term1->tid, $term3->tid);
+ taxonomy_term_save($term2);
+ $parents = taxonomy_get_parents($term2->tid);
+ $this->assertTrue(isset($parents[$term1->tid]) && isset($parents[$term3->tid]), 'Both parents found successfully.');
+ }
+
+ /**
+ * Test that hook_node_$op implementations work correctly.
+ *
+ * Save & edit a node and assert that taxonomy terms are saved/loaded properly.
+ */
+ function testTaxonomyNode() {
+ // Create two taxonomy terms.
+ $term1 = $this->createTerm($this->vocabulary);
+ $term2 = $this->createTerm($this->vocabulary);
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit[$this->instance['field_name'] . '[' . $langcode . '][]'] = $term1->tid;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is displayed when the node is viewed.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText($term1->name, 'Term is displayed when viewing the node.');
+
+ // Edit the node with a different term.
+ $edit[$this->instance['field_name'] . '[' . $langcode . '][]'] = $term2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText($term2->name, 'Term is displayed when viewing the node.');
+
+ // Preview the node.
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Preview'));
+ $this->assertNoUniqueText($term2->name, 'Term is displayed when previewing the node.');
+ $this->drupalPost(NULL, NULL, t('Preview'));
+ $this->assertNoUniqueText($term2->name, 'Term is displayed when previewing the node again.');
+ }
+
+ /**
+ * Test term creation with a free-tagging vocabulary from the node form.
+ */
+ function testNodeTermCreationAndDeletion() {
+ // Enable tags in the vocabulary.
+ $instance = $this->instance;
+ $instance['widget'] = array('type' => 'taxonomy_autocomplete');
+ $instance['bundle'] = 'page';
+ field_create_instance($instance);
+ $terms = array(
+ 'term1' => $this->randomName(),
+ 'term2' => $this->randomName() . ', ' . $this->randomName(),
+ 'term3' => $this->randomName(),
+ );
+
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ // Insert the terms in a comma separated list. Vocabulary 1 is a
+ // free-tagging field created by the default profile.
+ $edit[$instance['field_name'] . "[$langcode]"] = drupal_implode_tags($terms);
+
+ // Preview and verify the terms appear but are not created.
+ $this->drupalPost('node/add/page', $edit, t('Preview'));
+ foreach ($terms as $term) {
+ $this->assertText($term, 'The term appears on the node preview.');
+ }
+ $tree = taxonomy_get_tree($this->vocabulary->vid);
+ $this->assertTrue(empty($tree), 'The terms are not created on preview.');
+
+ // taxonomy.module does not maintain its static caches.
+ drupal_static_reset();
+
+ // Save, creating the terms.
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+ $this->assertRaw(t('@type %title has been created.', array('@type' => t('Basic page'), '%title' => $edit["title"])), 'The node was created successfully.');
+ foreach ($terms as $term) {
+ $this->assertText($term, 'The term was saved and appears on the node page.');
+ }
+
+ // Get the created terms.
+ $term_objects = array();
+ foreach ($terms as $key => $term) {
+ $term_objects[$key] = taxonomy_get_term_by_name($term);
+ $term_objects[$key] = reset($term_objects[$key]);
+ }
+
+ // Delete term 1.
+ $this->drupalPost('taxonomy/term/' . $term_objects['term1']->tid . '/edit', array(), t('Delete'));
+ $this->drupalPost(NULL, NULL, t('Delete'));
+ $term_names = array($term_objects['term2']->name, $term_objects['term3']->name);
+
+ // Get the node.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $this->drupalGet('node/' . $node->nid);
+
+ foreach ($term_names as $term_name) {
+ $this->assertText($term_name, format_string('The term %name appears on the node page after one term %deleted was deleted', array('%name' => $term_name, '%deleted' => $term_objects['term1']->name)));
+ }
+ $this->assertNoText($term_objects['term1']->name, format_string('The deleted term %name does not appear on the node page.', array('%name' => $term_objects['term1']->name)));
+
+ // Test autocomplete on term 2, which contains a comma.
+ // The term will be quoted, and the " will be encoded in unicode (\u0022).
+ $input = substr($term_objects['term2']->name, 0, 3);
+ $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name . '/' . $input);
+ $this->assertRaw('{"\u0022' . $term_objects['term2']->name . '\u0022":"' . $term_objects['term2']->name . '"}', format_string('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term2']->name)));
+
+ // Test autocomplete on term 3 - it is alphanumeric only, so no extra
+ // quoting.
+ $input = substr($term_objects['term3']->name, 0, 3);
+ $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name . '/' . $input);
+ $this->assertRaw('{"' . $term_objects['term3']->name . '":"' . $term_objects['term3']->name . '"}', format_string('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term3']->name)));
+
+ // Test taxonomy autocomplete with a nonexistent field.
+ $field_name = $this->randomName();
+ $tag = $this->randomName();
+ $message = t("Taxonomy field @field_name not found.", array('@field_name' => $field_name));
+ $this->assertFalse(field_info_field($field_name), format_string('Field %field_name does not exist.', array('%field_name' => $field_name)));
+ $this->drupalGet('taxonomy/autocomplete/' . $field_name . '/' . $tag);
+ $this->assertRaw($message, 'Autocomplete returns correct error message when the taxonomy field does not exist.');
+
+ // Test the autocomplete path without passing a field_name and terms.
+ // This should not trigger a PHP notice.
+ $field_name = '';
+ $message = t("Taxonomy field @field_name not found.", array('@field_name' => $field_name));
+ $this->drupalGet('taxonomy/autocomplete');
+ $this->assertRaw($message, 'Autocomplete returns correct error message when no taxonomy field is given.');
+ }
+
+ /**
+ * Tests term autocompletion edge cases with slashes in the names.
+ */
+ function testTermAutocompletion() {
+ // Add a term with a slash in the name.
+ $first_term = $this->createTerm($this->vocabulary);
+ $first_term->name = '10/16/2011';
+ taxonomy_term_save($first_term);
+ // Add another term that differs after the slash character.
+ $second_term = $this->createTerm($this->vocabulary);
+ $second_term->name = '10/17/2011';
+ taxonomy_term_save($second_term);
+ // Add another term that has both a comma and a slash character.
+ $third_term = $this->createTerm($this->vocabulary);
+ $third_term->name = 'term with, a comma and / a slash';
+ taxonomy_term_save($third_term);
+
+ // Try to autocomplete a term name that matches both terms.
+ // We should get both term in a json encoded string.
+ $input = '10/';
+ $path = 'taxonomy/autocomplete/taxonomy_';
+ $path .= $this->vocabulary->machine_name . '/' . $input;
+ // The result order is not guaranteed, so check each term separately.
+ $url = url($path, array('absolute' => TRUE));
+ $result = drupal_http_request($url);
+ $data = drupal_json_decode($result->data);
+ $this->assertEqual($data[$first_term->name], check_plain($first_term->name), 'Autocomplete returned the first matching term.');
+ $this->assertEqual($data[$second_term->name], check_plain($second_term->name), 'Autocomplete returned the second matching term.');
+
+ // Try to autocomplete a term name that matches first term.
+ // We should only get the first term in a json encoded string.
+ $input = '10/16';
+ $url = 'taxonomy/autocomplete/taxonomy_';
+ $url .= $this->vocabulary->machine_name . '/' . $input;
+ $this->drupalGet($url);
+ $target = array($first_term->name => check_plain($first_term->name));
+ $this->assertRaw(drupal_json_encode($target), 'Autocomplete returns only the expected matching term.');
+
+ // Try to autocomplete a term name with both a comma and a slash.
+ $input = '"term with, comma and / a';
+ $url = 'taxonomy/autocomplete/taxonomy_';
+ $url .= $this->vocabulary->machine_name . '/' . $input;
+ $this->drupalGet($url);
+ $n = $third_term->name;
+ // Term names containing commas or quotes must be wrapped in quotes.
+ if (strpos($third_term->name, ',') !== FALSE || strpos($third_term->name, '"') !== FALSE) {
+ $n = '"' . str_replace('"', '""', $third_term->name) . '"';
+ }
+ $target = array($n => check_plain($third_term->name));
+ $this->assertRaw(drupal_json_encode($target), 'Autocomplete returns a term containing a comma and a slash.');
+ }
+
+ /**
+ * Save, edit and delete a term using the user interface.
+ */
+ function testTermInterface() {
+ $edit = array(
+ 'name' => $this->randomName(12),
+ 'description[value]' => $this->randomName(100),
+ );
+ // Explicitly set the parents field to 'root', to ensure that
+ // taxonomy_form_term_submit() handles the invalid term ID correctly.
+ $edit['parent[]'] = array(0);
+
+ // Create the term to edit.
+ $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', $edit, t('Save'));
+
+ $terms = taxonomy_get_term_by_name($edit['name']);
+ $term = reset($terms);
+ $this->assertNotNull($term, 'Term found in database.');
+
+ // Submitting a term takes us to the add page; we need the List page.
+ $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);
+
+ // Test edit link as accessed from Taxonomy administration pages.
+ // Because Simpletest creates its own database when running tests, we know
+ // the first edit link found on the listing page is to our term.
+ $this->clickLink(t('edit'));
+
+ $this->assertRaw($edit['name'], 'The randomly generated term name is present.');
+ $this->assertText($edit['description[value]'], 'The randomly generated term description is present.');
+
+ $edit = array(
+ 'name' => $this->randomName(14),
+ 'description[value]' => $this->randomName(102),
+ );
+
+ // Edit the term.
+ $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', $edit, t('Save'));
+
+ // Check that the term is still present at admin UI after edit.
+ $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);
+ $this->assertText($edit['name'], 'The randomly generated term name is present.');
+ $this->assertLink(t('edit'));
+
+ // View the term and check that it is correct.
+ $this->drupalGet('taxonomy/term/' . $term->tid);
+ $this->assertText($edit['name'], 'The randomly generated term name is present.');
+ $this->assertText($edit['description[value]'], 'The randomly generated term description is present.');
+
+ // Did this page request display a 'term-listing-heading'?
+ $this->assertPattern('|class="taxonomy-term-description"|', 'Term page displayed the term description element.');
+ // Check that it does NOT show a description when description is blank.
+ $term->description = '';
+ taxonomy_term_save($term);
+ $this->drupalGet('taxonomy/term/' . $term->tid);
+ $this->assertNoPattern('|class="taxonomy-term-description"|', 'Term page did not display the term description when description was blank.');
+
+ // Check that the term feed page is working.
+ $this->drupalGet('taxonomy/term/' . $term->tid . '/feed');
+
+ // Check that the term edit page does not try to interpret additional path
+ // components as arguments for taxonomy_form_term().
+ $this->drupalGet('taxonomy/term/' . $term->tid . '/edit/' . $this->randomName());
+
+ // Delete the term.
+ $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', array(), t('Delete'));
+ $this->drupalPost(NULL, NULL, t('Delete'));
+
+ // Assert that the term no longer exists.
+ $this->drupalGet('taxonomy/term/' . $term->tid);
+ $this->assertResponse(404, 'The taxonomy term page was not found.');
+ }
+
+ /**
+ * Save, edit and delete a term using the user interface.
+ */
+ function testTermReorder() {
+ $this->createTerm($this->vocabulary);
+ $this->createTerm($this->vocabulary);
+ $this->createTerm($this->vocabulary);
+
+ // Fetch the created terms in the default alphabetical order, i.e. term1
+ // precedes term2 alphabetically, and term2 precedes term3.
+ drupal_static_reset('taxonomy_get_tree');
+ drupal_static_reset('taxonomy_get_treeparent');
+ drupal_static_reset('taxonomy_get_treeterms');
+ list($term1, $term2, $term3) = taxonomy_get_tree($this->vocabulary->vid);
+
+ $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);
+
+ // Each term has four hidden fields, "tid:1:0[tid]", "tid:1:0[parent]",
+ // "tid:1:0[depth]", and "tid:1:0[weight]". Change the order to term2,
+ // term3, term1 by setting weight property, make term3 a child of term2 by
+ // setting the parent and depth properties, and update all hidden fields.
+ $edit = array(
+ 'tid:' . $term2->tid . ':0[tid]' => $term2->tid,
+ 'tid:' . $term2->tid . ':0[parent]' => 0,
+ 'tid:' . $term2->tid . ':0[depth]' => 0,
+ 'tid:' . $term2->tid . ':0[weight]' => 0,
+ 'tid:' . $term3->tid . ':0[tid]' => $term3->tid,
+ 'tid:' . $term3->tid . ':0[parent]' => $term2->tid,
+ 'tid:' . $term3->tid . ':0[depth]' => 1,
+ 'tid:' . $term3->tid . ':0[weight]' => 1,
+ 'tid:' . $term1->tid . ':0[tid]' => $term1->tid,
+ 'tid:' . $term1->tid . ':0[parent]' => 0,
+ 'tid:' . $term1->tid . ':0[depth]' => 0,
+ 'tid:' . $term1->tid . ':0[weight]' => 2,
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ drupal_static_reset('taxonomy_get_tree');
+ drupal_static_reset('taxonomy_get_treeparent');
+ drupal_static_reset('taxonomy_get_treeterms');
+ $terms = taxonomy_get_tree($this->vocabulary->vid);
+ $this->assertEqual($terms[0]->tid, $term2->tid, 'Term 2 was moved above term 1.');
+ $this->assertEqual($terms[1]->parents, array($term2->tid), 'Term 3 was made a child of term 2.');
+ $this->assertEqual($terms[2]->tid, $term1->tid, 'Term 1 was moved below term 2.');
+
+ $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name, array(), t('Reset to alphabetical'));
+ // Submit confirmation form.
+ $this->drupalPost(NULL, array(), t('Reset to alphabetical'));
+
+ drupal_static_reset('taxonomy_get_tree');
+ drupal_static_reset('taxonomy_get_treeparent');
+ drupal_static_reset('taxonomy_get_treeterms');
+ $terms = taxonomy_get_tree($this->vocabulary->vid);
+ $this->assertEqual($terms[0]->tid, $term1->tid, 'Term 1 was moved to back above term 2.');
+ $this->assertEqual($terms[1]->tid, $term2->tid, 'Term 2 was moved to back below term 1.');
+ $this->assertEqual($terms[2]->tid, $term3->tid, 'Term 3 is still below term 2.');
+ $this->assertEqual($terms[2]->parents, array($term2->tid), 'Term 3 is still a child of term 2.' . var_export($terms[1]->tid, 1));
+ }
+
+ /**
+ * Test saving a term with multiple parents through the UI.
+ */
+ function testTermMultipleParentsInterface() {
+ // Add a new term to the vocabulary so that we can have multiple parents.
+ $parent = $this->createTerm($this->vocabulary);
+
+ // Add a new term with multiple parents.
+ $edit = array(
+ 'name' => $this->randomName(12),
+ 'description[value]' => $this->randomName(100),
+ 'parent[]' => array(0, $parent->tid),
+ );
+ // Save the new term.
+ $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', $edit, t('Save'));
+
+ // Check that the term was successfully created.
+ $terms = taxonomy_get_term_by_name($edit['name']);
+ $term = reset($terms);
+ $this->assertNotNull($term, 'Term found in database.');
+ $this->assertEqual($edit['name'], $term->name, 'Term name was successfully saved.');
+ $this->assertEqual($edit['description[value]'], $term->description, 'Term description was successfully saved.');
+ // Check that the parent tid is still there. The other parent (<root>) is
+ // not added by taxonomy_get_parents().
+ $parents = taxonomy_get_parents($term->tid);
+ $parent = reset($parents);
+ $this->assertEqual($edit['parent[]'][1], $parent->tid, 'Term parents were successfully saved.');
+ }
+
+ /**
+ * Test taxonomy_get_term_by_name().
+ */
+ function testTaxonomyGetTermByName() {
+ $term = $this->createTerm($this->vocabulary);
+
+ // Load the term with the exact name.
+ $terms = taxonomy_get_term_by_name($term->name);
+ $this->assertTrue(isset($terms[$term->tid]), 'Term loaded using exact name.');
+
+ // Load the term with space concatenated.
+ $terms = taxonomy_get_term_by_name(' ' . $term->name . ' ');
+ $this->assertTrue(isset($terms[$term->tid]), 'Term loaded with extra whitespace.');
+
+ // Load the term with name uppercased.
+ $terms = taxonomy_get_term_by_name(strtoupper($term->name));
+ $this->assertTrue(isset($terms[$term->tid]), 'Term loaded with uppercased name.');
+
+ // Load the term with name lowercased.
+ $terms = taxonomy_get_term_by_name(strtolower($term->name));
+ $this->assertTrue(isset($terms[$term->tid]), 'Term loaded with lowercased name.');
+
+ // Try to load an invalid term name.
+ $terms = taxonomy_get_term_by_name('Banana');
+ $this->assertFalse($terms);
+
+ // Try to load the term using a substring of the name.
+ $terms = taxonomy_get_term_by_name(drupal_substr($term->name, 2));
+ $this->assertFalse($terms);
+
+ // Create a new term in a different vocabulary with the same name.
+ $new_vocabulary = $this->createVocabulary();
+ $new_term = new stdClass();
+ $new_term->name = $term->name;
+ $new_term->vid = $new_vocabulary->vid;
+ taxonomy_term_save($new_term);
+
+ // Load multiple terms with the same name.
+ $terms = taxonomy_get_term_by_name($term->name);
+ $this->assertEqual(count($terms), 2, 'Two terms loaded with the same name.');
+
+ // Load single term when restricted to one vocabulary.
+ $terms = taxonomy_get_term_by_name($term->name, $this->vocabulary->machine_name);
+ $this->assertEqual(count($terms), 1, 'One term loaded when restricted by vocabulary.');
+ $this->assertTrue(isset($terms[$term->tid]), 'Term loaded using exact name and vocabulary machine name.');
+
+ // Create a new term with another name.
+ $term2 = $this->createTerm($this->vocabulary);
+
+ // Try to load a term by name that doesn't exist in this vocabulary but
+ // exists in another vocabulary.
+ $terms = taxonomy_get_term_by_name($term2->name, $new_vocabulary->machine_name);
+ $this->assertFalse($terms, 'Invalid term name restricted by vocabulary machine name not loaded.');
+
+ // Try to load terms filtering by a non-existing vocabulary.
+ $terms = taxonomy_get_term_by_name($term2->name, 'non_existing_vocabulary');
+ $this->assertEqual(count($terms), 0, 'No terms loaded when restricted by a non-existing vocabulary.');
+ }
+
+}
+
+/**
+ * Tests the rendering of term reference fields in RSS feeds.
+ */
+class TaxonomyRSSTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy RSS Content.',
+ 'description' => 'Ensure that data added as terms appears in RSS feeds if "RSS Category" format is selected.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy');
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types'));
+ $this->drupalLogin($this->admin_user);
+ $this->vocabulary = $this->createVocabulary();
+
+ $field = array(
+ 'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($field);
+
+ $this->instance = array(
+ 'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance);
+ }
+
+ /**
+ * Tests that terms added to nodes are displayed in core RSS feed.
+ *
+ * Create a node and assert that taxonomy terms appear in rss.xml.
+ */
+ function testTaxonomyRSS() {
+ // Create two taxonomy terms.
+ $term1 = $this->createTerm($this->vocabulary);
+
+ // RSS display must be added manually.
+ $this->drupalGet("admin/structure/types/manage/article/display");
+ $edit = array(
+ "view_modes_custom[rss]" => '1',
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Change the format to 'RSS category'.
+ $this->drupalGet("admin/structure/types/manage/article/display/rss");
+ $edit = array(
+ "fields[taxonomy_" . $this->vocabulary->machine_name . "][type]" => 'taxonomy_term_reference_rss_category',
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit[$this->instance['field_name'] . '[' . $langcode . '][]'] = $term1->tid;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is displayed when the RSS feed is viewed.
+ $this->drupalGet('rss.xml');
+ $test_element = array(
+ 'key' => 'category',
+ 'value' => $term1->name,
+ 'attributes' => array(
+ 'domain' => url('taxonomy/term/' . $term1->tid, array('absolute' => TRUE)),
+ ),
+ );
+ $this->assertRaw(format_xml_elements(array($test_element)), 'Term is displayed when viewing the rss feed.');
+ }
+
+}
+
+/**
+ * Tests the hook implementations that maintain the taxonomy index.
+ */
+class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term index',
+ 'description' => 'Tests the hook implementations that maintain the taxonomy index.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy');
+
+ // Create an administrative user.
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
+ $this->drupalLogin($this->admin_user);
+
+ // Create a vocabulary and add two term reference fields to article nodes.
+ $this->vocabulary = $this->createVocabulary();
+
+ $this->field_name_1 = drupal_strtolower($this->randomName());
+ $this->field_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_1);
+ $this->instance_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_1);
+
+ $this->field_name_2 = drupal_strtolower($this->randomName());
+ $this->field_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_2);
+ $this->instance_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_2);
+ }
+
+ /**
+ * Tests that the taxonomy index is maintained properly.
+ */
+ function testTaxonomyIndex() {
+ // Create terms in the vocabulary.
+ $term_1 = $this->createTerm($this->vocabulary);
+ $term_2 = $this->createTerm($this->vocabulary);
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode][]"] = $term_1->tid;
+ $edit["{$this->field_name_2}[$langcode][]"] = $term_1->tid;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is indexed, and only once.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, 'Term 1 is indexed once.');
+
+ // Update the article to change one term.
+ $edit["{$this->field_name_1}[$langcode][]"] = $term_2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that both terms are indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, 'Term 1 is indexed.');
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, 'Term 2 is indexed.');
+
+ // Update the article to change another term.
+ $edit["{$this->field_name_2}[$langcode][]"] = $term_2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that only one term is indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, 'Term 1 is not indexed.');
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, 'Term 2 is indexed once.');
+
+ // Redo the above tests without interface.
+ $update_node = array(
+ 'nid' => $node->nid,
+ 'vid' => $node->vid,
+ 'uid' => $node->uid,
+ 'type' => $node->type,
+ 'title' => $this->randomName(),
+ );
+
+ // Update the article with no term changed.
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that the index was not changed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, 'Term 1 is not indexed.');
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, 'Term 2 is indexed once.');
+
+ // Update the article to change one term.
+ $update_node[$this->field_name_1][$langcode] = array(array('tid' => $term_1->tid));
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that both terms are indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, 'Term 1 is indexed.');
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, 'Term 2 is indexed.');
+
+ // Update the article to change another term.
+ $update_node[$this->field_name_2][$langcode] = array(array('tid' => $term_1->tid));
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that only one term is indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, 'Term 1 is indexed once.');
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, 'Term 2 is not indexed.');
+ }
+
+ /**
+ * Tests that there is a link to the parent term on the child term page.
+ */
+ function testTaxonomyTermHierarchyBreadcrumbs() {
+ // Create two taxonomy terms and set term2 as the parent of term1.
+ $term1 = $this->createTerm($this->vocabulary);
+ $term2 = $this->createTerm($this->vocabulary);
+ $term1->parent = array($term2->tid);
+ taxonomy_term_save($term1);
+
+ // Verify that the page breadcrumbs include a link to the parent term.
+ $this->drupalGet('taxonomy/term/' . $term1->tid);
+ $this->assertRaw(l($term2->name, 'taxonomy/term/' . $term2->tid), 'Parent term link is displayed when viewing the node.');
+ }
+
+}
+
+/**
+ * Test the taxonomy_term_load_multiple() function.
+ */
+class TaxonomyLoadMultipleTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term multiple loading',
+ 'description' => 'Test the loading of multiple taxonomy terms at once',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->taxonomy_admin = $this->drupalCreateUser(array('administer taxonomy'));
+ $this->drupalLogin($this->taxonomy_admin);
+ }
+
+ /**
+ * Create a vocabulary and some taxonomy terms, ensuring they're loaded
+ * correctly using taxonomy_term_load_multiple().
+ */
+ function testTaxonomyTermMultipleLoad() {
+ // Create a vocabulary.
+ $vocabulary = $this->createVocabulary();
+
+ // Create five terms in the vocabulary.
+ $i = 0;
+ while ($i < 5) {
+ $i++;
+ $this->createTerm($vocabulary);
+ }
+ // Load the terms from the vocabulary.
+ $terms = taxonomy_term_load_multiple(NULL, array('vid' => $vocabulary->vid));
+ $count = count($terms);
+ $this->assertEqual($count, 5, format_string('Correct number of terms were loaded. !count terms.', array('!count' => $count)));
+
+ // Load the same terms again by tid.
+ $terms2 = taxonomy_term_load_multiple(array_keys($terms));
+ $this->assertEqual($count, count($terms2), 'Five terms were loaded by tid.');
+ $this->assertEqual($terms, $terms2, 'Both arrays contain the same terms.');
+
+ // Load the terms by tid, with a condition on vid.
+ $terms3 = taxonomy_term_load_multiple(array_keys($terms2), array('vid' => $vocabulary->vid));
+ $this->assertEqual($terms2, $terms3);
+
+ // Remove one term from the array, then delete it.
+ $deleted = array_shift($terms3);
+ taxonomy_term_delete($deleted->tid);
+ $deleted_term = taxonomy_term_load($deleted->tid);
+ $this->assertFalse($deleted_term);
+
+ // Load terms from the vocabulary by vid.
+ $terms4 = taxonomy_term_load_multiple(NULL, array('vid' => $vocabulary->vid));
+ $this->assertEqual(count($terms4), 4, 'Correct number of terms were loaded.');
+ $this->assertFalse(isset($terms4[$deleted->tid]));
+
+ // Create a single term and load it by name.
+ $term = $this->createTerm($vocabulary);
+ $loaded_terms = taxonomy_term_load_multiple(array(), array('name' => $term->name));
+ $this->assertEqual(count($loaded_terms), 1, 'One term was loaded.');
+ $loaded_term = reset($loaded_terms);
+ $this->assertEqual($term->tid, $loaded_term->tid, 'Term loaded by name successfully.');
+ }
+}
+
+/**
+ * Tests for taxonomy hook invocation.
+ */
+class TaxonomyHooksTestCase extends TaxonomyWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term hooks',
+ 'description' => 'Hooks for taxonomy term load/save/delete.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy', 'taxonomy_test');
+ module_load_include('inc', 'taxonomy', 'taxonomy.pages');
+ $taxonomy_admin = $this->drupalCreateUser(array('administer taxonomy'));
+ $this->drupalLogin($taxonomy_admin);
+ }
+
+ /**
+ * Test that hooks are run correctly on creating, editing, viewing,
+ * and deleting a term.
+ *
+ * @see taxonomy_test.module
+ */
+ function testTaxonomyTermHooks() {
+ $vocabulary = $this->createVocabulary();
+
+ // Create a term with one antonym.
+ $edit = array(
+ 'name' => $this->randomName(),
+ 'antonym' => 'Long',
+ );
+ $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add', $edit, t('Save'));
+ $terms = taxonomy_get_term_by_name($edit['name']);
+ $term = reset($terms);
+ $this->assertEqual($term->antonym, $edit['antonym'], 'Antonym was loaded into the term object.');
+
+ // Update the term with a different antonym.
+ $edit = array(
+ 'name' => $this->randomName(),
+ 'antonym' => 'Short',
+ );
+ $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', $edit, t('Save'));
+ taxonomy_terms_static_reset();
+ $term = taxonomy_term_load($term->tid);
+ $this->assertEqual($edit['antonym'], $term->antonym, 'Antonym was successfully edited.');
+
+ // View the term and ensure that hook_taxonomy_term_view() and
+ // hook_entity_view() are invoked.
+ $term = taxonomy_term_load($term->tid);
+ $term_build = taxonomy_term_page($term);
+ $this->assertFalse(empty($term_build['term_heading']['term']['taxonomy_test_term_view_check']), 'hook_taxonomy_term_view() was invoked when viewing the term.');
+ $this->assertFalse(empty($term_build['term_heading']['term']['taxonomy_test_entity_view_check']), 'hook_entity_view() was invoked when viewing the term.');
+
+ // Delete the term.
+ taxonomy_term_delete($term->tid);
+ $antonym = db_query('SELECT tid FROM {taxonomy_term_antonym} WHERE tid = :tid', array(':tid' => $term->tid))->fetchField();
+ $this->assertFalse($antonym, 'The antonym were deleted from the database.');
+ }
+
+}
+
+/**
+ * Tests for taxonomy term field and formatter.
+ */
+class TaxonomyTermFieldTestCase extends TaxonomyWebTestCase {
+
+ protected $instance;
+ protected $vocabulary;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term reference field',
+ 'description' => 'Test the creation of term fields.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('field_test');
+
+ $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer taxonomy'));
+ $this->drupalLogin($web_user);
+ $this->vocabulary = $this->createVocabulary();
+
+ // Setup a field and instance.
+ $this->field_name = drupal_strtolower($this->randomName());
+ $this->field = array(
+ 'field_name' => $this->field_name,
+ 'type' => 'taxonomy_term_reference',
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => '0',
+ ),
+ ),
+ )
+ );
+ field_create_field($this->field);
+ $this->instance = array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'full' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance);
+ }
+
+ /**
+ * Test term field validation.
+ */
+ function testTaxonomyTermFieldValidation() {
+ // Test valid and invalid values with field_attach_validate().
+ $langcode = LANGUAGE_NONE;
+ $entity = field_test_create_stub_entity();
+ $term = $this->createTerm($this->vocabulary);
+ $entity->{$this->field_name}[$langcode][0]['tid'] = $term->tid;
+ try {
+ field_attach_validate('test_entity', $entity);
+ $this->pass('Correct term does not cause validation error.');
+ }
+ catch (FieldValidationException $e) {
+ $this->fail('Correct term does not cause validation error.');
+ }
+
+ $entity = field_test_create_stub_entity();
+ $bad_term = $this->createTerm($this->createVocabulary());
+ $entity->{$this->field_name}[$langcode][0]['tid'] = $bad_term->tid;
+ try {
+ field_attach_validate('test_entity', $entity);
+ $this->fail('Wrong term causes validation error.');
+ }
+ catch (FieldValidationException $e) {
+ $this->pass('Wrong term causes validation error.');
+ }
+ }
+
+ /**
+ * Test widgets.
+ */
+ function testTaxonomyTermFieldWidgets() {
+ // Create a term in the vocabulary.
+ $term = $this->createTerm($this->vocabulary);
+
+ // Display creation form.
+ $langcode = LANGUAGE_NONE;
+ $this->drupalGet('test-entity/add/test-bundle');
+ $this->assertFieldByName("{$this->field_name}[$langcode]", '', 'Widget is displayed.');
+
+ // Submit with some value.
+ $edit = array(
+ "{$this->field_name}[$langcode]" => array($term->tid),
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match);
+ $id = $match[1];
+ $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created.');
+
+ // Display the object.
+ $entity = field_test_entity_test_load($id);
+ $entities = array($id => $entity);
+ field_attach_prepare_view('test_entity', $entities, 'full');
+ $entity->content = field_attach_view('test_entity', $entity, 'full');
+ $this->content = drupal_render($entity->content);
+ $this->assertText($term->name, 'Term name is displayed.');
+
+ // Delete the vocabulary and verify that the widget is gone.
+ taxonomy_vocabulary_delete($this->vocabulary->vid);
+ $this->drupalGet('test-entity/add/test-bundle');
+ $this->assertNoFieldByName("{$this->field_name}[$langcode]", '', 'Widget is not displayed.');
+ }
+
+ /**
+ * Tests that vocabulary machine name changes are mirrored in field definitions.
+ */
+ function testTaxonomyTermFieldChangeMachineName() {
+ // Add several entries in the 'allowed_values' setting, to make sure that
+ // they all get updated.
+ $this->field['settings']['allowed_values'] = array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => '0',
+ ),
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => '0',
+ ),
+ array(
+ 'vocabulary' => 'foo',
+ 'parent' => '0',
+ ),
+ );
+ field_update_field($this->field);
+ // Change the machine name.
+ $old_name = $this->vocabulary->machine_name;
+ $new_name = drupal_strtolower($this->randomName());
+ $this->vocabulary->machine_name = $new_name;
+ taxonomy_vocabulary_save($this->vocabulary);
+
+ // Check that entity bundles are properly updated.
+ $info = entity_get_info('taxonomy_term');
+ $this->assertFalse(isset($info['bundles'][$old_name]), 'The old bundle name does not appear in entity_get_info().');
+ $this->assertTrue(isset($info['bundles'][$new_name]), 'The new bundle name appears in entity_get_info().');
+
+ // Check that the field instance is still attached to the vocabulary.
+ $field = field_info_field($this->field_name);
+ $allowed_values = $field['settings']['allowed_values'];
+ $this->assertEqual($allowed_values[0]['vocabulary'], $new_name, 'Index 0: Machine name was updated correctly.');
+ $this->assertEqual($allowed_values[1]['vocabulary'], $new_name, 'Index 1: Machine name was updated correctly.');
+ $this->assertEqual($allowed_values[2]['vocabulary'], 'foo', 'Index 2: Machine name was left untouched.');
+ }
+
+}
+
+/**
+ * Tests a taxonomy term reference field that allows multiple vocabularies.
+ */
+class TaxonomyTermFieldMultipleVocabularyTestCase extends TaxonomyWebTestCase {
+
+ protected $instance;
+ protected $vocabulary1;
+ protected $vocabulary2;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Multiple vocabulary term reference field',
+ 'description' => 'Tests term reference fields that allow multiple vocabularies.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('field_test');
+
+ $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer taxonomy'));
+ $this->drupalLogin($web_user);
+ $this->vocabulary1 = $this->createVocabulary();
+ $this->vocabulary2 = $this->createVocabulary();
+
+ // Set up a field and instance.
+ $this->field_name = drupal_strtolower($this->randomName());
+ $this->field = array(
+ 'field_name' => $this->field_name,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary1->machine_name,
+ 'parent' => '0',
+ ),
+ array(
+ 'vocabulary' => $this->vocabulary2->machine_name,
+ 'parent' => '0',
+ ),
+ ),
+ )
+ );
+ field_create_field($this->field);
+ $this->instance = array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => 'test_entity',
+ 'bundle' => 'test_bundle',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'full' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance);
+ }
+
+ /**
+ * Tests term reference field and widget with multiple vocabularies.
+ */
+ function testTaxonomyTermFieldMultipleVocabularies() {
+ // Create a term in each vocabulary.
+ $term1 = $this->createTerm($this->vocabulary1);
+ $term2 = $this->createTerm($this->vocabulary2);
+
+ // Submit an entity with both terms.
+ $langcode = LANGUAGE_NONE;
+ $this->drupalGet('test-entity/add/test-bundle');
+ $this->assertFieldByName("{$this->field_name}[$langcode][]", '', 'Widget is displayed.');
+ $edit = array(
+ "{$this->field_name}[$langcode][]" => array($term1->tid, $term2->tid),
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match);
+ $id = $match[1];
+ $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created.');
+
+ // Render the entity.
+ $entity = field_test_entity_test_load($id);
+ $entities = array($id => $entity);
+ field_attach_prepare_view('test_entity', $entities, 'full');
+ $entity->content = field_attach_view('test_entity', $entity, 'full');
+ $this->content = drupal_render($entity->content);
+ $this->assertText($term1->name, 'Term 1 name is displayed.');
+ $this->assertText($term2->name, 'Term 2 name is displayed.');
+
+ // Delete vocabulary 2.
+ taxonomy_vocabulary_delete($this->vocabulary2->vid);
+
+ // Re-render the content.
+ $entity = field_test_entity_test_load($id);
+ $entities = array($id => $entity);
+ field_attach_prepare_view('test_entity', $entities, 'full');
+ $entity->content = field_attach_view('test_entity', $entity, 'full');
+ $this->plainTextContent = FALSE;
+ $this->content = drupal_render($entity->content);
+
+ // Term 1 should still be displayed; term 2 should not be.
+ $this->assertText($term1->name, 'Term 1 name is displayed.');
+ $this->assertNoText($term2->name, 'Term 2 name is not displayed.');
+
+ // Verify that field and instance settings are correct.
+ $field_info = field_info_field($this->field_name);
+ $this->assertEqual(sizeof($field_info['settings']['allowed_values']), 1, 'Only one vocabulary is allowed for the field.');
+
+ // The widget should still be displayed.
+ $this->drupalGet('test-entity/add/test-bundle');
+ $this->assertFieldByName("{$this->field_name}[$langcode][]", '', 'Widget is still displayed.');
+
+ // Term 1 should still pass validation.
+ $edit = array(
+ "{$this->field_name}[$langcode][]" => array($term1->tid),
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+ }
+
+}
+
+/**
+ * Test taxonomy token replacement in strings.
+ */
+class TaxonomyTokenReplaceTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy token replacement',
+ 'description' => 'Generates text using placeholders for dummy content to check taxonomy token replacement.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
+ $this->drupalLogin($this->admin_user);
+ $this->vocabulary = $this->createVocabulary();
+ $this->langcode = LANGUAGE_NONE;
+
+ $field = array(
+ 'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($field);
+
+ $this->instance = array(
+ 'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance);
+ }
+
+ /**
+ * Creates some terms and a node, then tests the tokens generated from them.
+ */
+ function testTaxonomyTokenReplacement() {
+ global $language;
+
+ // Create two taxonomy terms.
+ $term1 = $this->createTerm($this->vocabulary);
+ $term2 = $this->createTerm($this->vocabulary);
+
+ // Edit $term2, setting $term1 as parent.
+ $edit = array();
+ $edit['name'] = '<blink>Blinking Text</blink>';
+ $edit['parent[]'] = array($term1->tid);
+ $this->drupalPost('taxonomy/term/' . $term2->tid . '/edit', $edit, t('Save'));
+
+ // Create node with term2.
+ $edit = array();
+ $node = $this->drupalCreateNode(array('type' => 'article'));
+ $edit[$this->instance['field_name'] . '[' . $this->langcode . '][]'] = $term2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Generate and test sanitized tokens for term1.
+ $tests = array();
+ $tests['[term:tid]'] = $term1->tid;
+ $tests['[term:name]'] = check_plain($term1->name);
+ $tests['[term:description]'] = check_markup($term1->description, $term1->format);
+ $tests['[term:url]'] = url('taxonomy/term/' . $term1->tid, array('absolute' => TRUE));
+ $tests['[term:node-count]'] = 0;
+ $tests['[term:parent:name]'] = '[term:parent:name]';
+ $tests['[term:vocabulary:name]'] = check_plain($this->vocabulary->name);
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('term' => $term1), array('language' => $language));
+ $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
+ }
+
+ // Generate and test sanitized tokens for term2.
+ $tests = array();
+ $tests['[term:tid]'] = $term2->tid;
+ $tests['[term:name]'] = check_plain($term2->name);
+ $tests['[term:description]'] = check_markup($term2->description, $term2->format);
+ $tests['[term:url]'] = url('taxonomy/term/' . $term2->tid, array('absolute' => TRUE));
+ $tests['[term:node-count]'] = 1;
+ $tests['[term:parent:name]'] = check_plain($term1->name);
+ $tests['[term:parent:url]'] = url('taxonomy/term/' . $term1->tid, array('absolute' => TRUE));
+ $tests['[term:parent:parent:name]'] = '[term:parent:parent:name]';
+ $tests['[term:vocabulary:name]'] = check_plain($this->vocabulary->name);
+
+ // Test to make sure that we generated something for each token.
+ $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('term' => $term2), array('language' => $language));
+ $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
+ }
+
+ // Generate and test unsanitized tokens.
+ $tests['[term:name]'] = $term2->name;
+ $tests['[term:description]'] = $term2->description;
+ $tests['[term:parent:name]'] = $term1->name;
+ $tests['[term:vocabulary:name]'] = $this->vocabulary->name;
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('term' => $term2), array('language' => $language, 'sanitize' => FALSE));
+ $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy term token %token replaced.', array('%token' => $input)));
+ }
+
+ // Generate and test sanitized tokens.
+ $tests = array();
+ $tests['[vocabulary:vid]'] = $this->vocabulary->vid;
+ $tests['[vocabulary:name]'] = check_plain($this->vocabulary->name);
+ $tests['[vocabulary:description]'] = filter_xss($this->vocabulary->description);
+ $tests['[vocabulary:node-count]'] = 1;
+ $tests['[vocabulary:term-count]'] = 2;
+
+ // Test to make sure that we generated something for each token.
+ $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('vocabulary' => $this->vocabulary), array('language' => $language));
+ $this->assertEqual($output, $expected, format_string('Sanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
+ }
+
+ // Generate and test unsanitized tokens.
+ $tests['[vocabulary:name]'] = $this->vocabulary->name;
+ $tests['[vocabulary:description]'] = $this->vocabulary->description;
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('vocabulary' => $this->vocabulary), array('language' => $language, 'sanitize' => FALSE));
+ $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
+ }
+ }
+
+}
+
+/**
+ * Tests for verifying that taxonomy pages use the correct theme.
+ */
+class TaxonomyThemeTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy theme switching',
+ 'description' => 'Verifies that various taxonomy pages use the expected theme.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Make sure we are using distinct default and administrative themes for
+ // the duration of these tests.
+ variable_set('theme_default', 'bartik');
+ variable_set('admin_theme', 'seven');
+
+ // Create and log in as a user who has permission to add and edit taxonomy
+ // terms and view the administrative theme.
+ $admin_user = $this->drupalCreateUser(array('administer taxonomy', 'view the administration theme'));
+ $this->drupalLogin($admin_user);
+ }
+
+ /**
+ * Test the theme used when adding, viewing and editing taxonomy terms.
+ */
+ function testTaxonomyTermThemes() {
+ // Adding a term to a vocabulary is considered an administrative action and
+ // should use the administrative theme.
+ $vocabulary = $this->createVocabulary();
+ $this->drupalGet('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add');
+ $this->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page for adding a taxonomy term.");
+
+ // Viewing a taxonomy term should use the default theme.
+ $term = $this->createTerm($vocabulary);
+ $this->drupalGet('taxonomy/term/' . $term->tid);
+ $this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page for viewing a taxonomy term.");
+
+ // Editing a taxonomy term should use the same theme as adding one.
+ $this->drupalGet('taxonomy/term/' . $term->tid . '/edit');
+ $this->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page for editing a taxonomy term.");
+ }
+
+}
+
+/**
+ * Tests the functionality of EntityFieldQuery for taxonomy entities.
+ */
+class TaxonomyEFQTestCase extends TaxonomyWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy EntityFieldQuery',
+ 'description' => 'Verifies operation of a taxonomy-based EntityFieldQuery.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy'));
+ $this->drupalLogin($this->admin_user);
+ $this->vocabulary = $this->createVocabulary();
+ }
+
+ /**
+ * Tests that a basic taxonomy EntityFieldQuery works.
+ */
+ function testTaxonomyEFQ() {
+ $terms = array();
+ for ($i = 0; $i < 5; $i++) {
+ $term = $this->createTerm($this->vocabulary);
+ $terms[$term->tid] = $term;
+ }
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'taxonomy_term');
+ $result = $query->execute();
+ $result = $result['taxonomy_term'];
+ asort($result);
+ $this->assertEqual(array_keys($terms), array_keys($result), 'Taxonomy terms were retrieved by EntityFieldQuery.');
+
+ // Create a second vocabulary and five more terms.
+ $vocabulary2 = $this->createVocabulary();
+ $terms2 = array();
+ for ($i = 0; $i < 5; $i++) {
+ $term = $this->createTerm($vocabulary2);
+ $terms2[$term->tid] = $term;
+ }
+
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'taxonomy_term');
+ $query->entityCondition('bundle', $vocabulary2->machine_name);
+ $result = $query->execute();
+ $result = $result['taxonomy_term'];
+ asort($result);
+ $this->assertEqual(array_keys($terms2), array_keys($result), format_string('Taxonomy terms from the %name vocabulary were retrieved by EntityFieldQuery.', array('%name' => $vocabulary2->name)));
+ }
+
+}
diff --git a/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.tokens.inc b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.tokens.inc
new file mode 100644
index 0000000..f8ae457
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/taxonomy/taxonomy.tokens.inc
@@ -0,0 +1,189 @@
+<?php
+
+/**
+ * @file
+ * Builds placeholder replacement tokens for taxonomy terms and vocabularies.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function taxonomy_token_info() {
+ $types['term'] = array(
+ 'name' => t("Taxonomy terms"),
+ 'description' => t("Tokens related to taxonomy terms."),
+ 'needs-data' => 'term',
+ );
+ $types['vocabulary'] = array(
+ 'name' => t("Vocabularies"),
+ 'description' => t("Tokens related to taxonomy vocabularies."),
+ 'needs-data' => 'vocabulary',
+ );
+
+ // Taxonomy term related variables.
+ $term['tid'] = array(
+ 'name' => t("Term ID"),
+ 'description' => t("The unique ID of the taxonomy term."),
+ );
+ $term['name'] = array(
+ 'name' => t("Name"),
+ 'description' => t("The name of the taxonomy term."),
+ );
+ $term['description'] = array(
+ 'name' => t("Description"),
+ 'description' => t("The optional description of the taxonomy term."),
+ );
+ $term['node-count'] = array(
+ 'name' => t("Node count"),
+ 'description' => t("The number of nodes tagged with the taxonomy term."),
+ );
+ $term['url'] = array(
+ 'name' => t("URL"),
+ 'description' => t("The URL of the taxonomy term."),
+ );
+
+ // Taxonomy vocabulary related variables.
+ $vocabulary['vid'] = array(
+ 'name' => t("Vocabulary ID"),
+ 'description' => t("The unique ID of the taxonomy vocabulary."),
+ );
+ $vocabulary['name'] = array(
+ 'name' => t("Name"),
+ 'description' => t("The name of the taxonomy vocabulary."),
+ );
+ $vocabulary['description'] = array(
+ 'name' => t("Description"),
+ 'description' => t("The optional description of the taxonomy vocabulary."),
+ );
+ $vocabulary['node-count'] = array(
+ 'name' => t("Node count"),
+ 'description' => t("The number of nodes tagged with terms belonging to the taxonomy vocabulary."),
+ );
+ $vocabulary['term-count'] = array(
+ 'name' => t("Term count"),
+ 'description' => t("The number of terms belonging to the taxonomy vocabulary."),
+ );
+
+ // Chained tokens for taxonomies
+ $term['vocabulary'] = array(
+ 'name' => t("Vocabulary"),
+ 'description' => t("The vocabulary the taxonomy term belongs to."),
+ 'type' => 'vocabulary',
+ );
+ $term['parent'] = array(
+ 'name' => t("Parent term"),
+ 'description' => t("The parent term of the taxonomy term, if one exists."),
+ 'type' => 'term',
+ );
+
+ return array(
+ 'types' => $types,
+ 'tokens' => array(
+ 'term' => $term,
+ 'vocabulary' => $vocabulary,
+ ),
+ );
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function taxonomy_tokens($type, $tokens, array $data = array(), array $options = array()) {
+ $replacements = array();
+ $sanitize = !empty($options['sanitize']);
+
+ if ($type == 'term' && !empty($data['term'])) {
+ $term = $data['term'];
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ case 'tid':
+ $replacements[$original] = $term->tid;
+ break;
+
+ case 'name':
+ $replacements[$original] = $sanitize ? check_plain($term->name) : $term->name;
+ break;
+
+ case 'description':
+ $replacements[$original] = $sanitize ? check_markup($term->description, $term->format, '', TRUE) : $term->description;
+ break;
+
+ case 'url':
+ $uri = entity_uri('taxonomy_term', $term);
+ $replacements[$original] = url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE)));
+ break;
+
+ case 'node-count':
+ $query = db_select('taxonomy_index');
+ $query->condition('tid', $term->tid);
+ $query->addTag('term_node_count');
+ $count = $query->countQuery()->execute()->fetchField();
+ $replacements[$original] = $count;
+ break;
+
+ case 'vocabulary':
+ $vocabulary = taxonomy_vocabulary_load($term->vid);
+ $replacements[$original] = check_plain($vocabulary->name);
+ break;
+
+ case 'parent':
+ if ($parents = taxonomy_get_parents($term->tid)) {
+ $parent = array_pop($parents);
+ $replacements[$original] = check_plain($parent->name);
+ }
+ break;
+ }
+ }
+
+ if ($vocabulary_tokens = token_find_with_prefix($tokens, 'vocabulary')) {
+ $vocabulary = taxonomy_vocabulary_load($term->vid);
+ $replacements += token_generate('vocabulary', $vocabulary_tokens, array('vocabulary' => $vocabulary), $options);
+ }
+
+ if (($vocabulary_tokens = token_find_with_prefix($tokens, 'parent')) && $parents = taxonomy_get_parents($term->tid)) {
+ $parent = array_pop($parents);
+ $replacements += token_generate('term', $vocabulary_tokens, array('term' => $parent), $options);
+ }
+ }
+
+ elseif ($type == 'vocabulary' && !empty($data['vocabulary'])) {
+ $vocabulary = $data['vocabulary'];
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ case 'vid':
+ $replacements[$original] = $vocabulary->vid;
+ break;
+
+ case 'name':
+ $replacements[$original] = $sanitize ? check_plain($vocabulary->name) : $vocabulary->name;
+ break;
+
+ case 'description':
+ $replacements[$original] = $sanitize ? filter_xss($vocabulary->description) : $vocabulary->description;
+ break;
+
+ case 'term-count':
+ $query = db_select('taxonomy_term_data');
+ $query->condition('vid', $vocabulary->vid);
+ $query->addTag('vocabulary_term_count');
+ $count = $query->countQuery()->execute()->fetchField();
+ $replacements[$original] = $count;
+ break;
+
+ case 'node-count':
+ $query = db_select('taxonomy_index', 'ti');
+ $query->addExpression('COUNT(DISTINCT ti.nid)');
+ $query->leftJoin('taxonomy_term_data', 'td', 'ti.tid = td.tid');
+ $query->condition('td.vid', $vocabulary->vid);
+ $query->addTag('vocabulary_node_count');
+ $count = $query->execute()->fetchField();
+ $replacements[$original] = $count;
+ break;
+ }
+ }
+ }
+
+ return $replacements;
+}