cms/drupal/modules/taxonomy/taxonomy.module
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Enables the organization of content into categories.
       
     6  */
       
     7 
       
     8 /**
       
     9  * Users can create new terms in a free-tagging vocabulary when
       
    10  * submitting a taxonomy_autocomplete_widget. We store a term object
       
    11  * whose tid is 'autocreate' as a field data item during widget
       
    12  * validation and then actually create the term if/when that field
       
    13  * data item makes it to taxonomy_field_insert/update().
       
    14  */
       
    15 
       
    16 /**
       
    17  * Implements hook_help().
       
    18  */
       
    19 function taxonomy_help($path, $arg) {
       
    20   switch ($path) {
       
    21     case 'admin/help#taxonomy':
       
    22       $output = '';
       
    23       $output .= '<h3>' . t('About') . '</h3>';
       
    24       $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>';
       
    25       $output .= '<h3>' . t('Uses') . '</h3>';
       
    26       $output .= '<dl>';
       
    27       $output .= '<dt>' . t('Creating vocabularies') . '</dt>';
       
    28       $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'))));
       
    29       $output .= '<ul><li>' . t('<em>vocabulary</em>: Music') . '</li>';
       
    30       $output .= '<ul><li>' . t('<em>term</em>: Jazz') . '</li>';
       
    31       $output .= '<ul><li>' . t('<em>sub-term</em>: Swing') . '</li>';
       
    32       $output .= '<li>' . t('<em>sub-term</em>: Fusion') . '</li></ul></ul>';
       
    33       $output .= '<ul><li>' . t('<em>term</em>: Rock') . '</li>';
       
    34       $output .= '<ul><li>' . t('<em>sub-term</em>: Country rock') . '</li>';
       
    35       $output .= '<li>' . t('<em>sub-term</em>: Hard rock') . '</li></ul></ul></ul>';
       
    36       $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>';
       
    37       $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>';
       
    38       $output .= '<dt>' . t('Assigning vocabularies to content types') . '</dt>';
       
    39       $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>';
       
    40       $output .= '<dt>' . t('Classifying content') . '</dt>';
       
    41       $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>';
       
    42       $output .= '<dt>' . t('Viewing listings and RSS feeds by term') . '</dt>';
       
    43       $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>';
       
    44       $output .= '<dt>' . t('Extending Taxonomy module') . '</dt>';
       
    45       $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'));
       
    46       $output .= '</dl>';
       
    47       return $output;
       
    48     case 'admin/structure/taxonomy':
       
    49       $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>';
       
    50       return $output;
       
    51     case 'admin/structure/taxonomy/%':
       
    52       $vocabulary = taxonomy_vocabulary_machine_name_load($arg[3]);
       
    53       switch ($vocabulary->hierarchy) {
       
    54         case 0:
       
    55           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>';
       
    56         case 1:
       
    57           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>';
       
    58         case 2:
       
    59           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>';
       
    60       }
       
    61   }
       
    62 }
       
    63 
       
    64 /**
       
    65  * Implements hook_permission().
       
    66  */
       
    67 function taxonomy_permission() {
       
    68   $permissions = array(
       
    69     'administer taxonomy' => array(
       
    70       'title' => t('Administer vocabularies and terms'),
       
    71     ),
       
    72   );
       
    73   foreach (taxonomy_get_vocabularies() as $vocabulary) {
       
    74     $permissions += array(
       
    75       'edit terms in ' . $vocabulary->vid => array(
       
    76         'title' => t('Edit terms in %vocabulary', array('%vocabulary' => $vocabulary->name)),
       
    77       ),
       
    78     );
       
    79     $permissions += array(
       
    80        'delete terms in ' . $vocabulary->vid => array(
       
    81          'title' => t('Delete terms from %vocabulary', array('%vocabulary' => $vocabulary->name)),
       
    82       ),
       
    83     );
       
    84   }
       
    85   return $permissions;
       
    86 }
       
    87 
       
    88 /**
       
    89  * Implements hook_entity_info().
       
    90  */
       
    91 function taxonomy_entity_info() {
       
    92   $return = array(
       
    93     'taxonomy_term' => array(
       
    94       'label' => t('Taxonomy term'),
       
    95       'controller class' => 'TaxonomyTermController',
       
    96       'base table' => 'taxonomy_term_data',
       
    97       'uri callback' => 'taxonomy_term_uri',
       
    98       'fieldable' => TRUE,
       
    99       'entity keys' => array(
       
   100         'id' => 'tid',
       
   101         'bundle' => 'vocabulary_machine_name',
       
   102         'label' => 'name',
       
   103       ),
       
   104       'bundle keys' => array(
       
   105         'bundle' => 'machine_name',
       
   106       ),
       
   107       'bundles' => array(),
       
   108       'view modes' => array(
       
   109         // @todo View mode for display as a field (when attached to nodes etc).
       
   110         'full' => array(
       
   111           'label' => t('Taxonomy term page'),
       
   112           'custom settings' => FALSE,
       
   113         ),
       
   114       ),
       
   115     ),
       
   116   );
       
   117   foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
       
   118     $return['taxonomy_term']['bundles'][$machine_name] = array(
       
   119       'label' => $vocabulary->name,
       
   120       'admin' => array(
       
   121         'path' => 'admin/structure/taxonomy/%taxonomy_vocabulary_machine_name',
       
   122         'real path' => 'admin/structure/taxonomy/' . $machine_name,
       
   123         'bundle argument' => 3,
       
   124         'access arguments' => array('administer taxonomy'),
       
   125       ),
       
   126     );
       
   127   }
       
   128   $return['taxonomy_vocabulary'] = array(
       
   129     'label' => t('Taxonomy vocabulary'),
       
   130     'controller class' => 'TaxonomyVocabularyController',
       
   131     'base table' => 'taxonomy_vocabulary',
       
   132     'entity keys' => array(
       
   133       'id' => 'vid',
       
   134       'label' => 'name',
       
   135     ),
       
   136     'fieldable' => FALSE,
       
   137   );
       
   138 
       
   139   return $return;
       
   140 }
       
   141 
       
   142 /**
       
   143  * Implements callback_entity_info_uri().
       
   144  */
       
   145 function taxonomy_term_uri($term) {
       
   146   return array(
       
   147     'path' => 'taxonomy/term/' . $term->tid,
       
   148   );
       
   149 }
       
   150 
       
   151 /**
       
   152  * Implements hook_field_extra_fields().
       
   153  */
       
   154 function taxonomy_field_extra_fields() {
       
   155   $return = array();
       
   156   $info = entity_get_info('taxonomy_term');
       
   157   foreach (array_keys($info['bundles']) as $bundle) {
       
   158     $return['taxonomy_term'][$bundle] = array(
       
   159       'form' => array(
       
   160         'name' => array(
       
   161           'label' => t('Name'),
       
   162           'description' => t('Term name textfield'),
       
   163           'weight' => -5,
       
   164         ),
       
   165         'description' => array(
       
   166           'label' => t('Description'),
       
   167           'description' => t('Term description textarea'),
       
   168           'weight' => 0,
       
   169         ),
       
   170       ),
       
   171       'display' => array(
       
   172         'description' => array(
       
   173           'label' => t('Description'),
       
   174           'description' => t('Term description'),
       
   175           'weight' => 0,
       
   176         ),
       
   177       ),
       
   178     );
       
   179   }
       
   180 
       
   181   return $return;
       
   182 }
       
   183 
       
   184 /**
       
   185  * Return nodes attached to a term across all field instances.
       
   186  *
       
   187  * This function requires taxonomy module to be maintaining its own tables,
       
   188  * and will return an empty array if it is not. If using other field storage
       
   189  * methods alternatives methods for listing terms will need to be used.
       
   190  *
       
   191  * @param $tid
       
   192  *   The term ID.
       
   193  * @param $pager
       
   194  *   Boolean to indicate whether a pager should be used.
       
   195  * @param $limit
       
   196  *   Integer. The maximum number of nodes to find.
       
   197  *   Set to FALSE for no limit.
       
   198  * @param $order
       
   199  *   An array of fields and directions.
       
   200  *
       
   201  * @return
       
   202  *   An array of nids matching the query.
       
   203  */
       
   204 function taxonomy_select_nodes($tid, $pager = TRUE, $limit = FALSE, $order = array('t.sticky' => 'DESC', 't.created' => 'DESC')) {
       
   205   if (!variable_get('taxonomy_maintain_index_table', TRUE)) {
       
   206     return array();
       
   207   }
       
   208   $query = db_select('taxonomy_index', 't');
       
   209   $query->addTag('node_access');
       
   210   $query->condition('tid', $tid);
       
   211   if ($pager) {
       
   212     $count_query = clone $query;
       
   213     $count_query->addExpression('COUNT(t.nid)');
       
   214 
       
   215     $query = $query->extend('PagerDefault');
       
   216     if ($limit !== FALSE) {
       
   217       $query = $query->limit($limit);
       
   218     }
       
   219     $query->setCountQuery($count_query);
       
   220   }
       
   221   else {
       
   222     if ($limit !== FALSE) {
       
   223       $query->range(0, $limit);
       
   224     }
       
   225   }
       
   226   $query->addField('t', 'nid');
       
   227   $query->addField('t', 'tid');
       
   228   foreach ($order as $field => $direction) {
       
   229     $query->orderBy($field, $direction);
       
   230     // ORDER BY fields need to be loaded too, assume they are in the form
       
   231     // table_alias.name
       
   232     list($table_alias, $name) = explode('.', $field);
       
   233     $query->addField($table_alias, $name);
       
   234   }
       
   235   return $query->execute()->fetchCol();
       
   236 }
       
   237 
       
   238 /**
       
   239  * Implements hook_theme().
       
   240  */
       
   241 function taxonomy_theme() {
       
   242   return array(
       
   243     'taxonomy_overview_vocabularies' => array(
       
   244       'render element' => 'form',
       
   245     ),
       
   246     'taxonomy_overview_terms' => array(
       
   247       'render element' => 'form',
       
   248     ),
       
   249     'taxonomy_term' => array(
       
   250       'render element' => 'elements',
       
   251       'template' => 'taxonomy-term',
       
   252     ),
       
   253   );
       
   254 }
       
   255 
       
   256 /**
       
   257  * Implements hook_menu().
       
   258  */
       
   259 function taxonomy_menu() {
       
   260   $items['admin/structure/taxonomy'] = array(
       
   261     'title' => 'Taxonomy',
       
   262     'description' => 'Manage tagging, categorization, and classification of your content.',
       
   263     'page callback' => 'drupal_get_form',
       
   264     'page arguments' => array('taxonomy_overview_vocabularies'),
       
   265     'access arguments' => array('administer taxonomy'),
       
   266     'file' => 'taxonomy.admin.inc',
       
   267   );
       
   268   $items['admin/structure/taxonomy/list'] = array(
       
   269     'title' => 'List',
       
   270     'type' => MENU_DEFAULT_LOCAL_TASK,
       
   271     'weight' => -10,
       
   272   );
       
   273   $items['admin/structure/taxonomy/add'] = array(
       
   274     'title' => 'Add vocabulary',
       
   275     'page callback' => 'drupal_get_form',
       
   276     'page arguments' => array('taxonomy_form_vocabulary'),
       
   277     'access arguments' => array('administer taxonomy'),
       
   278     'type' => MENU_LOCAL_ACTION,
       
   279     'file' => 'taxonomy.admin.inc',
       
   280   );
       
   281 
       
   282   $items['taxonomy/term/%taxonomy_term'] = array(
       
   283     'title' => 'Taxonomy term',
       
   284     'title callback' => 'taxonomy_term_title',
       
   285     'title arguments' => array(2),
       
   286     'page callback' => 'taxonomy_term_page',
       
   287     'page arguments' => array(2),
       
   288     'access arguments' => array('access content'),
       
   289     'file' => 'taxonomy.pages.inc',
       
   290   );
       
   291   $items['taxonomy/term/%taxonomy_term/view'] = array(
       
   292     'title' => 'View',
       
   293     'type' => MENU_DEFAULT_LOCAL_TASK,
       
   294   );
       
   295   $items['taxonomy/term/%taxonomy_term/edit'] = array(
       
   296     'title' => 'Edit',
       
   297     'page callback' => 'drupal_get_form',
       
   298     // Pass a NULL argument to ensure that additional path components are not
       
   299     // passed to taxonomy_form_term() as the vocabulary machine name argument.
       
   300     'page arguments' => array('taxonomy_form_term', 2, NULL),
       
   301     'access callback' => 'taxonomy_term_edit_access',
       
   302     'access arguments' => array(2),
       
   303     'type' => MENU_LOCAL_TASK,
       
   304     'weight' => 10,
       
   305     'file' => 'taxonomy.admin.inc',
       
   306   );
       
   307   $items['taxonomy/term/%taxonomy_term/feed'] = array(
       
   308     'title' => 'Taxonomy term',
       
   309     'title callback' => 'taxonomy_term_title',
       
   310     'title arguments' => array(2),
       
   311     'page callback' => 'taxonomy_term_feed',
       
   312     'page arguments' => array(2),
       
   313     'access arguments' => array('access content'),
       
   314     'type' => MENU_CALLBACK,
       
   315     'file' => 'taxonomy.pages.inc',
       
   316   );
       
   317   $items['taxonomy/autocomplete'] = array(
       
   318     'title' => 'Autocomplete taxonomy',
       
   319     'page callback' => 'taxonomy_autocomplete',
       
   320     'access arguments' => array('access content'),
       
   321     'type' => MENU_CALLBACK,
       
   322     'file' => 'taxonomy.pages.inc',
       
   323   );
       
   324 
       
   325   $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name'] = array(
       
   326     'title callback' => 'entity_label',
       
   327     'title arguments' => array('taxonomy_vocabulary', 3),
       
   328     'page callback' => 'drupal_get_form',
       
   329     'page arguments' => array('taxonomy_overview_terms', 3),
       
   330     'access arguments' => array('administer taxonomy'),
       
   331     'file' => 'taxonomy.admin.inc',
       
   332   );
       
   333   $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/list'] = array(
       
   334     'title' => 'List',
       
   335     'type' => MENU_DEFAULT_LOCAL_TASK,
       
   336     'weight' => -20,
       
   337   );
       
   338   $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/edit'] = array(
       
   339     'title' => 'Edit',
       
   340     'page callback' => 'drupal_get_form',
       
   341     'page arguments' => array('taxonomy_form_vocabulary', 3),
       
   342     'access arguments' => array('administer taxonomy'),
       
   343     'type' => MENU_LOCAL_TASK,
       
   344     'weight' => -10,
       
   345     'file' => 'taxonomy.admin.inc',
       
   346   );
       
   347 
       
   348   $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add'] = array(
       
   349     'title' => 'Add term',
       
   350     'page callback' => 'drupal_get_form',
       
   351     'page arguments' => array('taxonomy_form_term', array(), 3),
       
   352     'access arguments' => array('administer taxonomy'),
       
   353     'type' => MENU_LOCAL_ACTION,
       
   354     'file' => 'taxonomy.admin.inc',
       
   355   );
       
   356 
       
   357   return $items;
       
   358 }
       
   359 
       
   360 /**
       
   361  * Implements hook_admin_paths().
       
   362  */
       
   363 function taxonomy_admin_paths() {
       
   364   $paths = array(
       
   365     'taxonomy/term/*/edit' => TRUE,
       
   366   );
       
   367   return $paths;
       
   368 }
       
   369 
       
   370 /**
       
   371  * Return edit access for a given term.
       
   372  */
       
   373 function taxonomy_term_edit_access($term) {
       
   374   return user_access("edit terms in $term->vid") || user_access('administer taxonomy');
       
   375 }
       
   376 
       
   377 /**
       
   378  * Returns the sanitized name of a vocabulary.
       
   379  *
       
   380  * Deprecated. This function was previously used as a menu item title callback
       
   381  * but has been replaced by using entity_label() (which does not
       
   382  * sanitize the title, since the menu system does that automatically). In
       
   383  * Drupal 7, use that function for title callbacks, and call check_plain()
       
   384  * directly if you need a sanitized title.
       
   385  */
       
   386 function taxonomy_admin_vocabulary_title_callback($vocabulary) {
       
   387   return check_plain($vocabulary->name);
       
   388 }
       
   389 
       
   390 /**
       
   391  * Saves a vocabulary.
       
   392  *
       
   393  * @param $vocabulary
       
   394  *   A vocabulary object with the following properties:
       
   395  *   - vid: (optional) The ID of the vocabulary (omit if creating a new
       
   396  *     vocabulary; only use to update an existing vocabulary).
       
   397  *   - name: The human-readable name of the vocabulary.
       
   398  *   - machine_name: The machine name of the vocabulary.
       
   399  *   - description: (optional) The vocabulary's description.
       
   400  *   - hierarchy: The hierarchy level of the vocabulary.
       
   401  *   - module: (optional) The module altering the vocabulary.
       
   402  *   - weight: (optional) The weight of this vocabulary in relation to other
       
   403  *     vocabularies.
       
   404  *   - original: (optional) The original vocabulary object before any changes
       
   405  *     are applied.
       
   406  *   - old_machine_name: (optional) The original machine name of the
       
   407  *     vocabulary.
       
   408  *
       
   409  * @return
       
   410  *   Status constant indicating whether the vocabulary was inserted (SAVED_NEW)
       
   411  *   or updated (SAVED_UPDATED).
       
   412  */
       
   413 function taxonomy_vocabulary_save($vocabulary) {
       
   414   // Prevent leading and trailing spaces in vocabulary names.
       
   415   if (!empty($vocabulary->name)) {
       
   416     $vocabulary->name = trim($vocabulary->name);
       
   417   }
       
   418   // Load the stored entity, if any.
       
   419   if (!empty($vocabulary->vid)) {
       
   420     if (!isset($vocabulary->original)) {
       
   421       $vocabulary->original = entity_load_unchanged('taxonomy_vocabulary', $vocabulary->vid);
       
   422     }
       
   423     // Make sure machine name changes are easily detected.
       
   424     // @todo: Remove in Drupal 8, as it is deprecated by directly reading from
       
   425     // $vocabulary->original.
       
   426     $vocabulary->old_machine_name = $vocabulary->original->machine_name;
       
   427   }
       
   428 
       
   429   if (!isset($vocabulary->module)) {
       
   430     $vocabulary->module = 'taxonomy';
       
   431   }
       
   432 
       
   433   module_invoke_all('taxonomy_vocabulary_presave', $vocabulary);
       
   434   module_invoke_all('entity_presave', $vocabulary, 'taxonomy_vocabulary');
       
   435 
       
   436   if (!empty($vocabulary->vid) && !empty($vocabulary->name)) {
       
   437     $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid');
       
   438     taxonomy_vocabulary_static_reset(array($vocabulary->vid));
       
   439     if ($vocabulary->old_machine_name != $vocabulary->machine_name) {
       
   440       field_attach_rename_bundle('taxonomy_term', $vocabulary->old_machine_name, $vocabulary->machine_name);
       
   441     }
       
   442     module_invoke_all('taxonomy_vocabulary_update', $vocabulary);
       
   443     module_invoke_all('entity_update', $vocabulary, 'taxonomy_vocabulary');
       
   444   }
       
   445   elseif (empty($vocabulary->vid)) {
       
   446     $status = drupal_write_record('taxonomy_vocabulary', $vocabulary);
       
   447     taxonomy_vocabulary_static_reset();
       
   448     field_attach_create_bundle('taxonomy_term', $vocabulary->machine_name);
       
   449     module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
       
   450     module_invoke_all('entity_insert', $vocabulary, 'taxonomy_vocabulary');
       
   451   }
       
   452 
       
   453   unset($vocabulary->original);
       
   454   cache_clear_all();
       
   455 
       
   456   return $status;
       
   457 }
       
   458 
       
   459 /**
       
   460  * Deletes a vocabulary.
       
   461  *
       
   462  * This will update all Taxonomy fields so that they don't reference the
       
   463  * deleted vocabulary. It also will delete fields that have no remaining
       
   464  * vocabulary references. All taxonomy terms of the deleted vocabulary
       
   465  * will be deleted as well.
       
   466  *
       
   467  * @param $vid
       
   468  *   A vocabulary ID.
       
   469  * @return
       
   470  *   Constant indicating items were deleted.
       
   471  */
       
   472 function taxonomy_vocabulary_delete($vid) {
       
   473   $vocabulary = taxonomy_vocabulary_load($vid);
       
   474 
       
   475   $transaction = db_transaction();
       
   476   try {
       
   477     // Only load terms without a parent, child terms will get deleted too.
       
   478     $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();
       
   479     foreach ($result as $tid) {
       
   480       taxonomy_term_delete($tid);
       
   481     }
       
   482     db_delete('taxonomy_vocabulary')
       
   483       ->condition('vid', $vid)
       
   484       ->execute();
       
   485 
       
   486     field_attach_delete_bundle('taxonomy_term', $vocabulary->machine_name);
       
   487     module_invoke_all('taxonomy_vocabulary_delete', $vocabulary);
       
   488     module_invoke_all('entity_delete', $vocabulary, 'taxonomy_vocabulary');
       
   489 
       
   490     // Load all Taxonomy module fields and delete those which use only this
       
   491     // vocabulary.
       
   492     $taxonomy_fields = field_read_fields(array('module' => 'taxonomy'));
       
   493     foreach ($taxonomy_fields as $field_name => $taxonomy_field) {
       
   494       $modified_field = FALSE;
       
   495       // Term reference fields may reference terms from more than one
       
   496       // vocabulary.
       
   497       foreach ($taxonomy_field['settings']['allowed_values'] as $key => $allowed_value) {
       
   498         if ($allowed_value['vocabulary'] == $vocabulary->machine_name) {
       
   499           unset($taxonomy_field['settings']['allowed_values'][$key]);
       
   500           $modified_field = TRUE;
       
   501         }
       
   502       }
       
   503       if ($modified_field) {
       
   504         if (empty($taxonomy_field['settings']['allowed_values'])) {
       
   505           field_delete_field($field_name);
       
   506         }
       
   507         else {
       
   508           // Update the field definition with the new allowed values.
       
   509           field_update_field($taxonomy_field);
       
   510         }
       
   511       }
       
   512     }
       
   513 
       
   514     cache_clear_all();
       
   515     taxonomy_vocabulary_static_reset();
       
   516 
       
   517     return SAVED_DELETED;
       
   518   }
       
   519   catch (Exception $e) {
       
   520     $transaction->rollback();
       
   521     watchdog_exception('taxonomy', $e);
       
   522     throw $e;
       
   523   }
       
   524 }
       
   525 
       
   526 /**
       
   527  * Implements hook_taxonomy_vocabulary_update().
       
   528  */
       
   529 function taxonomy_taxonomy_vocabulary_update($vocabulary) {
       
   530   // Reflect machine name changes in the definitions of existing 'taxonomy'
       
   531   // fields.
       
   532   if (!empty($vocabulary->old_machine_name) && $vocabulary->old_machine_name != $vocabulary->machine_name) {
       
   533     $fields = field_read_fields();
       
   534     foreach ($fields as $field_name => $field) {
       
   535       $update = FALSE;
       
   536       if ($field['type'] == 'taxonomy_term_reference') {
       
   537         foreach ($field['settings']['allowed_values'] as $key => &$value) {
       
   538           if ($value['vocabulary'] == $vocabulary->old_machine_name) {
       
   539             $value['vocabulary'] = $vocabulary->machine_name;
       
   540             $update = TRUE;
       
   541           }
       
   542         }
       
   543         if ($update) {
       
   544           field_update_field($field);
       
   545         }
       
   546       }
       
   547     }
       
   548   }
       
   549 }
       
   550 
       
   551 /**
       
   552  * Checks and updates the hierarchy flag of a vocabulary.
       
   553  *
       
   554  * Checks the current parents of all terms in a vocabulary and updates the
       
   555  * vocabulary's hierarchy setting to the lowest possible level. If no term
       
   556  * has parent terms then the vocabulary will be given a hierarchy of 0.
       
   557  * If any term has a single parent then the vocabulary will be given a
       
   558  * hierarchy of 1. If any term has multiple parents then the vocabulary
       
   559  * will be given a hierarchy of 2.
       
   560  *
       
   561  * @param $vocabulary
       
   562  *   A vocabulary object.
       
   563  * @param $changed_term
       
   564  *   An array of the term structure that was updated.
       
   565  *
       
   566  * @return
       
   567  *   An integer that represents the level of the vocabulary's hierarchy.
       
   568  */
       
   569 function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
       
   570   $tree = taxonomy_get_tree($vocabulary->vid);
       
   571   $hierarchy = 0;
       
   572   foreach ($tree as $term) {
       
   573     // Update the changed term with the new parent value before comparison.
       
   574     if ($term->tid == $changed_term['tid']) {
       
   575       $term = (object) $changed_term;
       
   576       $term->parents = $term->parent;
       
   577     }
       
   578     // Check this term's parent count.
       
   579     if (count($term->parents) > 1) {
       
   580       $hierarchy = 2;
       
   581       break;
       
   582     }
       
   583     elseif (count($term->parents) == 1 && !isset($term->parents[0])) {
       
   584       $hierarchy = 1;
       
   585     }
       
   586   }
       
   587   if ($hierarchy != $vocabulary->hierarchy) {
       
   588     $vocabulary->hierarchy = $hierarchy;
       
   589     taxonomy_vocabulary_save($vocabulary);
       
   590   }
       
   591 
       
   592   return $hierarchy;
       
   593 }
       
   594 
       
   595 /**
       
   596  * Saves a term object to the database.
       
   597  *
       
   598  * @param $term
       
   599  *   The taxonomy term object with the following properties:
       
   600  *   - vid: The ID of the vocabulary the term is assigned to.
       
   601  *   - name: The name of the term.
       
   602  *   - tid: (optional) The unique ID for the term being saved. If $term->tid is
       
   603  *     empty or omitted, a new term will be inserted.
       
   604  *   - description: (optional) The term's description.
       
   605  *   - format: (optional) The text format for the term's description.
       
   606  *   - weight: (optional) The weight of this term in relation to other terms
       
   607  *     within the same vocabulary.
       
   608  *   - parent: (optional) The parent term(s) for this term. This can be a single
       
   609  *     term ID or an array of term IDs. A value of 0 means this term does not
       
   610  *     have any parents. When omitting this variable during an update, the
       
   611  *     existing hierarchy for the term remains unchanged.
       
   612  *   - vocabulary_machine_name: (optional) The machine name of the vocabulary
       
   613  *     the term is assigned to. If not given, this value will be set
       
   614  *     automatically by loading the vocabulary based on $term->vid.
       
   615  *   - original: (optional) The original taxonomy term object before any changes
       
   616  *     were applied. When omitted, the unchanged taxonomy term object is
       
   617  *     loaded from the database and stored in this property.
       
   618  *   Since a taxonomy term is an entity, any fields contained in the term object
       
   619  *   are saved alongside the term object.
       
   620  *
       
   621  * @return
       
   622  *   Status constant indicating whether term was inserted (SAVED_NEW) or updated
       
   623  *   (SAVED_UPDATED). When inserting a new term, $term->tid will contain the
       
   624  *   term ID of the newly created term.
       
   625  */
       
   626 function taxonomy_term_save($term) {
       
   627   // Prevent leading and trailing spaces in term names.
       
   628   $term->name = trim($term->name);
       
   629   if (!isset($term->vocabulary_machine_name)) {
       
   630     $vocabulary = taxonomy_vocabulary_load($term->vid);
       
   631     $term->vocabulary_machine_name = $vocabulary->machine_name;
       
   632   }
       
   633 
       
   634   // Load the stored entity, if any.
       
   635   if (!empty($term->tid) && !isset($term->original)) {
       
   636     $term->original = entity_load_unchanged('taxonomy_term', $term->tid);
       
   637   }
       
   638 
       
   639   field_attach_presave('taxonomy_term', $term);
       
   640   module_invoke_all('taxonomy_term_presave', $term);
       
   641   module_invoke_all('entity_presave', $term, 'taxonomy_term');
       
   642 
       
   643   if (empty($term->tid)) {
       
   644     $op = 'insert';
       
   645     $status = drupal_write_record('taxonomy_term_data', $term);
       
   646     field_attach_insert('taxonomy_term', $term);
       
   647     if (!isset($term->parent)) {
       
   648       $term->parent = array(0);
       
   649     }
       
   650   }
       
   651   else {
       
   652     $op = 'update';
       
   653     $status = drupal_write_record('taxonomy_term_data', $term, 'tid');
       
   654     field_attach_update('taxonomy_term', $term);
       
   655     if (isset($term->parent)) {
       
   656       db_delete('taxonomy_term_hierarchy')
       
   657         ->condition('tid', $term->tid)
       
   658         ->execute();
       
   659     }
       
   660   }
       
   661 
       
   662   if (isset($term->parent)) {
       
   663     if (!is_array($term->parent)) {
       
   664       $term->parent = array($term->parent);
       
   665     }
       
   666     $query = db_insert('taxonomy_term_hierarchy')
       
   667       ->fields(array('tid', 'parent'));
       
   668     foreach ($term->parent as $parent) {
       
   669       if (is_array($parent)) {
       
   670         foreach ($parent as $tid) {
       
   671           $query->values(array(
       
   672             'tid' => $term->tid,
       
   673             'parent' => $tid
       
   674           ));
       
   675         }
       
   676       }
       
   677       else {
       
   678         $query->values(array(
       
   679           'tid' => $term->tid,
       
   680           'parent' => $parent
       
   681         ));
       
   682       }
       
   683     }
       
   684     $query->execute();
       
   685   }
       
   686 
       
   687   // Reset the taxonomy term static variables.
       
   688   taxonomy_terms_static_reset();
       
   689 
       
   690   // Invoke the taxonomy hooks.
       
   691   module_invoke_all("taxonomy_term_$op", $term);
       
   692   module_invoke_all("entity_$op", $term, 'taxonomy_term');
       
   693   unset($term->original);
       
   694 
       
   695   return $status;
       
   696 }
       
   697 
       
   698 /**
       
   699  * Delete a term.
       
   700  *
       
   701  * @param $tid
       
   702  *   The term ID.
       
   703  * @return
       
   704  *   Status constant indicating deletion.
       
   705  */
       
   706 function taxonomy_term_delete($tid) {
       
   707   $transaction = db_transaction();
       
   708   try {
       
   709     $tids = array($tid);
       
   710     while ($tids) {
       
   711       $children_tids = $orphans = array();
       
   712       foreach ($tids as $tid) {
       
   713         // See if any of the term's children are about to be become orphans:
       
   714         if ($children = taxonomy_get_children($tid)) {
       
   715           foreach ($children as $child) {
       
   716             // If the term has multiple parents, we don't delete it.
       
   717             $parents = taxonomy_get_parents($child->tid);
       
   718             if (count($parents) == 1) {
       
   719               $orphans[] = $child->tid;
       
   720             }
       
   721           }
       
   722         }
       
   723 
       
   724         if ($term = taxonomy_term_load($tid)) {
       
   725           db_delete('taxonomy_term_data')
       
   726             ->condition('tid', $tid)
       
   727             ->execute();
       
   728           db_delete('taxonomy_term_hierarchy')
       
   729             ->condition('tid', $tid)
       
   730             ->execute();
       
   731 
       
   732           field_attach_delete('taxonomy_term', $term);
       
   733           module_invoke_all('taxonomy_term_delete', $term);
       
   734           module_invoke_all('entity_delete', $term, 'taxonomy_term');
       
   735           taxonomy_terms_static_reset();
       
   736         }
       
   737       }
       
   738 
       
   739       $tids = $orphans;
       
   740     }
       
   741     return SAVED_DELETED;
       
   742   }
       
   743   catch (Exception $e) {
       
   744     $transaction->rollback();
       
   745     watchdog_exception('taxonomy', $e);
       
   746     throw $e;
       
   747   }
       
   748 }
       
   749 
       
   750 /**
       
   751  * Generates an array which displays a term detail page.
       
   752  *
       
   753  * @param term
       
   754  *   A taxonomy term object.
       
   755  * @return
       
   756  *   A $page element suitable for use by drupal_render().
       
   757  */
       
   758 function taxonomy_term_show($term) {
       
   759   return taxonomy_term_view_multiple(array($term->tid => $term), 'full');
       
   760 }
       
   761 
       
   762 /**
       
   763  * Constructs a drupal_render() style array from an array of loaded terms.
       
   764  *
       
   765  * @param $terms
       
   766  *   An array of taxonomy terms as returned by taxonomy_term_load_multiple().
       
   767  * @param $view_mode
       
   768  *   View mode, e.g. 'full', 'teaser'...
       
   769  * @param $weight
       
   770  *   An integer representing the weight of the first taxonomy term in the list.
       
   771  * @param $langcode
       
   772  *   (optional) A language code to use for rendering. Defaults to the global
       
   773  *   content language of the current request.
       
   774  *
       
   775  * @return
       
   776  *   An array in the format expected by drupal_render().
       
   777  */
       
   778 function taxonomy_term_view_multiple($terms, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
       
   779   $build = array();
       
   780   $entities_by_view_mode = entity_view_mode_prepare('taxonomy_term', $terms, $view_mode, $langcode);
       
   781   foreach ($entities_by_view_mode as $entity_view_mode => $entities) {
       
   782     field_attach_prepare_view('taxonomy_term', $entities, $entity_view_mode, $langcode);
       
   783     entity_prepare_view('taxonomy_term', $entities, $langcode);
       
   784 
       
   785     foreach ($entities as $entity) {
       
   786       $build['taxonomy_terms'][$entity->tid] = taxonomy_term_view($entity, $entity_view_mode, $langcode);
       
   787     }
       
   788   }
       
   789 
       
   790   foreach ($terms as $term) {
       
   791     $build['taxonomy_terms'][$term->tid]['#weight'] = $weight;
       
   792     $weight++;
       
   793   }
       
   794   // Sort here, to preserve the input order of the entities that were passed to
       
   795   // this function.
       
   796   uasort($build['taxonomy_terms'], 'element_sort');
       
   797   $build['taxonomy_terms']['#sorted'] = TRUE;
       
   798 
       
   799   return $build;
       
   800 }
       
   801 
       
   802 /**
       
   803  * Builds a structured array representing the term's content.
       
   804  *
       
   805  * The content built for the taxonomy term (field values, file attachments or
       
   806  * other term components) will vary depending on the $view_mode parameter.
       
   807  *
       
   808  * Drupal core defines the following view modes for terms, with the following
       
   809  * default use cases:
       
   810  *   - full (default): term is displayed on its own page (taxonomy/term/123)
       
   811  * Contributed modules might define additional view modes, or use existing
       
   812  * view modes in additional contexts.
       
   813  *
       
   814  * @param $term
       
   815  *   A taxonomy term object.
       
   816  * @param $view_mode
       
   817  *   View mode, e.g. 'full', 'teaser'...
       
   818  * @param $langcode
       
   819  *   (optional) A language code to use for rendering. Defaults to the global
       
   820  *   content language of the current request.
       
   821  */
       
   822 function taxonomy_term_build_content($term, $view_mode = 'full', $langcode = NULL) {
       
   823   if (!isset($langcode)) {
       
   824     $langcode = $GLOBALS['language_content']->language;
       
   825   }
       
   826 
       
   827   // Remove previously built content, if exists.
       
   828   $term->content = array();
       
   829 
       
   830   // Allow modules to change the view mode.
       
   831   $view_mode = key(entity_view_mode_prepare('taxonomy_term', array($term->tid => $term), $view_mode, $langcode));
       
   832 
       
   833   // Add the term description if the term has one and it is visible.
       
   834   $type = 'taxonomy_term';
       
   835   $entity_ids = entity_extract_ids($type, $term);
       
   836   $settings = field_view_mode_settings($type, $entity_ids[2]);
       
   837   $fields = field_extra_fields_get_display($type, $entity_ids[2], $view_mode);
       
   838   if (!empty($term->description) && isset($fields['description']) && $fields['description']['visible']) {
       
   839     $term->content['description'] = array(
       
   840       '#markup' => check_markup($term->description, $term->format, '', TRUE),
       
   841       '#weight' => $fields['description']['weight'],
       
   842       '#prefix' => '<div class="taxonomy-term-description">',
       
   843       '#suffix' => '</div>',
       
   844     );
       
   845   }
       
   846 
       
   847   // Build fields content.
       
   848   // In case of a multiple view, taxonomy_term_view_multiple() already ran the
       
   849   // 'prepare_view' step. An internal flag prevents the operation from running
       
   850   // twice.
       
   851   field_attach_prepare_view('taxonomy_term', array($term->tid => $term), $view_mode, $langcode);
       
   852   entity_prepare_view('taxonomy_term', array($term->tid => $term), $langcode);
       
   853   $term->content += field_attach_view('taxonomy_term', $term, $view_mode, $langcode);
       
   854 
       
   855   // Allow modules to make their own additions to the taxonomy term.
       
   856   module_invoke_all('taxonomy_term_view', $term, $view_mode, $langcode);
       
   857   module_invoke_all('entity_view', $term, 'taxonomy_term', $view_mode, $langcode);
       
   858 
       
   859   // Make sure the current view mode is stored if no module has already
       
   860   // populated the related key.
       
   861   $term->content += array('#view_mode' => $view_mode);
       
   862 }
       
   863 
       
   864 /**
       
   865  * Generate an array for rendering the given term.
       
   866  *
       
   867  * @param $term
       
   868  *   A term object.
       
   869  * @param $view_mode
       
   870  *   View mode, e.g. 'full', 'teaser'...
       
   871  * @param $langcode
       
   872  *   (optional) A language code to use for rendering. Defaults to the global
       
   873  *   content language of the current request.
       
   874  *
       
   875  * @return
       
   876  *   An array as expected by drupal_render().
       
   877  */
       
   878 function taxonomy_term_view($term, $view_mode = 'full', $langcode = NULL) {
       
   879   if (!isset($langcode)) {
       
   880     $langcode = $GLOBALS['language_content']->language;
       
   881   }
       
   882 
       
   883   // Populate $term->content with a render() array.
       
   884   taxonomy_term_build_content($term, $view_mode, $langcode);
       
   885   $build = $term->content;
       
   886 
       
   887   // We don't need duplicate rendering info in $term->content.
       
   888   unset($term->content);
       
   889 
       
   890   $build += array(
       
   891     '#theme' => 'taxonomy_term',
       
   892     '#term' => $term,
       
   893     '#view_mode' => $view_mode,
       
   894     '#language' => $langcode,
       
   895   );
       
   896 
       
   897   $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css';
       
   898 
       
   899   // Allow modules to modify the structured taxonomy term.
       
   900   $type = 'taxonomy_term';
       
   901   drupal_alter(array('taxonomy_term_view', 'entity_view'), $build, $type);
       
   902 
       
   903   return $build;
       
   904 }
       
   905 
       
   906 /**
       
   907  * Process variables for taxonomy-term.tpl.php.
       
   908  */
       
   909 function template_preprocess_taxonomy_term(&$variables) {
       
   910   $variables['view_mode'] = $variables['elements']['#view_mode'];
       
   911   $variables['term'] = $variables['elements']['#term'];
       
   912   $term = $variables['term'];
       
   913 
       
   914   $uri = entity_uri('taxonomy_term', $term);
       
   915   $variables['term_url']  = url($uri['path'], $uri['options']);
       
   916   $variables['term_name'] = check_plain($term->name);
       
   917   $variables['page']      = $variables['view_mode'] == 'full' && taxonomy_term_is_page($term);
       
   918 
       
   919   // Flatten the term object's member fields.
       
   920   $variables = array_merge((array) $term, $variables);
       
   921 
       
   922   // Helpful $content variable for templates.
       
   923   $variables['content'] = array();
       
   924   foreach (element_children($variables['elements']) as $key) {
       
   925     $variables['content'][$key] = $variables['elements'][$key];
       
   926   }
       
   927 
       
   928   // field_attach_preprocess() overwrites the $[field_name] variables with the
       
   929   // values of the field in the language that was selected for display, instead
       
   930   // of the raw values in $term->[field_name], which contain all values in all
       
   931   // languages.
       
   932   field_attach_preprocess('taxonomy_term', $term, $variables['content'], $variables);
       
   933 
       
   934   // Gather classes, and clean up name so there are no underscores.
       
   935   $vocabulary_name_css = str_replace('_', '-', $term->vocabulary_machine_name);
       
   936   $variables['classes_array'][] = 'vocabulary-' . $vocabulary_name_css;
       
   937 
       
   938   $variables['theme_hook_suggestions'][] = 'taxonomy_term__' . $term->vocabulary_machine_name;
       
   939   $variables['theme_hook_suggestions'][] = 'taxonomy_term__' . $term->tid;
       
   940 }
       
   941 
       
   942 /**
       
   943  * Returns whether the current page is the page of the passed-in term.
       
   944  *
       
   945  * @param $term
       
   946  *   A term object.
       
   947  */
       
   948 function taxonomy_term_is_page($term) {
       
   949   $page_term = menu_get_object('taxonomy_term', 2);
       
   950   return (!empty($page_term) ? $page_term->tid == $term->tid : FALSE);
       
   951 }
       
   952 
       
   953 /**
       
   954  * Clear all static cache variables for terms.
       
   955  */
       
   956 function taxonomy_terms_static_reset() {
       
   957   drupal_static_reset('taxonomy_term_count_nodes');
       
   958   drupal_static_reset('taxonomy_get_tree');
       
   959   drupal_static_reset('taxonomy_get_tree:parents');
       
   960   drupal_static_reset('taxonomy_get_tree:terms');
       
   961   drupal_static_reset('taxonomy_get_parents');
       
   962   drupal_static_reset('taxonomy_get_parents_all');
       
   963   drupal_static_reset('taxonomy_get_children');
       
   964   entity_get_controller('taxonomy_term')->resetCache();
       
   965 }
       
   966 
       
   967 /**
       
   968  * Clear all static cache variables for vocabularies.
       
   969  *
       
   970  * @param $ids
       
   971  * An array of ids to reset in entity controller cache.
       
   972  */
       
   973 function taxonomy_vocabulary_static_reset($ids = NULL) {
       
   974   drupal_static_reset('taxonomy_vocabulary_get_names');
       
   975   entity_get_controller('taxonomy_vocabulary')->resetCache($ids);
       
   976 }
       
   977 
       
   978 /**
       
   979  * Return an array of all vocabulary objects.
       
   980  *
       
   981  * @return
       
   982  *   An array of all vocabulary objects, indexed by vid.
       
   983  */
       
   984 function taxonomy_get_vocabularies() {
       
   985   return taxonomy_vocabulary_load_multiple(FALSE, array());
       
   986 }
       
   987 
       
   988 /**
       
   989  * Get names for all taxonomy vocabularies.
       
   990  *
       
   991  * @return
       
   992  *   An associative array of objects keyed by vocabulary machine name with
       
   993  *   information about taxonomy vocabularies. Each object has properties:
       
   994  *   - name: The vocabulary name.
       
   995  *   - machine_name: The machine name.
       
   996  *   - vid: The vocabulary ID.
       
   997  */
       
   998 function taxonomy_vocabulary_get_names() {
       
   999   $names = &drupal_static(__FUNCTION__);
       
  1000 
       
  1001   if (!isset($names)) {
       
  1002     $names = db_query('SELECT name, machine_name, vid FROM {taxonomy_vocabulary}')->fetchAllAssoc('machine_name');
       
  1003   }
       
  1004 
       
  1005   return $names;
       
  1006 }
       
  1007 
       
  1008 /**
       
  1009  * Finds all parents of a given term ID.
       
  1010  *
       
  1011  * @param $tid
       
  1012  *   A taxonomy term ID.
       
  1013  *
       
  1014  * @return
       
  1015  *   An array of term objects which are the parents of the term $tid, or an
       
  1016  *   empty array if parents are not found.
       
  1017  */
       
  1018 function taxonomy_get_parents($tid) {
       
  1019   $parents = &drupal_static(__FUNCTION__, array());
       
  1020 
       
  1021   if ($tid && !isset($parents[$tid])) {
       
  1022     $query = db_select('taxonomy_term_data', 't');
       
  1023     $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid');
       
  1024     $query->addField('t', 'tid');
       
  1025     $query->condition('h.tid', $tid);
       
  1026     $query->addTag('taxonomy_term_access');
       
  1027     $query->orderBy('t.weight');
       
  1028     $query->orderBy('t.name');
       
  1029     $tids = $query->execute()->fetchCol();
       
  1030     $parents[$tid] = taxonomy_term_load_multiple($tids);
       
  1031   }
       
  1032 
       
  1033   return isset($parents[$tid]) ? $parents[$tid] : array();
       
  1034 }
       
  1035 
       
  1036 /**
       
  1037  * Find all ancestors of a given term ID.
       
  1038  */
       
  1039 function taxonomy_get_parents_all($tid) {
       
  1040   $cache = &drupal_static(__FUNCTION__, array());
       
  1041 
       
  1042   if (isset($cache[$tid])) {
       
  1043     return $cache[$tid];
       
  1044   }
       
  1045 
       
  1046   $parents = array();
       
  1047   if ($term = taxonomy_term_load($tid)) {
       
  1048     $parents[] = $term;
       
  1049     $n = 0;
       
  1050     while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
       
  1051       $parents = array_merge($parents, $parent);
       
  1052       $n++;
       
  1053     }
       
  1054   }
       
  1055 
       
  1056   $cache[$tid] = $parents;
       
  1057 
       
  1058   return $parents;
       
  1059 }
       
  1060 
       
  1061 /**
       
  1062  * Finds all children of a term ID.
       
  1063  *
       
  1064  * @param $tid
       
  1065  *   A taxonomy term ID.
       
  1066  * @param $vid
       
  1067  *   An optional vocabulary ID to restrict the child search.
       
  1068  *
       
  1069  * @return
       
  1070  *   An array of term objects that are the children of the term $tid, or an
       
  1071  *   empty array when no children exist.
       
  1072  */
       
  1073 function taxonomy_get_children($tid, $vid = 0) {
       
  1074   $children = &drupal_static(__FUNCTION__, array());
       
  1075 
       
  1076   if ($tid && !isset($children[$tid])) {
       
  1077     $query = db_select('taxonomy_term_data', 't');
       
  1078     $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
       
  1079     $query->addField('t', 'tid');
       
  1080     $query->condition('h.parent', $tid);
       
  1081     if ($vid) {
       
  1082       $query->condition('t.vid', $vid);
       
  1083     }
       
  1084     $query->addTag('taxonomy_term_access');
       
  1085     $query->orderBy('t.weight');
       
  1086     $query->orderBy('t.name');
       
  1087     $tids = $query->execute()->fetchCol();
       
  1088     $children[$tid] = taxonomy_term_load_multiple($tids);
       
  1089   }
       
  1090 
       
  1091   return isset($children[$tid]) ? $children[$tid] : array();
       
  1092 }
       
  1093 
       
  1094 /**
       
  1095  * Create a hierarchical representation of a vocabulary.
       
  1096  *
       
  1097  * @param $vid
       
  1098  *   Which vocabulary to generate the tree for.
       
  1099  * @param $parent
       
  1100  *   The term ID under which to generate the tree. If 0, generate the tree
       
  1101  *   for the entire vocabulary.
       
  1102  * @param $max_depth
       
  1103  *   The number of levels of the tree to return. Leave NULL to return all levels.
       
  1104  * @param $load_entities
       
  1105  *   If TRUE, a full entity load will occur on the term objects. Otherwise they
       
  1106  *   are partial objects queried directly from the {taxonomy_term_data} table to
       
  1107  *   save execution time and memory consumption when listing large numbers of
       
  1108  *   terms. Defaults to FALSE.
       
  1109  *
       
  1110  * @return
       
  1111  *   An array of all term objects in the tree. Each term object is extended
       
  1112  *   to have "depth" and "parents" attributes in addition to its normal ones.
       
  1113  *   Results are statically cached. Term objects will be partial or complete
       
  1114  *   depending on the $load_entities parameter.
       
  1115  */
       
  1116 function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
       
  1117   $children = &drupal_static(__FUNCTION__, array());
       
  1118   $parents = &drupal_static(__FUNCTION__ . ':parents', array());
       
  1119   $terms = &drupal_static(__FUNCTION__ . ':terms', array());
       
  1120 
       
  1121   // We cache trees, so it's not CPU-intensive to call taxonomy_get_tree() on a
       
  1122   // term and its children, too.
       
  1123   if (!isset($children[$vid])) {
       
  1124     $children[$vid] = array();
       
  1125     $parents[$vid] = array();
       
  1126     $terms[$vid] = array();
       
  1127 
       
  1128     $query = db_select('taxonomy_term_data', 't');
       
  1129     $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
       
  1130     $result = $query
       
  1131       ->addTag('translatable')
       
  1132       ->addTag('taxonomy_term_access')
       
  1133       ->fields('t')
       
  1134       ->fields('h', array('parent'))
       
  1135       ->condition('t.vid', $vid)
       
  1136       ->orderBy('t.weight')
       
  1137       ->orderBy('t.name')
       
  1138       ->execute();
       
  1139 
       
  1140     foreach ($result as $term) {
       
  1141       $children[$vid][$term->parent][] = $term->tid;
       
  1142       $parents[$vid][$term->tid][] = $term->parent;
       
  1143       $terms[$vid][$term->tid] = $term;
       
  1144     }
       
  1145   }
       
  1146 
       
  1147   // Load full entities, if necessary. The entity controller statically
       
  1148   // caches the results.
       
  1149   if ($load_entities) {
       
  1150     $term_entities = taxonomy_term_load_multiple(array_keys($terms[$vid]));
       
  1151   }
       
  1152 
       
  1153   $max_depth = (!isset($max_depth)) ? count($children[$vid]) : $max_depth;
       
  1154   $tree = array();
       
  1155 
       
  1156   // Keeps track of the parents we have to process, the last entry is used
       
  1157   // for the next processing step.
       
  1158   $process_parents = array();
       
  1159   $process_parents[] = $parent;
       
  1160 
       
  1161   // Loops over the parent terms and adds its children to the tree array.
       
  1162   // Uses a loop instead of a recursion, because it's more efficient.
       
  1163   while (count($process_parents)) {
       
  1164     $parent = array_pop($process_parents);
       
  1165     // The number of parents determines the current depth.
       
  1166     $depth = count($process_parents);
       
  1167     if ($max_depth > $depth && !empty($children[$vid][$parent])) {
       
  1168       $has_children = FALSE;
       
  1169       $child = current($children[$vid][$parent]);
       
  1170       do {
       
  1171         if (empty($child)) {
       
  1172           break;
       
  1173         }
       
  1174         $term = $load_entities ? $term_entities[$child] : $terms[$vid][$child];
       
  1175         if (isset($parents[$vid][$term->tid])) {
       
  1176           // Clone the term so that the depth attribute remains correct
       
  1177           // in the event of multiple parents.
       
  1178           $term = clone $term;
       
  1179         }
       
  1180         $term->depth = $depth;
       
  1181         unset($term->parent);
       
  1182         $term->parents = $parents[$vid][$term->tid];
       
  1183         $tree[] = $term;
       
  1184         if (!empty($children[$vid][$term->tid])) {
       
  1185           $has_children = TRUE;
       
  1186 
       
  1187           // We have to continue with this parent later.
       
  1188           $process_parents[] = $parent;
       
  1189           // Use the current term as parent for the next iteration.
       
  1190           $process_parents[] = $term->tid;
       
  1191 
       
  1192           // Reset pointers for child lists because we step in there more often
       
  1193           // with multi parents.
       
  1194           reset($children[$vid][$term->tid]);
       
  1195           // Move pointer so that we get the correct term the next time.
       
  1196           next($children[$vid][$parent]);
       
  1197           break;
       
  1198         }
       
  1199       } while ($child = next($children[$vid][$parent]));
       
  1200 
       
  1201       if (!$has_children) {
       
  1202         // We processed all terms in this hierarchy-level, reset pointer
       
  1203         // so that this function works the next time it gets called.
       
  1204         reset($children[$vid][$parent]);
       
  1205       }
       
  1206     }
       
  1207   }
       
  1208 
       
  1209   return $tree;
       
  1210 }
       
  1211 
       
  1212 /**
       
  1213  * Try to map a string to an existing term, as for glossary use.
       
  1214  *
       
  1215  * Provides a case-insensitive and trimmed mapping, to maximize the
       
  1216  * likelihood of a successful match.
       
  1217  *
       
  1218  * @param $name
       
  1219  *   Name of the term to search for.
       
  1220  * @param $vocabulary
       
  1221  *   (optional) Vocabulary machine name to limit the search. Defaults to NULL.
       
  1222  *
       
  1223  * @return
       
  1224  *   An array of matching term objects.
       
  1225  */
       
  1226 function taxonomy_get_term_by_name($name, $vocabulary = NULL) {
       
  1227   $conditions = array('name' => trim($name));
       
  1228   if (isset($vocabulary)) {
       
  1229     $vocabularies = taxonomy_vocabulary_get_names();
       
  1230     if (isset($vocabularies[$vocabulary])) {
       
  1231       $conditions['vid'] = $vocabularies[$vocabulary]->vid;
       
  1232     }
       
  1233     else {
       
  1234       // Return an empty array when filtering by a non-existing vocabulary.
       
  1235       return array();
       
  1236     }
       
  1237   }
       
  1238   return taxonomy_term_load_multiple(array(), $conditions);
       
  1239 }
       
  1240 
       
  1241 /**
       
  1242  * Controller class for taxonomy terms.
       
  1243  *
       
  1244  * This extends the DrupalDefaultEntityController class. Only alteration is
       
  1245  * that we match the condition on term name case-independently.
       
  1246  */
       
  1247 class TaxonomyTermController extends DrupalDefaultEntityController {
       
  1248 
       
  1249   protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
       
  1250     $query = parent::buildQuery($ids, $conditions, $revision_id);
       
  1251     $query->addTag('translatable');
       
  1252     $query->addTag('taxonomy_term_access');
       
  1253     // When name is passed as a condition use LIKE.
       
  1254     if (isset($conditions['name'])) {
       
  1255       $query_conditions = &$query->conditions();
       
  1256       foreach ($query_conditions as $key => $condition) {
       
  1257         if (is_array($condition) && $condition['field'] == 'base.name') {
       
  1258           $query_conditions[$key]['operator'] = 'LIKE';
       
  1259           $query_conditions[$key]['value'] = db_like($query_conditions[$key]['value']);
       
  1260         }
       
  1261       }
       
  1262     }
       
  1263     // Add the machine name field from the {taxonomy_vocabulary} table.
       
  1264     $query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid');
       
  1265     $query->addField('v', 'machine_name', 'vocabulary_machine_name');
       
  1266     return $query;
       
  1267   }
       
  1268 
       
  1269   protected function cacheGet($ids, $conditions = array()) {
       
  1270     $terms = parent::cacheGet($ids, $conditions);
       
  1271     // Name matching is case insensitive, note that with some collations
       
  1272     // LOWER() and drupal_strtolower() may return different results.
       
  1273     foreach ($terms as $term) {
       
  1274       $term_values = (array) $term;
       
  1275       if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) {
       
  1276         unset($terms[$term->tid]);
       
  1277       }
       
  1278     }
       
  1279     return $terms;
       
  1280   }
       
  1281 }
       
  1282 
       
  1283 /**
       
  1284  * Controller class for taxonomy vocabularies.
       
  1285  *
       
  1286  * This extends the DrupalDefaultEntityController class, adding required
       
  1287  * special handling for taxonomy vocabulary objects.
       
  1288  */
       
  1289 class TaxonomyVocabularyController extends DrupalDefaultEntityController {
       
  1290 
       
  1291   protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
       
  1292     $query = parent::buildQuery($ids, $conditions, $revision_id);
       
  1293     $query->addTag('translatable');
       
  1294     $query->orderBy('base.weight');
       
  1295     $query->orderBy('base.name');
       
  1296     return $query;
       
  1297   }
       
  1298 }
       
  1299 
       
  1300 /**
       
  1301  * Load multiple taxonomy terms based on certain conditions.
       
  1302  *
       
  1303  * This function should be used whenever you need to load more than one term
       
  1304  * from the database. Terms are loaded into memory and will not require
       
  1305  * database access if loaded again during the same page request.
       
  1306  *
       
  1307  * @see entity_load()
       
  1308  * @see EntityFieldQuery
       
  1309  *
       
  1310  * @param $tids
       
  1311  *   An array of taxonomy term IDs.
       
  1312  * @param $conditions
       
  1313  *   (deprecated) An associative array of conditions on the {taxonomy_term}
       
  1314  *   table, where the keys are the database fields and the values are the
       
  1315  *   values those fields must have. Instead, it is preferable to use
       
  1316  *   EntityFieldQuery to retrieve a list of entity IDs loadable by
       
  1317  *   this function.
       
  1318  *
       
  1319  * @return
       
  1320  *   An array of term objects, indexed by tid. When no results are found, an
       
  1321  *   empty array is returned.
       
  1322  *
       
  1323  * @todo Remove $conditions in Drupal 8.
       
  1324  */
       
  1325 function taxonomy_term_load_multiple($tids = array(), $conditions = array()) {
       
  1326   return entity_load('taxonomy_term', $tids, $conditions);
       
  1327 }
       
  1328 
       
  1329 /**
       
  1330  * Load multiple taxonomy vocabularies based on certain conditions.
       
  1331  *
       
  1332  * This function should be used whenever you need to load more than one
       
  1333  * vocabulary from the database. Terms are loaded into memory and will not
       
  1334  * require database access if loaded again during the same page request.
       
  1335  *
       
  1336  * @see entity_load()
       
  1337  *
       
  1338  * @param $vids
       
  1339  *  An array of taxonomy vocabulary IDs, or FALSE to load all vocabularies.
       
  1340  * @param $conditions
       
  1341  *  An array of conditions to add to the query.
       
  1342  *
       
  1343  * @return
       
  1344  *  An array of vocabulary objects, indexed by vid.
       
  1345  */
       
  1346 function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) {
       
  1347   return entity_load('taxonomy_vocabulary', $vids, $conditions);
       
  1348 }
       
  1349 
       
  1350 /**
       
  1351  * Return the vocabulary object matching a vocabulary ID.
       
  1352  *
       
  1353  * @param $vid
       
  1354  *   The vocabulary's ID.
       
  1355  *
       
  1356  * @return
       
  1357  *   The vocabulary object with all of its metadata, if exists, FALSE otherwise.
       
  1358  *   Results are statically cached.
       
  1359  *
       
  1360  * @see taxonomy_vocabulary_machine_name_load()
       
  1361  */
       
  1362 function taxonomy_vocabulary_load($vid) {
       
  1363   $vocabularies = taxonomy_vocabulary_load_multiple(array($vid));
       
  1364   return reset($vocabularies);
       
  1365 }
       
  1366 
       
  1367 /**
       
  1368  * Return the vocabulary object matching a vocabulary machine name.
       
  1369  *
       
  1370  * @param $name
       
  1371  *   The vocabulary's machine name.
       
  1372  *
       
  1373  * @return
       
  1374  *   The vocabulary object with all of its metadata, if exists, FALSE otherwise.
       
  1375  *   Results are statically cached.
       
  1376  *
       
  1377  * @see taxonomy_vocabulary_load()
       
  1378  */
       
  1379 function taxonomy_vocabulary_machine_name_load($name) {
       
  1380   $vocabularies = taxonomy_vocabulary_load_multiple(NULL, array('machine_name' => $name));
       
  1381   return reset($vocabularies);
       
  1382 }
       
  1383 
       
  1384 /**
       
  1385  * Return the term object matching a term ID.
       
  1386  *
       
  1387  * @param $tid
       
  1388  *   A term's ID
       
  1389  *
       
  1390  * @return
       
  1391  *   A taxonomy term object, or FALSE if the term was not found. Results are
       
  1392  *   statically cached.
       
  1393  */
       
  1394 function taxonomy_term_load($tid) {
       
  1395   if (!is_numeric($tid)) {
       
  1396     return FALSE;
       
  1397   }
       
  1398   $term = taxonomy_term_load_multiple(array($tid), array());
       
  1399   return $term ? $term[$tid] : FALSE;
       
  1400 }
       
  1401 
       
  1402 /**
       
  1403  * Helper function for array_map purposes.
       
  1404  */
       
  1405 function _taxonomy_get_tid_from_term($term) {
       
  1406   return $term->tid;
       
  1407 }
       
  1408 
       
  1409 /**
       
  1410  * Implodes a list of tags of a certain vocabulary into a string.
       
  1411  *
       
  1412  * @see drupal_explode_tags()
       
  1413  */
       
  1414 function taxonomy_implode_tags($tags, $vid = NULL) {
       
  1415   $typed_tags = array();
       
  1416   foreach ($tags as $tag) {
       
  1417     // Extract terms belonging to the vocabulary in question.
       
  1418     if (!isset($vid) || $tag->vid == $vid) {
       
  1419       // Make sure we have a completed loaded taxonomy term.
       
  1420       if (isset($tag->name)) {
       
  1421         // Commas and quotes in tag names are special cases, so encode 'em.
       
  1422         if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
       
  1423           $typed_tags[] = '"' . str_replace('"', '""', $tag->name) . '"';
       
  1424         }
       
  1425         else {
       
  1426           $typed_tags[] = $tag->name;
       
  1427         }
       
  1428       }
       
  1429     }
       
  1430   }
       
  1431   return implode(', ', $typed_tags);
       
  1432 }
       
  1433 
       
  1434 /**
       
  1435  * Implements hook_field_info().
       
  1436  *
       
  1437  * Field settings:
       
  1438  * - allowed_values: a list array of one or more vocabulary trees:
       
  1439  *   - vocabulary: a vocabulary machine name.
       
  1440  *   - parent: a term ID of a term whose children are allowed. This should be
       
  1441  *     '0' if all terms in a vocabulary are allowed. The allowed values do not
       
  1442  *     include the parent term.
       
  1443  *
       
  1444  */
       
  1445 function taxonomy_field_info() {
       
  1446   return array(
       
  1447     'taxonomy_term_reference' => array(
       
  1448       'label' => t('Term reference'),
       
  1449       'description' => t('This field stores a reference to a taxonomy term.'),
       
  1450       'default_widget' => 'options_select',
       
  1451       'default_formatter' => 'taxonomy_term_reference_link',
       
  1452       'settings' => array(
       
  1453         'allowed_values' => array(
       
  1454           array(
       
  1455             'vocabulary' => '',
       
  1456             'parent' => '0',
       
  1457           ),
       
  1458         ),
       
  1459       ),
       
  1460     ),
       
  1461   );
       
  1462 }
       
  1463 
       
  1464 /**
       
  1465  * Implements hook_field_widget_info().
       
  1466  */
       
  1467 function taxonomy_field_widget_info() {
       
  1468   return array(
       
  1469     'taxonomy_autocomplete' => array(
       
  1470       'label' => t('Autocomplete term widget (tagging)'),
       
  1471       'field types' => array('taxonomy_term_reference'),
       
  1472       'settings' => array(
       
  1473         'size' => 60,
       
  1474         'autocomplete_path' => 'taxonomy/autocomplete',
       
  1475       ),
       
  1476       'behaviors' => array(
       
  1477         'multiple values' => FIELD_BEHAVIOR_CUSTOM,
       
  1478       ),
       
  1479     ),
       
  1480   );
       
  1481 }
       
  1482 
       
  1483 /**
       
  1484  * Implements hook_field_widget_info_alter().
       
  1485  */
       
  1486 function taxonomy_field_widget_info_alter(&$info) {
       
  1487   $info['options_select']['field types'][] = 'taxonomy_term_reference';
       
  1488   $info['options_buttons']['field types'][] = 'taxonomy_term_reference';
       
  1489 }
       
  1490 
       
  1491 /**
       
  1492  * Implements hook_options_list().
       
  1493  */
       
  1494 function taxonomy_options_list($field, $instance, $entity_type, $entity) {
       
  1495   $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'taxonomy_allowed_values';
       
  1496   return $function($field);
       
  1497 }
       
  1498 
       
  1499 /**
       
  1500  * Implements hook_field_validate().
       
  1501  *
       
  1502  * Taxonomy field settings allow for either a single vocabulary ID, multiple
       
  1503  * vocabulary IDs, or sub-trees of a vocabulary to be specified as allowed
       
  1504  * values, although only the first of these is supported via the field UI.
       
  1505  * Confirm that terms entered as values meet at least one of these conditions.
       
  1506  *
       
  1507  * Possible error codes:
       
  1508  * - 'taxonomy_term_illegal_value': The value is not part of the list of allowed values.
       
  1509  */
       
  1510 function taxonomy_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
       
  1511   // Build an array of existing term IDs so they can be loaded with
       
  1512   // taxonomy_term_load_multiple();
       
  1513   foreach ($items as $delta => $item) {
       
  1514     if (!empty($item['tid']) && $item['tid'] != 'autocreate') {
       
  1515       $tids[] = $item['tid'];
       
  1516     }
       
  1517   }
       
  1518   if (!empty($tids)) {
       
  1519     $terms = taxonomy_term_load_multiple($tids);
       
  1520 
       
  1521     // Check each existing item to ensure it can be found in the
       
  1522     // allowed values for this field.
       
  1523     foreach ($items as $delta => $item) {
       
  1524       $validate = TRUE;
       
  1525       if (!empty($item['tid']) && $item['tid'] != 'autocreate') {
       
  1526         $validate = FALSE;
       
  1527         foreach ($field['settings']['allowed_values'] as $settings) {
       
  1528           // If no parent is specified, check if the term is in the vocabulary.
       
  1529           if (isset($settings['vocabulary']) && empty($settings['parent'])) {
       
  1530             if ($settings['vocabulary'] == $terms[$item['tid']]->vocabulary_machine_name) {
       
  1531               $validate = TRUE;
       
  1532               break;
       
  1533             }
       
  1534           }
       
  1535           // If a parent is specified, then to validate it must appear in the
       
  1536           // array returned by taxonomy_get_parents_all().
       
  1537           elseif (!empty($settings['parent'])) {
       
  1538             $ancestors = taxonomy_get_parents_all($item['tid']);
       
  1539             foreach ($ancestors as $ancestor) {
       
  1540               if ($ancestor->tid == $settings['parent']) {
       
  1541                 $validate = TRUE;
       
  1542                 break 2;
       
  1543               }
       
  1544             }
       
  1545           }
       
  1546         }
       
  1547       }
       
  1548       if (!$validate) {
       
  1549         $errors[$field['field_name']][$langcode][$delta][] = array(
       
  1550           'error' => 'taxonomy_term_reference_illegal_value',
       
  1551           'message' => t('%name: illegal value.', array('%name' => $instance['label'])),
       
  1552         );
       
  1553       }
       
  1554     }
       
  1555   }
       
  1556 }
       
  1557 
       
  1558 /**
       
  1559  * Implements hook_field_is_empty().
       
  1560  */
       
  1561 function taxonomy_field_is_empty($item, $field) {
       
  1562   if (!is_array($item) || (empty($item['tid']) && (string) $item['tid'] !== '0')) {
       
  1563     return TRUE;
       
  1564   }
       
  1565   return FALSE;
       
  1566 }
       
  1567 
       
  1568 /**
       
  1569  * Implements hook_field_formatter_info().
       
  1570  */
       
  1571 function taxonomy_field_formatter_info() {
       
  1572   return array(
       
  1573     'taxonomy_term_reference_link' => array(
       
  1574       'label' => t('Link'),
       
  1575       'field types' => array('taxonomy_term_reference'),
       
  1576     ),
       
  1577     'taxonomy_term_reference_plain' => array(
       
  1578       'label' => t('Plain text'),
       
  1579       'field types' => array('taxonomy_term_reference'),
       
  1580     ),
       
  1581     'taxonomy_term_reference_rss_category' => array(
       
  1582       'label' => t('RSS category'),
       
  1583       'field types' => array('taxonomy_term_reference'),
       
  1584     ),
       
  1585   );
       
  1586 }
       
  1587 
       
  1588 /**
       
  1589  * Implements hook_field_formatter_view().
       
  1590  */
       
  1591 function taxonomy_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
       
  1592   $element = array();
       
  1593 
       
  1594   // Terms whose tid is 'autocreate' do not exist
       
  1595   // yet and $item['taxonomy_term'] is not set. Theme such terms as
       
  1596   // just their name.
       
  1597 
       
  1598   switch ($display['type']) {
       
  1599     case 'taxonomy_term_reference_link':
       
  1600       foreach ($items as $delta => $item) {
       
  1601         if ($item['tid'] == 'autocreate') {
       
  1602           $element[$delta] = array(
       
  1603             '#markup' => check_plain($item['name']),
       
  1604           );
       
  1605         }
       
  1606         else {
       
  1607           $term = $item['taxonomy_term'];
       
  1608           $uri = entity_uri('taxonomy_term', $term);
       
  1609           $element[$delta] = array(
       
  1610             '#type' => 'link',
       
  1611             '#title' => $term->name,
       
  1612             '#href' => $uri['path'],
       
  1613             '#options' => $uri['options'],
       
  1614           );
       
  1615         }
       
  1616       }
       
  1617       break;
       
  1618 
       
  1619     case 'taxonomy_term_reference_plain':
       
  1620       foreach ($items as $delta => $item) {
       
  1621         $name = ($item['tid'] != 'autocreate' ? $item['taxonomy_term']->name : $item['name']);
       
  1622         $element[$delta] = array(
       
  1623           '#markup' => check_plain($name),
       
  1624         );
       
  1625       }
       
  1626       break;
       
  1627 
       
  1628     case 'taxonomy_term_reference_rss_category':
       
  1629       foreach ($items as $delta => $item) {
       
  1630         $entity->rss_elements[] = array(
       
  1631           'key' => 'category',
       
  1632           'value' => $item['tid'] != 'autocreate' ? $item['taxonomy_term']->name : $item['name'],
       
  1633           'attributes' => array(
       
  1634             'domain' => $item['tid'] != 'autocreate' ? url('taxonomy/term/' . $item['tid'], array('absolute' => TRUE)) : '',
       
  1635           ),
       
  1636         );
       
  1637       }
       
  1638       break;
       
  1639   }
       
  1640 
       
  1641   return $element;
       
  1642 }
       
  1643 
       
  1644 /**
       
  1645  * Returns the set of valid terms for a taxonomy field.
       
  1646  *
       
  1647  * @param $field
       
  1648  *   The field definition.
       
  1649  * @return
       
  1650  *   The array of valid terms for this field, keyed by term id.
       
  1651  */
       
  1652 function taxonomy_allowed_values($field) {
       
  1653   $options = array();
       
  1654   foreach ($field['settings']['allowed_values'] as $tree) {
       
  1655     if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
       
  1656       if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'])) {
       
  1657         foreach ($terms as $term) {
       
  1658           $options[$term->tid] = str_repeat('-', $term->depth) . $term->name;
       
  1659         }
       
  1660       }
       
  1661     }
       
  1662   }
       
  1663   return $options;
       
  1664 }
       
  1665 
       
  1666 /**
       
  1667  * Implements hook_field_formatter_prepare_view().
       
  1668  *
       
  1669  * This preloads all taxonomy terms for multiple loaded objects at once and
       
  1670  * unsets values for invalid terms that do not exist.
       
  1671  */
       
  1672 function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
       
  1673   $tids = array();
       
  1674 
       
  1675   // Collect every possible term attached to any of the fieldable entities.
       
  1676   foreach ($entities as $id => $entity) {
       
  1677     foreach ($items[$id] as $delta => $item) {
       
  1678       // Force the array key to prevent duplicates.
       
  1679       if ($item['tid'] != 'autocreate') {
       
  1680         $tids[$item['tid']] = $item['tid'];
       
  1681       }
       
  1682     }
       
  1683   }
       
  1684   if ($tids) {
       
  1685     $terms = taxonomy_term_load_multiple($tids);
       
  1686 
       
  1687     // Iterate through the fieldable entities again to attach the loaded term data.
       
  1688     foreach ($entities as $id => $entity) {
       
  1689       $rekey = FALSE;
       
  1690 
       
  1691       foreach ($items[$id] as $delta => $item) {
       
  1692         // Check whether the taxonomy term field instance value could be loaded.
       
  1693         if (isset($terms[$item['tid']])) {
       
  1694           // Replace the instance value with the term data.
       
  1695           $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']];
       
  1696         }
       
  1697         // Terms to be created are not in $terms, but are still legitimate.
       
  1698         elseif ($item['tid'] == 'autocreate') {
       
  1699           // Leave the item in place.
       
  1700         }
       
  1701         // Otherwise, unset the instance value, since the term does not exist.
       
  1702         else {
       
  1703           unset($items[$id][$delta]);
       
  1704           $rekey = TRUE;
       
  1705         }
       
  1706       }
       
  1707 
       
  1708       if ($rekey) {
       
  1709         // Rekey the items array.
       
  1710         $items[$id] = array_values($items[$id]);
       
  1711       }
       
  1712     }
       
  1713   }
       
  1714 }
       
  1715 
       
  1716 /**
       
  1717  * Title callback for term pages.
       
  1718  *
       
  1719  * @param $term
       
  1720  *   A term object.
       
  1721  *
       
  1722  * @return
       
  1723  *   The term name to be used as the page title.
       
  1724  */
       
  1725 function taxonomy_term_title($term) {
       
  1726   return $term->name;
       
  1727 }
       
  1728 
       
  1729 /**
       
  1730  * Implements hook_field_widget_form().
       
  1731  */
       
  1732 function taxonomy_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
       
  1733   $tags = array();
       
  1734   foreach ($items as $item) {
       
  1735     $tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']);
       
  1736   }
       
  1737 
       
  1738   $element += array(
       
  1739     '#type' => 'textfield',
       
  1740     '#default_value' => taxonomy_implode_tags($tags),
       
  1741     '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $field['field_name'],
       
  1742     '#size' => $instance['widget']['settings']['size'],
       
  1743     '#maxlength' => 1024,
       
  1744     '#element_validate' => array('taxonomy_autocomplete_validate'),
       
  1745   );
       
  1746 
       
  1747   return $element;
       
  1748 }
       
  1749 
       
  1750 /**
       
  1751  * Form element validate handler for taxonomy term autocomplete element.
       
  1752  */
       
  1753 function taxonomy_autocomplete_validate($element, &$form_state) {
       
  1754   // Autocomplete widgets do not send their tids in the form, so we must detect
       
  1755   // them here and process them independently.
       
  1756   $value = array();
       
  1757   if ($tags = $element['#value']) {
       
  1758     // Collect candidate vocabularies.
       
  1759     $field = field_widget_field($element, $form_state);
       
  1760     $vocabularies = array();
       
  1761     foreach ($field['settings']['allowed_values'] as $tree) {
       
  1762       if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
       
  1763         $vocabularies[$vocabulary->vid] = $vocabulary;
       
  1764       }
       
  1765     }
       
  1766 
       
  1767     // Translate term names into actual terms.
       
  1768     $typed_terms = drupal_explode_tags($tags);
       
  1769     foreach ($typed_terms as $typed_term) {
       
  1770       // See if the term exists in the chosen vocabulary and return the tid;
       
  1771       // otherwise, create a new 'autocreate' term for insert/update.
       
  1772       if ($possibilities = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => array_keys($vocabularies)))) {
       
  1773         $term = array_pop($possibilities);
       
  1774       }
       
  1775       else {
       
  1776         $vocabulary = reset($vocabularies);
       
  1777         $term = array(
       
  1778           'tid' => 'autocreate',
       
  1779           'vid' => $vocabulary->vid,
       
  1780           'name' => $typed_term,
       
  1781           'vocabulary_machine_name' => $vocabulary->machine_name,
       
  1782         );
       
  1783       }
       
  1784       $value[] = (array)$term;
       
  1785     }
       
  1786   }
       
  1787 
       
  1788   form_set_value($element, $value, $form_state);
       
  1789 }
       
  1790 
       
  1791 /**
       
  1792  * Implements hook_field_widget_error().
       
  1793  */
       
  1794 function taxonomy_field_widget_error($element, $error, $form, &$form_state) {
       
  1795   form_error($element, $error['message']);
       
  1796 }
       
  1797 /**
       
  1798  * Implements hook_field_settings_form().
       
  1799  */
       
  1800 function taxonomy_field_settings_form($field, $instance, $has_data) {
       
  1801   // Get proper values for 'allowed_values_function', which is a core setting.
       
  1802   $vocabularies = taxonomy_get_vocabularies();
       
  1803   $options = array();
       
  1804   foreach ($vocabularies as $vocabulary) {
       
  1805     $options[$vocabulary->machine_name] = $vocabulary->name;
       
  1806   }
       
  1807   $form['allowed_values'] = array(
       
  1808     '#tree' => TRUE,
       
  1809   );
       
  1810 
       
  1811   foreach ($field['settings']['allowed_values'] as $delta => $tree) {
       
  1812     $form['allowed_values'][$delta]['vocabulary'] = array(
       
  1813       '#type' => 'select',
       
  1814       '#title' => t('Vocabulary'),
       
  1815       '#default_value' => $tree['vocabulary'],
       
  1816       '#options' => $options,
       
  1817       '#required' => TRUE,
       
  1818       '#description' => t('The vocabulary which supplies the options for this field.'),
       
  1819       '#disabled' => $has_data,
       
  1820     );
       
  1821     $form['allowed_values'][$delta]['parent'] = array(
       
  1822       '#type' => 'value',
       
  1823       '#value' => $tree['parent'],
       
  1824     );
       
  1825   }
       
  1826 
       
  1827   return $form;
       
  1828 }
       
  1829 
       
  1830 /**
       
  1831  * Implements hook_rdf_mapping().
       
  1832  *
       
  1833  * @return array
       
  1834  *   The rdf mapping for vocabularies and terms.
       
  1835  */
       
  1836 function taxonomy_rdf_mapping() {
       
  1837   return array(
       
  1838     array(
       
  1839       'type' => 'taxonomy_term',
       
  1840       'bundle' => RDF_DEFAULT_BUNDLE,
       
  1841       'mapping' => array(
       
  1842         'rdftype' => array('skos:Concept'),
       
  1843         'name'   => array(
       
  1844           'predicates' => array('rdfs:label', 'skos:prefLabel'),
       
  1845         ),
       
  1846         'description'   => array(
       
  1847           'predicates' => array('skos:definition'),
       
  1848         ),
       
  1849         'vid'   => array(
       
  1850           'predicates' => array('skos:inScheme'),
       
  1851           'type' => 'rel',
       
  1852         ),
       
  1853         'parent'   => array(
       
  1854           'predicates' => array('skos:broader'),
       
  1855           'type' => 'rel',
       
  1856         ),
       
  1857       ),
       
  1858     ),
       
  1859     array(
       
  1860       'type' => 'taxonomy_vocabulary',
       
  1861       'bundle' => RDF_DEFAULT_BUNDLE,
       
  1862       'mapping' => array(
       
  1863         'rdftype' => array('skos:ConceptScheme'),
       
  1864         'name'   => array(
       
  1865           'predicates' => array('dc:title'),
       
  1866         ),
       
  1867         'description'   => array(
       
  1868           'predicates' => array('rdfs:comment'),
       
  1869         ),
       
  1870       ),
       
  1871     ),
       
  1872   );
       
  1873 }
       
  1874 
       
  1875 /**
       
  1876  * @defgroup taxonomy_index Taxonomy indexing
       
  1877  * @{
       
  1878  * Functions to maintain taxonomy indexing.
       
  1879  *
       
  1880  * Taxonomy uses default field storage to store canonical relationships
       
  1881  * between terms and fieldable entities. However its most common use case
       
  1882  * requires listing all content associated with a term or group of terms
       
  1883  * sorted by creation date. To avoid slow queries due to joining across
       
  1884  * multiple node and field tables with various conditions and order by criteria,
       
  1885  * we maintain a denormalized table with all relationships between terms,
       
  1886  * published nodes and common sort criteria such as sticky and created.
       
  1887  * This is used as a lookup table by taxonomy_select_nodes(). When using other
       
  1888  * field storage engines or alternative methods of denormalizing this data
       
  1889  * you should set the variable 'taxonomy_maintain_index_table' to FALSE
       
  1890  * to avoid unnecessary writes in SQL.
       
  1891  */
       
  1892 
       
  1893 /**
       
  1894  * Implements hook_field_presave().
       
  1895  *
       
  1896  * Create any new terms defined in a freetagging vocabulary.
       
  1897  */
       
  1898 function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
       
  1899   foreach ($items as $delta => $item) {
       
  1900     if ($item['tid'] == 'autocreate') {
       
  1901       $term = (object) $item;
       
  1902       unset($term->tid);
       
  1903       taxonomy_term_save($term);
       
  1904       $items[$delta]['tid'] = $term->tid;
       
  1905     }
       
  1906   }
       
  1907 }
       
  1908 
       
  1909 /**
       
  1910  * Implements hook_node_insert().
       
  1911  */
       
  1912 function taxonomy_node_insert($node) {
       
  1913   // Add taxonomy index entries for the node.
       
  1914   taxonomy_build_node_index($node);
       
  1915 }
       
  1916 
       
  1917 /**
       
  1918  * Builds and inserts taxonomy index entries for a given node.
       
  1919  *
       
  1920  * The index lists all terms that are related to a given node entity, and is
       
  1921  * therefore maintained at the entity level.
       
  1922  *
       
  1923  * @param $node
       
  1924  *   The node object.
       
  1925  */
       
  1926 function taxonomy_build_node_index($node) {
       
  1927   // We maintain a denormalized table of term/node relationships, containing
       
  1928   // only data for current, published nodes.
       
  1929   $status = NULL;
       
  1930   if (variable_get('taxonomy_maintain_index_table', TRUE)) {
       
  1931     // If a node property is not set in the node object when node_save() is
       
  1932     // called, the old value from $node->original is used.
       
  1933     if (!empty($node->original)) {
       
  1934       $status = (int)(!empty($node->status) || (!isset($node->status) && !empty($node->original->status)));
       
  1935       $sticky = (int)(!empty($node->sticky) || (!isset($node->sticky) && !empty($node->original->sticky)));
       
  1936     }
       
  1937     else {
       
  1938       $status = (int)(!empty($node->status));
       
  1939       $sticky = (int)(!empty($node->sticky));
       
  1940     }
       
  1941   }
       
  1942   // We only maintain the taxonomy index for published nodes.
       
  1943   if ($status) {
       
  1944     // Collect a unique list of all the term IDs from all node fields.
       
  1945     $tid_all = array();
       
  1946     foreach (field_info_instances('node', $node->type) as $instance) {
       
  1947       $field_name = $instance['field_name'];
       
  1948       $field = field_info_field($field_name);
       
  1949       if ($field['module'] == 'taxonomy' && $field['storage']['type'] == 'field_sql_storage') {
       
  1950         // If a field value is not set in the node object when node_save() is
       
  1951         // called, the old value from $node->original is used.
       
  1952         if (isset($node->{$field_name})) {
       
  1953           $items = $node->{$field_name};
       
  1954         }
       
  1955         elseif (isset($node->original->{$field_name})) {
       
  1956           $items = $node->original->{$field_name};
       
  1957         }
       
  1958         else {
       
  1959           continue;
       
  1960         }
       
  1961         foreach (field_available_languages('node', $field) as $langcode) {
       
  1962           if (!empty($items[$langcode])) {
       
  1963             foreach ($items[$langcode] as $item) {
       
  1964               $tid_all[$item['tid']] = $item['tid'];
       
  1965             }
       
  1966           }
       
  1967         }
       
  1968       }
       
  1969     }
       
  1970     // Insert index entries for all the node's terms.
       
  1971     if (!empty($tid_all)) {
       
  1972       $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created'));
       
  1973       foreach ($tid_all as $tid) {
       
  1974         $query->values(array(
       
  1975           'nid' => $node->nid,
       
  1976           'tid' => $tid,
       
  1977           'sticky' => $sticky,
       
  1978           'created' => $node->created,
       
  1979         ));
       
  1980       }
       
  1981       $query->execute();
       
  1982     }
       
  1983   }
       
  1984 }
       
  1985 
       
  1986 /**
       
  1987  * Implements hook_node_update().
       
  1988  */
       
  1989 function taxonomy_node_update($node) {
       
  1990   // Always rebuild the node's taxonomy index entries on node save.
       
  1991   taxonomy_delete_node_index($node);
       
  1992   taxonomy_build_node_index($node);
       
  1993 }
       
  1994 
       
  1995 /**
       
  1996  * Implements hook_node_delete().
       
  1997  */
       
  1998 function taxonomy_node_delete($node) {
       
  1999   // Clean up the {taxonomy_index} table when nodes are deleted.
       
  2000   taxonomy_delete_node_index($node);
       
  2001 }
       
  2002 
       
  2003 /**
       
  2004  * Deletes taxonomy index entries for a given node.
       
  2005  *
       
  2006  * @param $node
       
  2007  *   The node object.
       
  2008  */
       
  2009 function taxonomy_delete_node_index($node) {
       
  2010   if (variable_get('taxonomy_maintain_index_table', TRUE)) {
       
  2011     db_delete('taxonomy_index')->condition('nid', $node->nid)->execute();
       
  2012   }
       
  2013 }
       
  2014 
       
  2015 /**
       
  2016  * Implements hook_taxonomy_term_delete().
       
  2017  */
       
  2018 function taxonomy_taxonomy_term_delete($term) {
       
  2019   if (variable_get('taxonomy_maintain_index_table', TRUE)) {
       
  2020     // Clean up the {taxonomy_index} table when terms are deleted.
       
  2021     db_delete('taxonomy_index')->condition('tid', $term->tid)->execute();
       
  2022   }
       
  2023 }
       
  2024 
       
  2025 /**
       
  2026  * @} End of "defgroup taxonomy_index".
       
  2027  */
       
  2028 
       
  2029 /**
       
  2030  * Implements hook_entity_query_alter().
       
  2031  *
       
  2032  * Converts EntityFieldQuery instances on taxonomy terms that have an entity
       
  2033  * condition on term bundles (vocabulary machine names). Since the vocabulary
       
  2034  * machine name is not present in the {taxonomy_term_data} table itself, we have
       
  2035  * to convert the bundle condition into a property condition of vocabulary IDs
       
  2036  * to match against {taxonomy_term_data}.vid.
       
  2037  */
       
  2038 function taxonomy_entity_query_alter($query) {
       
  2039   $conditions = &$query->entityConditions;
       
  2040 
       
  2041   // Alter only taxonomy term queries with bundle conditions.
       
  2042   if (isset($conditions['entity_type']) && $conditions['entity_type']['value'] == 'taxonomy_term' && isset($conditions['bundle'])) {
       
  2043     // Convert vocabulary machine names to vocabulary IDs.
       
  2044     $vocabulary_data = taxonomy_vocabulary_get_names();
       
  2045     $vids = array();
       
  2046     if (is_array($conditions['bundle']['value'])) {
       
  2047       foreach ($conditions['bundle']['value'] as $vocabulary_machine_name) {
       
  2048         $vids[] = $vocabulary_data[$vocabulary_machine_name]->vid;
       
  2049       }
       
  2050     }
       
  2051     else {
       
  2052       $vocabulary_machine_name = $conditions['bundle']['value'];
       
  2053       $vids = $vocabulary_data[$vocabulary_machine_name]->vid;
       
  2054     }
       
  2055 
       
  2056     $query->propertyCondition('vid', $vids, $conditions['bundle']['operator']);
       
  2057     unset($conditions['bundle']);
       
  2058   }
       
  2059 }