cms/drupal/modules/taxonomy/taxonomy.admin.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Administrative page callbacks for the taxonomy module.
       
     6  */
       
     7 
       
     8 /**
       
     9  * Form builder to list and manage vocabularies.
       
    10  *
       
    11  * @ingroup forms
       
    12  * @see taxonomy_overview_vocabularies_submit()
       
    13  * @see theme_taxonomy_overview_vocabularies()
       
    14  */
       
    15 function taxonomy_overview_vocabularies($form) {
       
    16   $vocabularies = taxonomy_get_vocabularies();
       
    17   $form['#tree'] = TRUE;
       
    18   foreach ($vocabularies as $vocabulary) {
       
    19     $form[$vocabulary->vid]['#vocabulary'] = $vocabulary;
       
    20     $form[$vocabulary->vid]['name'] = array('#markup' => check_plain($vocabulary->name));
       
    21     $form[$vocabulary->vid]['weight'] = array(
       
    22       '#type' => 'weight',
       
    23       '#title' => t('Weight for @title', array('@title' => $vocabulary->name)),
       
    24       '#title_display' => 'invisible',
       
    25       '#delta' => 10,
       
    26       '#default_value' => $vocabulary->weight,
       
    27     );
       
    28     $form[$vocabulary->vid]['edit'] = array('#type' => 'link', '#title' => t('edit vocabulary'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name/edit");
       
    29     $form[$vocabulary->vid]['list'] = array('#type' => 'link', '#title' => t('list terms'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name");
       
    30     $form[$vocabulary->vid]['add'] = array('#type' => 'link', '#title' => t('add terms'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name/add");
       
    31   }
       
    32 
       
    33   // Only make this form include a submit button and weight if more than one
       
    34   // vocabulary exists.
       
    35   if (count($vocabularies) > 1) {
       
    36     $form['actions'] = array('#type' => 'actions');
       
    37     $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
       
    38   }
       
    39   elseif (isset($vocabulary)) {
       
    40     unset($form[$vocabulary->vid]['weight']);
       
    41   }
       
    42   return $form;
       
    43 }
       
    44 
       
    45 /**
       
    46  * Submit handler for vocabularies overview. Updates changed vocabulary weights.
       
    47  *
       
    48  * @see taxonomy_overview_vocabularies()
       
    49  */
       
    50 function taxonomy_overview_vocabularies_submit($form, &$form_state) {
       
    51   foreach ($form_state['values'] as $vid => $vocabulary) {
       
    52     if (is_numeric($vid) && $form[$vid]['#vocabulary']->weight != $form_state['values'][$vid]['weight']) {
       
    53       $form[$vid]['#vocabulary']->weight = $form_state['values'][$vid]['weight'];
       
    54       taxonomy_vocabulary_save($form[$vid]['#vocabulary']);
       
    55     }
       
    56   }
       
    57   drupal_set_message(t('The configuration options have been saved.'));
       
    58 }
       
    59 
       
    60 /**
       
    61  * Returns HTML for the vocabulary overview form as a sortable list of vocabularies.
       
    62  *
       
    63  * @param $variables
       
    64  *   An associative array containing:
       
    65  *   - form: A render element representing the form.
       
    66  *
       
    67  * @see taxonomy_overview_vocabularies()
       
    68  * @ingroup themeable
       
    69  */
       
    70 function theme_taxonomy_overview_vocabularies($variables) {
       
    71   $form = $variables['form'];
       
    72 
       
    73   $rows = array();
       
    74 
       
    75   foreach (element_children($form) as $key) {
       
    76     if (isset($form[$key]['name'])) {
       
    77       $vocabulary = &$form[$key];
       
    78 
       
    79       $row = array();
       
    80       $row[] = drupal_render($vocabulary['name']);
       
    81       if (isset($vocabulary['weight'])) {
       
    82         $vocabulary['weight']['#attributes']['class'] = array('vocabulary-weight');
       
    83         $row[] = drupal_render($vocabulary['weight']);
       
    84       }
       
    85       $row[] = drupal_render($vocabulary['edit']);
       
    86       $row[] = drupal_render($vocabulary['list']);
       
    87       $row[] = drupal_render($vocabulary['add']);
       
    88       $rows[] = array('data' => $row, 'class' => array('draggable'));
       
    89     }
       
    90   }
       
    91 
       
    92   $header = array(t('Vocabulary name'));
       
    93   if (isset($form['actions'])) {
       
    94     $header[] = t('Weight');
       
    95     drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'vocabulary-weight');
       
    96   }
       
    97   $header[] = array('data' => t('Operations'), 'colspan' => '3');
       
    98   return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No vocabularies available. <a href="@link">Add vocabulary</a>.', array('@link' => url('admin/structure/taxonomy/add'))), 'attributes' => array('id' => 'taxonomy'))) . drupal_render_children($form);
       
    99 }
       
   100 
       
   101 /**
       
   102  * Form builder for the vocabulary editing form.
       
   103  *
       
   104  * @ingroup forms
       
   105  * @see taxonomy_form_vocabulary_submit()
       
   106  * @see taxonomy_form_vocabulary_validate()
       
   107  */
       
   108 function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
       
   109   // During initial form build, add the entity to the form state for use
       
   110   // during form building and processing. During a rebuild, use what is in the
       
   111   // form state.
       
   112   if (!isset($form_state['vocabulary'])) {
       
   113     $vocabulary = is_object($edit) ? $edit : (object) $edit;
       
   114     $defaults = array(
       
   115       'name' => '',
       
   116       'machine_name' => '',
       
   117       'description' => '',
       
   118       'hierarchy' => 0,
       
   119       'weight' => 0,
       
   120     );
       
   121     foreach ($defaults as $key => $value) {
       
   122       if (!isset($vocabulary->$key)) {
       
   123         $vocabulary->$key = $value;
       
   124       }
       
   125     }
       
   126     $form_state['vocabulary'] = $vocabulary;
       
   127   }
       
   128   else {
       
   129     $vocabulary = $form_state['vocabulary'];
       
   130   }
       
   131 
       
   132   // @todo Legacy support. Modules are encouraged to access the entity using
       
   133   //   $form_state. Remove in Drupal 8.
       
   134   $form['#vocabulary'] = $form_state['vocabulary'];
       
   135 
       
   136   // Check whether we need a deletion confirmation form.
       
   137   if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
       
   138     return taxonomy_vocabulary_confirm_delete($form, $form_state, $form_state['values']['vid']);
       
   139   }
       
   140   $form['name'] = array(
       
   141     '#type' => 'textfield',
       
   142     '#title' => t('Name'),
       
   143     '#default_value' => $vocabulary->name,
       
   144     '#maxlength' => 255,
       
   145     '#required' => TRUE,
       
   146   );
       
   147   $form['machine_name'] = array(
       
   148     '#type' => 'machine_name',
       
   149     '#default_value' => $vocabulary->machine_name,
       
   150     '#maxlength' => 255,
       
   151     '#machine_name' => array(
       
   152       'exists' => 'taxonomy_vocabulary_machine_name_load',
       
   153     ),
       
   154   );
       
   155   $form['old_machine_name'] = array(
       
   156     '#type' => 'value',
       
   157     '#value' => $vocabulary->machine_name,
       
   158   );
       
   159   $form['description'] = array(
       
   160     '#type' => 'textfield',
       
   161     '#title' => t('Description'),
       
   162     '#default_value' => $vocabulary->description,
       
   163   );
       
   164   // Set the hierarchy to "multiple parents" by default. This simplifies the
       
   165   // vocabulary form and standardizes the term form.
       
   166   $form['hierarchy'] = array(
       
   167     '#type' => 'value',
       
   168     '#value' => '0',
       
   169   );
       
   170 
       
   171   $form['actions'] = array('#type' => 'actions');
       
   172   $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
       
   173   if (isset($vocabulary->vid)) {
       
   174     $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
       
   175     $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid);
       
   176     $form['module'] = array('#type' => 'value', '#value' => $vocabulary->module);
       
   177   }
       
   178   $form['#validate'][] = 'taxonomy_form_vocabulary_validate';
       
   179 
       
   180   return $form;
       
   181 }
       
   182 
       
   183 /**
       
   184  * Form validation handler for taxonomy_form_vocabulary().
       
   185  *
       
   186  * Makes sure that the machine name of the vocabulary is not in the
       
   187  * disallowed list (names that conflict with menu items, such as 'list'
       
   188  * and 'add').
       
   189  *
       
   190  * @see taxonomy_form_vocabulary()
       
   191  * @see taxonomy_form_vocabulary_submit()
       
   192  */
       
   193 function taxonomy_form_vocabulary_validate($form, &$form_state) {
       
   194   // During the deletion there is no 'machine_name' key
       
   195   if (isset($form_state['values']['machine_name'])) {
       
   196     // Do not allow machine names to conflict with taxonomy path arguments.
       
   197     $machine_name = $form_state['values']['machine_name'];
       
   198     $disallowed = array('add', 'list');
       
   199     if (in_array($machine_name, $disallowed)) {
       
   200       form_set_error('machine_name', t('The machine-readable name cannot be "add" or "list".'));
       
   201     }
       
   202   }
       
   203 }
       
   204 
       
   205 /**
       
   206  * Form submission handler for taxonomy_form_vocabulary().
       
   207  *
       
   208  * @see taxonomy_form_vocabulary()
       
   209  * @see taxonomy_form_vocabulary_validate()
       
   210  */
       
   211 function taxonomy_form_vocabulary_submit($form, &$form_state) {
       
   212   if ($form_state['triggering_element']['#value'] == t('Delete')) {
       
   213     // Rebuild the form to confirm vocabulary deletion.
       
   214     $form_state['rebuild'] = TRUE;
       
   215     $form_state['confirm_delete'] = TRUE;
       
   216     return;
       
   217   }
       
   218 
       
   219   $vocabulary = $form_state['vocabulary'];
       
   220   entity_form_submit_build_entity('taxonomy_vocabulary', $vocabulary, $form, $form_state);
       
   221 
       
   222   switch (taxonomy_vocabulary_save($vocabulary)) {
       
   223     case SAVED_NEW:
       
   224       drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
       
   225       watchdog('taxonomy', 'Created new vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
       
   226       break;
       
   227 
       
   228     case SAVED_UPDATED:
       
   229       drupal_set_message(t('Updated vocabulary %name.', array('%name' => $vocabulary->name)));
       
   230       watchdog('taxonomy', 'Updated vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
       
   231       break;
       
   232   }
       
   233 
       
   234   $form_state['values']['vid'] = $vocabulary->vid;
       
   235   $form_state['vid'] = $vocabulary->vid;
       
   236   $form_state['redirect'] = 'admin/structure/taxonomy';
       
   237 }
       
   238 
       
   239 /**
       
   240  * Form builder for the taxonomy terms overview.
       
   241  *
       
   242  * Display a tree of all the terms in a vocabulary, with options to edit
       
   243  * each one. The form is made drag and drop by the theme function.
       
   244  *
       
   245  * @ingroup forms
       
   246  * @see taxonomy_overview_terms_submit()
       
   247  * @see theme_taxonomy_overview_terms()
       
   248  */
       
   249 function taxonomy_overview_terms($form, &$form_state, $vocabulary) {
       
   250   global $pager_page_array, $pager_total, $pager_total_items;
       
   251 
       
   252   // Check for confirmation forms.
       
   253   if (isset($form_state['confirm_reset_alphabetical'])) {
       
   254     return taxonomy_vocabulary_confirm_reset_alphabetical($form, $form_state, $vocabulary->vid);
       
   255   }
       
   256 
       
   257   $form['#vocabulary'] = $vocabulary;
       
   258   $form['#tree'] = TRUE;
       
   259   $form['#parent_fields'] = FALSE;
       
   260 
       
   261   $page            = isset($_GET['page']) ? $_GET['page'] : 0;
       
   262   $page_increment  = variable_get('taxonomy_terms_per_page_admin', 100);  // Number of terms per page.
       
   263   $page_entries    = 0;   // Elements shown on this page.
       
   264   $before_entries  = 0;   // Elements at the root level before this page.
       
   265   $after_entries   = 0;   // Elements at the root level after this page.
       
   266   $root_entries    = 0;   // Elements at the root level on this page.
       
   267 
       
   268   // Terms from previous and next pages are shown if the term tree would have
       
   269   // been cut in the middle. Keep track of how many extra terms we show on each
       
   270   // page of terms.
       
   271   $back_step    = NULL;
       
   272   $forward_step = 0;
       
   273 
       
   274   // An array of the terms to be displayed on this page.
       
   275   $current_page = array();
       
   276 
       
   277   $delta = 0;
       
   278   $term_deltas = array();
       
   279   $tree = taxonomy_get_tree($vocabulary->vid);
       
   280   $term = current($tree);
       
   281   do {
       
   282     // In case this tree is completely empty.
       
   283     if (empty($term)) {
       
   284       break;
       
   285     }
       
   286     $delta++;
       
   287     // Count entries before the current page.
       
   288     if ($page && ($page * $page_increment) > $before_entries && !isset($back_step)) {
       
   289       $before_entries++;
       
   290       continue;
       
   291     }
       
   292     // Count entries after the current page.
       
   293     elseif ($page_entries > $page_increment && isset($complete_tree)) {
       
   294       $after_entries++;
       
   295       continue;
       
   296     }
       
   297 
       
   298     // Do not let a term start the page that is not at the root.
       
   299     if (isset($term->depth) && ($term->depth > 0) && !isset($back_step)) {
       
   300       $back_step = 0;
       
   301       while ($pterm = prev($tree)) {
       
   302         $before_entries--;
       
   303         $back_step++;
       
   304         if ($pterm->depth == 0) {
       
   305           prev($tree);
       
   306           continue 2; // Jump back to the start of the root level parent.
       
   307        }
       
   308       }
       
   309     }
       
   310     $back_step = isset($back_step) ? $back_step : 0;
       
   311 
       
   312     // Continue rendering the tree until we reach the a new root item.
       
   313     if ($page_entries >= $page_increment + $back_step + 1 && $term->depth == 0 && $root_entries > 1) {
       
   314       $complete_tree = TRUE;
       
   315       // This new item at the root level is the first item on the next page.
       
   316       $after_entries++;
       
   317       continue;
       
   318     }
       
   319     if ($page_entries >= $page_increment + $back_step) {
       
   320       $forward_step++;
       
   321     }
       
   322 
       
   323     // Finally, if we've gotten down this far, we're rendering a term on this page.
       
   324     $page_entries++;
       
   325     $term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0;
       
   326     $key = 'tid:' . $term->tid . ':' . $term_deltas[$term->tid];
       
   327 
       
   328     // Keep track of the first term displayed on this page.
       
   329     if ($page_entries == 1) {
       
   330       $form['#first_tid'] = $term->tid;
       
   331     }
       
   332     // Keep a variable to make sure at least 2 root elements are displayed.
       
   333     if ($term->parents[0] == 0) {
       
   334       $root_entries++;
       
   335     }
       
   336     $current_page[$key] = $term;
       
   337   } while ($term = next($tree));
       
   338 
       
   339   // Because we didn't use a pager query, set the necessary pager variables.
       
   340   $total_entries = $before_entries + $page_entries + $after_entries;
       
   341   $pager_total_items[0] = $total_entries;
       
   342   $pager_page_array[0] = $page;
       
   343   $pager_total[0] = ceil($total_entries / $page_increment);
       
   344 
       
   345   // If this form was already submitted once, it's probably hit a validation
       
   346   // error. Ensure the form is rebuilt in the same order as the user submitted.
       
   347   if (!empty($form_state['input'])) {
       
   348     $order = array_flip(array_keys($form_state['input'])); // Get the $_POST order.
       
   349     $current_page = array_merge($order, $current_page); // Update our form with the new order.
       
   350     foreach ($current_page as $key => $term) {
       
   351       // Verify this is a term for the current page and set at the current depth.
       
   352       if (is_array($form_state['input'][$key]) && is_numeric($form_state['input'][$key]['tid'])) {
       
   353         $current_page[$key]->depth = $form_state['input'][$key]['depth'];
       
   354       }
       
   355       else {
       
   356         unset($current_page[$key]);
       
   357       }
       
   358     }
       
   359   }
       
   360 
       
   361   // Build the actual form.
       
   362   foreach ($current_page as $key => $term) {
       
   363     // Save the term for the current page so we don't have to load it a second time.
       
   364     $form[$key]['#term'] = (array) $term;
       
   365     if (isset($term->parents)) {
       
   366       $form[$key]['#term']['parent'] = $term->parent = $term->parents[0];
       
   367       unset($form[$key]['#term']['parents'], $term->parents);
       
   368     }
       
   369 
       
   370     $form[$key]['view'] = array('#type' => 'link', '#title' => $term->name, '#href' => "taxonomy/term/$term->tid");
       
   371     if ($vocabulary->hierarchy < 2 && count($tree) > 1) {
       
   372       $form['#parent_fields'] = TRUE;
       
   373       $form[$key]['tid'] = array(
       
   374         '#type' => 'hidden',
       
   375         '#value' => $term->tid
       
   376       );
       
   377       $form[$key]['parent'] = array(
       
   378         '#type' => 'hidden',
       
   379         // Yes, default_value on a hidden. It needs to be changeable by the javascript.
       
   380         '#default_value' => $term->parent,
       
   381       );
       
   382       $form[$key]['depth'] = array(
       
   383         '#type' => 'hidden',
       
   384         // Same as above, the depth is modified by javascript, so it's a default_value.
       
   385         '#default_value' => $term->depth,
       
   386       );
       
   387       $form[$key]['weight'] = array(
       
   388         '#type' => 'weight',
       
   389         '#delta' => $delta,
       
   390         '#title_display' => 'invisible',
       
   391         '#title' => t('Weight for added term'),
       
   392         '#default_value' => $term->weight,
       
   393       );
       
   394     }
       
   395     $form[$key]['edit'] = array('#type' => 'link', '#title' => t('edit'), '#href' => 'taxonomy/term/' . $term->tid . '/edit', '#options' => array('query' => drupal_get_destination()));
       
   396   }
       
   397 
       
   398   $form['#total_entries'] = $total_entries;
       
   399   $form['#page_increment'] = $page_increment;
       
   400   $form['#page_entries'] = $page_entries;
       
   401   $form['#back_step'] = $back_step;
       
   402   $form['#forward_step'] = $forward_step;
       
   403   $form['#empty_text'] = t('No terms available. <a href="@link">Add term</a>.', array('@link' => url('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add')));
       
   404 
       
   405   if ($vocabulary->hierarchy < 2 && count($tree) > 1) {
       
   406     $form['actions'] = array('#type' => 'actions', '#tree' => FALSE);
       
   407     $form['actions']['submit'] = array(
       
   408       '#type' => 'submit',
       
   409       '#value' => t('Save')
       
   410     );
       
   411     $form['actions']['reset_alphabetical'] = array(
       
   412       '#type' => 'submit',
       
   413       '#value' => t('Reset to alphabetical')
       
   414     );
       
   415     $form_state['redirect'] = array($_GET['q'], (isset($_GET['page']) ? array('query' => array('page' => $_GET['page'])) : array()));
       
   416   }
       
   417 
       
   418   return $form;
       
   419 }
       
   420 
       
   421 /**
       
   422  * Submit handler for terms overview form.
       
   423  *
       
   424  * Rather than using a textfield or weight field, this form depends entirely
       
   425  * upon the order of form elements on the page to determine new weights.
       
   426  *
       
   427  * Because there might be hundreds or thousands of taxonomy terms that need to
       
   428  * be ordered, terms are weighted from 0 to the number of terms in the
       
   429  * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
       
   430  * lowest to highest, but are not necessarily sequential. Numbers may be skipped
       
   431  * when a term has children so that reordering is minimal when a child is
       
   432  * added or removed from a term.
       
   433  *
       
   434  * @see taxonomy_overview_terms()
       
   435  */
       
   436 function taxonomy_overview_terms_submit($form, &$form_state) {
       
   437   if ($form_state['triggering_element']['#value'] == t('Reset to alphabetical')) {
       
   438     // Execute the reset action.
       
   439     if ($form_state['values']['reset_alphabetical'] === TRUE) {
       
   440       return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
       
   441     }
       
   442     // Rebuild the form to confirm the reset action.
       
   443     $form_state['rebuild'] = TRUE;
       
   444     $form_state['confirm_reset_alphabetical'] = TRUE;
       
   445     return;
       
   446   }
       
   447 
       
   448   // Sort term order based on weight.
       
   449   uasort($form_state['values'], 'drupal_sort_weight');
       
   450 
       
   451   $vocabulary = $form['#vocabulary'];
       
   452   $hierarchy = 0; // Update the current hierarchy type as we go.
       
   453 
       
   454   $changed_terms = array();
       
   455   $tree = taxonomy_get_tree($vocabulary->vid);
       
   456 
       
   457   if (empty($tree)) {
       
   458     return;
       
   459   }
       
   460 
       
   461   // Build a list of all terms that need to be updated on previous pages.
       
   462   $weight = 0;
       
   463   $term = (array) $tree[0];
       
   464   while ($term['tid'] != $form['#first_tid']) {
       
   465     if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
       
   466       $term['parent'] = $term['parents'][0];
       
   467       $term['weight'] = $weight;
       
   468       $changed_terms[$term['tid']] = $term;
       
   469     }
       
   470     $weight++;
       
   471     $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
       
   472     $term = (array) $tree[$weight];
       
   473   }
       
   474 
       
   475   // Renumber the current page weights and assign any new parents.
       
   476   $level_weights = array();
       
   477   foreach ($form_state['values'] as $tid => $values) {
       
   478     if (isset($form[$tid]['#term'])) {
       
   479       $term = $form[$tid]['#term'];
       
   480       // Give terms at the root level a weight in sequence with terms on previous pages.
       
   481       if ($values['parent'] == 0 && $term['weight'] != $weight) {
       
   482         $term['weight'] = $weight;
       
   483         $changed_terms[$term['tid']] = $term;
       
   484       }
       
   485       // Terms not at the root level can safely start from 0 because they're all on this page.
       
   486       elseif ($values['parent'] > 0) {
       
   487         $level_weights[$values['parent']] = isset($level_weights[$values['parent']]) ? $level_weights[$values['parent']] + 1 : 0;
       
   488         if ($level_weights[$values['parent']] != $term['weight']) {
       
   489           $term['weight'] = $level_weights[$values['parent']];
       
   490           $changed_terms[$term['tid']] = $term;
       
   491         }
       
   492       }
       
   493       // Update any changed parents.
       
   494       if ($values['parent'] != $term['parent']) {
       
   495         $term['parent'] = $values['parent'];
       
   496         $changed_terms[$term['tid']] = $term;
       
   497       }
       
   498       $hierarchy = $term['parent'] != 0 ? 1 : $hierarchy;
       
   499       $weight++;
       
   500     }
       
   501   }
       
   502 
       
   503   // Build a list of all terms that need to be updated on following pages.
       
   504   for ($weight; $weight < count($tree); $weight++) {
       
   505     $term = (array) $tree[$weight];
       
   506     if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
       
   507       $term['parent'] = $term['parents'][0];
       
   508       $term['weight'] = $weight;
       
   509       $changed_terms[$term['tid']] = $term;
       
   510     }
       
   511     $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
       
   512   }
       
   513 
       
   514   // Save all updated terms.
       
   515   foreach ($changed_terms as $changed) {
       
   516     $term = (object) $changed;
       
   517     // Update term_hierachy and term_data directly since we don't have a
       
   518     // fully populated term object to save.
       
   519     db_update('taxonomy_term_hierarchy')
       
   520       ->fields(array('parent' => $term->parent))
       
   521       ->condition('tid', $term->tid, '=')
       
   522       ->execute();
       
   523 
       
   524     db_update('taxonomy_term_data')
       
   525       ->fields(array('weight' => $term->weight))
       
   526       ->condition('tid', $term->tid, '=')
       
   527       ->execute();
       
   528   }
       
   529 
       
   530   // Update the vocabulary hierarchy to flat or single hierarchy.
       
   531   if ($vocabulary->hierarchy != $hierarchy) {
       
   532     $vocabulary->hierarchy = $hierarchy;
       
   533     taxonomy_vocabulary_save($vocabulary);
       
   534   }
       
   535   drupal_set_message(t('The configuration options have been saved.'));
       
   536 }
       
   537 
       
   538 /**
       
   539  * Returns HTML for a terms overview form as a sortable list of terms.
       
   540  *
       
   541  * @param $variables
       
   542  *   An associative array containing:
       
   543  *   - form: A render element representing the form.
       
   544  *
       
   545  * @see taxonomy_overview_terms()
       
   546  * @ingroup themeable
       
   547  */
       
   548 function theme_taxonomy_overview_terms($variables) {
       
   549   $form = $variables['form'];
       
   550 
       
   551   $page_increment  = $form['#page_increment'];
       
   552   $page_entries    = $form['#page_entries'];
       
   553   $back_step     = $form['#back_step'];
       
   554   $forward_step  = $form['#forward_step'];
       
   555 
       
   556   // Add drag and drop if parent fields are present in the form.
       
   557   if ($form['#parent_fields']) {
       
   558     drupal_add_tabledrag('taxonomy', 'match', 'parent', 'term-parent', 'term-parent', 'term-id', FALSE);
       
   559     drupal_add_tabledrag('taxonomy', 'depth', 'group', 'term-depth', NULL, NULL, FALSE);
       
   560     drupal_add_js(drupal_get_path('module', 'taxonomy') . '/taxonomy.js');
       
   561     drupal_add_js(array('taxonomy' => array('backStep' => $back_step, 'forwardStep' => $forward_step)), 'setting');
       
   562     drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
       
   563   }
       
   564   drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'term-weight');
       
   565 
       
   566   $errors = form_get_errors() != FALSE ? form_get_errors() : array();
       
   567   $rows = array();
       
   568   foreach (element_children($form) as $key) {
       
   569     if (isset($form[$key]['#term'])) {
       
   570       $term = &$form[$key];
       
   571 
       
   572       $row = array();
       
   573       $row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', array('size' => $term['#term']['depth'])) : ''). drupal_render($term['view']);
       
   574       if ($form['#parent_fields']) {
       
   575         $term['tid']['#attributes']['class'] = array('term-id');
       
   576         $term['parent']['#attributes']['class'] = array('term-parent');
       
   577         $term['depth']['#attributes']['class'] = array('term-depth');
       
   578         $row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']);
       
   579       }
       
   580       $term['weight']['#attributes']['class'] = array('term-weight');
       
   581       $row[] = drupal_render($term['weight']);
       
   582       $row[] = drupal_render($term['edit']);
       
   583       $row = array('data' => $row);
       
   584       $rows[$key] = $row;
       
   585     }
       
   586   }
       
   587 
       
   588   // Add necessary classes to rows.
       
   589   $row_position = 0;
       
   590   foreach ($rows as $key => $row) {
       
   591     $rows[$key]['class'] = array();
       
   592     if (isset($form['#parent_fields'])) {
       
   593       $rows[$key]['class'][] = 'draggable';
       
   594     }
       
   595 
       
   596     // Add classes that mark which terms belong to previous and next pages.
       
   597     if ($row_position < $back_step || $row_position >= $page_entries - $forward_step) {
       
   598       $rows[$key]['class'][] = 'taxonomy-term-preview';
       
   599     }
       
   600 
       
   601     if ($row_position !== 0 && $row_position !== count($rows) - 1) {
       
   602       if ($row_position == $back_step - 1 || $row_position == $page_entries - $forward_step - 1) {
       
   603         $rows[$key]['class'][] = 'taxonomy-term-divider-top';
       
   604       }
       
   605       elseif ($row_position == $back_step || $row_position == $page_entries - $forward_step) {
       
   606         $rows[$key]['class'][] = 'taxonomy-term-divider-bottom';
       
   607       }
       
   608     }
       
   609 
       
   610     // Add an error class if this row contains a form error.
       
   611     foreach ($errors as $error_key => $error) {
       
   612       if (strpos($error_key, $key) === 0) {
       
   613         $rows[$key]['class'][] = 'error';
       
   614       }
       
   615     }
       
   616     $row_position++;
       
   617   }
       
   618 
       
   619   if (empty($rows)) {
       
   620     $rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '3'));
       
   621   }
       
   622 
       
   623   $header = array(t('Name'), t('Weight'), t('Operations'));
       
   624   $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'taxonomy')));
       
   625   $output .= drupal_render_children($form);
       
   626   $output .= theme('pager');
       
   627 
       
   628   return $output;
       
   629 }
       
   630 
       
   631 /**
       
   632  * Form function for the term edit form.
       
   633  *
       
   634  * @ingroup forms
       
   635  * @see taxonomy_form_term_submit()
       
   636  */
       
   637 function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = NULL) {
       
   638   // During initial form build, add the term entity to the form state for use
       
   639   // during form building and processing. During a rebuild, use what is in the
       
   640   // form state.
       
   641   if (!isset($form_state['term'])) {
       
   642     $term = is_object($edit) ? $edit : (object) $edit;
       
   643     if (!isset($vocabulary) && isset($term->vid)) {
       
   644       $vocabulary = taxonomy_vocabulary_load($term->vid);
       
   645     }
       
   646     $defaults = array(
       
   647       'name' => '',
       
   648       'description' => '',
       
   649       'format' => NULL,
       
   650       'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL,
       
   651       'tid' => NULL,
       
   652       'weight' => 0,
       
   653     );
       
   654     foreach ($defaults as $key => $value) {
       
   655       if (!isset($term->$key)) {
       
   656         $term->$key = $value;
       
   657       }
       
   658     }
       
   659     $form_state['term'] = $term;
       
   660   }
       
   661   else {
       
   662     $term = $form_state['term'];
       
   663     if (!isset($vocabulary) && isset($term->vid)) {
       
   664       $vocabulary = taxonomy_vocabulary_load($term->vid);
       
   665     }
       
   666   }
       
   667 
       
   668   $parent = array_keys(taxonomy_get_parents($term->tid));
       
   669   $form['#term'] = (array) $term;
       
   670   $form['#term']['parent'] = $parent;
       
   671   $form['#vocabulary'] = $vocabulary;
       
   672 
       
   673   // Check for confirmation forms.
       
   674   if (isset($form_state['confirm_delete'])) {
       
   675     return array_merge($form, taxonomy_term_confirm_delete($form, $form_state, $term->tid));
       
   676   }
       
   677 
       
   678   $form['name'] = array(
       
   679     '#type' => 'textfield',
       
   680     '#title' => t('Name'),
       
   681     '#default_value' => $term->name,
       
   682     '#maxlength' => 255,
       
   683     '#required' => TRUE,
       
   684     '#weight' => -5,
       
   685   );
       
   686   $form['description'] = array(
       
   687     '#type' => 'text_format',
       
   688     '#title' => t('Description'),
       
   689     '#default_value' => $term->description,
       
   690     '#format' => $term->format,
       
   691     '#weight' => 0,
       
   692   );
       
   693 
       
   694   $form['vocabulary_machine_name'] = array(
       
   695     '#type' => 'value',
       
   696     '#value' => isset($term->vocabulary_machine_name) ? $term->vocabulary_machine_name : $vocabulary->name,
       
   697   );
       
   698 
       
   699   $langcode = entity_language('taxonomy_term', $term);
       
   700   field_attach_form('taxonomy_term', $term, $form, $form_state, $langcode);
       
   701 
       
   702   $form['relations'] = array(
       
   703     '#type' => 'fieldset',
       
   704     '#title' => t('Relations'),
       
   705     '#collapsible' => TRUE,
       
   706     '#collapsed' => $vocabulary->hierarchy < 2,
       
   707     '#weight' => 10,
       
   708   );
       
   709 
       
   710   // taxonomy_get_tree and taxonomy_get_parents may contain large numbers of
       
   711   // items so we check for taxonomy_override_selector before loading the
       
   712   // full vocabulary. Contrib modules can then intercept before
       
   713   // hook_form_alter to provide scalable alternatives.
       
   714   if (!variable_get('taxonomy_override_selector', FALSE)) {
       
   715     $parent = array_keys(taxonomy_get_parents($term->tid));
       
   716     $children = taxonomy_get_tree($vocabulary->vid, $term->tid);
       
   717 
       
   718     // A term can't be the child of itself, nor of its children.
       
   719     foreach ($children as $child) {
       
   720       $exclude[] = $child->tid;
       
   721     }
       
   722     $exclude[] = $term->tid;
       
   723 
       
   724     $tree = taxonomy_get_tree($vocabulary->vid);
       
   725     $options = array('<' . t('root') . '>');
       
   726     if (empty($parent)) {
       
   727       $parent = array(0);
       
   728     }
       
   729     foreach ($tree as $item) {
       
   730       if (!in_array($item->tid, $exclude)) {
       
   731         $options[$item->tid] = str_repeat('-', $item->depth) . $item->name;
       
   732       }
       
   733     }
       
   734     $form['relations']['parent'] = array(
       
   735       '#type' => 'select',
       
   736       '#title' => t('Parent terms'),
       
   737       '#options' => $options,
       
   738       '#default_value' => $parent,
       
   739       '#multiple' => TRUE,
       
   740     );
       
   741 
       
   742   }
       
   743   $form['relations']['weight'] = array(
       
   744     '#type' => 'textfield',
       
   745     '#title' => t('Weight'),
       
   746     '#size' => 6,
       
   747     '#default_value' => $term->weight,
       
   748     '#description' => t('Terms are displayed in ascending order by weight.'),
       
   749     '#required' => TRUE,
       
   750   );
       
   751   $form['vid'] = array(
       
   752     '#type' => 'value',
       
   753     '#value' => $vocabulary->vid,
       
   754   );
       
   755   $form['tid'] = array(
       
   756     '#type' => 'value',
       
   757     '#value' => $term->tid,
       
   758   );
       
   759 
       
   760   $form['actions'] = array('#type' => 'actions');
       
   761   $form['actions']['submit'] = array(
       
   762     '#type' => 'submit',
       
   763     '#value' => t('Save'),
       
   764     '#weight' => 5,
       
   765   );
       
   766 
       
   767   if ($term->tid) {
       
   768     $form['actions']['delete'] = array(
       
   769       '#type' => 'submit',
       
   770       '#value' => t('Delete'),
       
   771       '#access' => user_access("delete terms in $vocabulary->vid") || user_access('administer taxonomy'),
       
   772       '#weight' => 10,
       
   773     );
       
   774   }
       
   775   else {
       
   776     $form_state['redirect'] = $_GET['q'];
       
   777   }
       
   778 
       
   779   return $form;
       
   780 }
       
   781 
       
   782 /**
       
   783  * Validation handler for the term form.
       
   784  *
       
   785  * @see taxonomy_form_term()
       
   786  */
       
   787 function taxonomy_form_term_validate($form, &$form_state) {
       
   788   entity_form_field_validate('taxonomy_term', $form, $form_state);
       
   789 
       
   790   // Ensure numeric values.
       
   791   if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
       
   792     form_set_error('weight', t('Weight value must be numeric.'));
       
   793   }
       
   794 }
       
   795 
       
   796 /**
       
   797  * Submit handler to insert or update a term.
       
   798  *
       
   799  * @see taxonomy_form_term()
       
   800  */
       
   801 function taxonomy_form_term_submit($form, &$form_state) {
       
   802   if ($form_state['triggering_element']['#value'] == t('Delete')) {
       
   803     // Execute the term deletion.
       
   804     if ($form_state['values']['delete'] === TRUE) {
       
   805       return taxonomy_term_confirm_delete_submit($form, $form_state);
       
   806     }
       
   807     // Rebuild the form to confirm term deletion.
       
   808     $form_state['rebuild'] = TRUE;
       
   809     $form_state['confirm_delete'] = TRUE;
       
   810     return;
       
   811   }
       
   812 
       
   813   $term = taxonomy_form_term_submit_build_taxonomy_term($form, $form_state);
       
   814 
       
   815   $status = taxonomy_term_save($term);
       
   816   switch ($status) {
       
   817     case SAVED_NEW:
       
   818       drupal_set_message(t('Created new term %term.', array('%term' => $term->name)));
       
   819       watchdog('taxonomy', 'Created new term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
       
   820       break;
       
   821     case SAVED_UPDATED:
       
   822       drupal_set_message(t('Updated term %term.', array('%term' => $term->name)));
       
   823       watchdog('taxonomy', 'Updated term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
       
   824       // Clear the page and block caches to avoid stale data.
       
   825       cache_clear_all();
       
   826       break;
       
   827   }
       
   828 
       
   829   $current_parent_count = count($form_state['values']['parent']);
       
   830   $previous_parent_count = count($form['#term']['parent']);
       
   831   // Root doesn't count if it's the only parent.
       
   832   if ($current_parent_count == 1 && isset($form_state['values']['parent'][0])) {
       
   833     $current_parent_count = 0;
       
   834     $form_state['values']['parent'] = array();
       
   835   }
       
   836 
       
   837   // If the number of parents has been reduced to one or none, do a check on the
       
   838   // parents of every term in the vocabulary value.
       
   839   if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
       
   840     taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
       
   841   }
       
   842   // If we've increased the number of parents and this is a single or flat
       
   843   // hierarchy, update the vocabulary immediately.
       
   844   elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']->hierarchy < 2) {
       
   845     $form['#vocabulary']->hierarchy = $current_parent_count == 1 ? 1 : 2;
       
   846     taxonomy_vocabulary_save($form['#vocabulary']);
       
   847   }
       
   848 
       
   849   $form_state['values']['tid'] = $term->tid;
       
   850   $form_state['tid'] = $term->tid;
       
   851 }
       
   852 
       
   853 /**
       
   854  * Updates the form state's term entity by processing this submission's values.
       
   855  */
       
   856 function taxonomy_form_term_submit_build_taxonomy_term($form, &$form_state) {
       
   857   $term = $form_state['term'];
       
   858   entity_form_submit_build_entity('taxonomy_term', $term, $form, $form_state);
       
   859 
       
   860   // Convert text_format field into values expected by taxonomy_term_save().
       
   861   $description = $form_state['values']['description'];
       
   862   $term->description = $description['value'];
       
   863   $term->format = $description['format'];
       
   864   return $term;
       
   865 }
       
   866 
       
   867 /**
       
   868  * Form builder for the term delete form.
       
   869  *
       
   870  * @ingroup forms
       
   871  * @see taxonomy_term_confirm_delete_submit()
       
   872  */
       
   873 function taxonomy_term_confirm_delete($form, &$form_state, $tid) {
       
   874   $term = taxonomy_term_load($tid);
       
   875 
       
   876   // Always provide entity id in the same form key as in the entity edit form.
       
   877   $form['tid'] = array('#type' => 'value', '#value' => $tid);
       
   878 
       
   879   $form['#term'] = $term;
       
   880   $form['type'] = array('#type' => 'value', '#value' => 'term');
       
   881   $form['name'] = array('#type' => 'value', '#value' => $term->name);
       
   882   $form['vocabulary_machine_name'] = array('#type' => 'value', '#value' => $term->vocabulary_machine_name);
       
   883   $form['delete'] = array('#type' => 'value', '#value' => TRUE);
       
   884   return confirm_form($form,
       
   885     t('Are you sure you want to delete the term %title?',
       
   886     array('%title' => $term->name)),
       
   887     'admin/structure/taxonomy',
       
   888     t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
       
   889     t('Delete'),
       
   890     t('Cancel'));
       
   891 }
       
   892 
       
   893 /**
       
   894  * Submit handler to delete a term after confirmation.
       
   895  *
       
   896  * @see taxonomy_term_confirm_delete()
       
   897  */
       
   898 function taxonomy_term_confirm_delete_submit($form, &$form_state) {
       
   899   taxonomy_term_delete($form_state['values']['tid']);
       
   900   taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
       
   901   drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name'])));
       
   902   watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
       
   903   $form_state['redirect'] = 'admin/structure/taxonomy';
       
   904   cache_clear_all();
       
   905   return;
       
   906 }
       
   907 
       
   908 /**
       
   909  * Form builder for the vocabulary delete confirmation form.
       
   910  *
       
   911  * @ingroup forms
       
   912  * @see taxonomy_vocabulary_confirm_delete_submit()
       
   913  */
       
   914 function taxonomy_vocabulary_confirm_delete($form, &$form_state, $vid) {
       
   915   $vocabulary = taxonomy_vocabulary_load($vid);
       
   916 
       
   917   // Always provide entity id in the same form key as in the entity edit form.
       
   918   $form['vid'] = array('#type' => 'value', '#value' => $vid);
       
   919 
       
   920   $form['#vocabulary'] = $vocabulary;
       
   921   $form['#id'] = 'taxonomy_vocabulary_confirm_delete';
       
   922   $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
       
   923   $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
       
   924   $form['#submit'] = array('taxonomy_vocabulary_confirm_delete_submit');
       
   925   return confirm_form($form,
       
   926     t('Are you sure you want to delete the vocabulary %title?',
       
   927     array('%title' => $vocabulary->name)),
       
   928     'admin/structure/taxonomy',
       
   929     t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
       
   930     t('Delete'),
       
   931     t('Cancel'));
       
   932 }
       
   933 
       
   934 /**
       
   935  * Submit handler to delete a vocabulary after confirmation.
       
   936  *
       
   937  * @see taxonomy_vocabulary_confirm_delete()
       
   938  */
       
   939 function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
       
   940   $status = taxonomy_vocabulary_delete($form_state['values']['vid']);
       
   941   drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
       
   942   watchdog('taxonomy', 'Deleted vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
       
   943   $form_state['redirect'] = 'admin/structure/taxonomy';
       
   944   cache_clear_all();
       
   945   return;
       
   946 }
       
   947 
       
   948 /**
       
   949  * Form builder to confirm resetting a vocabulary to alphabetical order.
       
   950  *
       
   951  * @ingroup forms
       
   952  * @see taxonomy_vocabulary_confirm_reset_alphabetical_submit()
       
   953  */
       
   954 function taxonomy_vocabulary_confirm_reset_alphabetical($form, &$form_state, $vid) {
       
   955   $vocabulary = taxonomy_vocabulary_load($vid);
       
   956 
       
   957   $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
       
   958   $form['vid'] = array('#type' => 'value', '#value' => $vid);
       
   959   $form['machine_name'] = array('#type' => 'value', '#value' => $vocabulary->machine_name);
       
   960   $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
       
   961   $form['reset_alphabetical'] = array('#type' => 'value', '#value' => TRUE);
       
   962   return confirm_form($form,
       
   963                   t('Are you sure you want to reset the vocabulary %title to alphabetical order?',
       
   964                   array('%title' => $vocabulary->name)),
       
   965                   'admin/structure/taxonomy/' . $vocabulary->machine_name,
       
   966                   t('Resetting a vocabulary will discard all custom ordering and sort items alphabetically.'),
       
   967                   t('Reset to alphabetical'),
       
   968                   t('Cancel'));
       
   969 }
       
   970 
       
   971 /**
       
   972  * Submit handler to reset a vocabulary to alphabetical order after confirmation.
       
   973  *
       
   974  * @see taxonomy_vocabulary_confirm_reset_alphabetical()
       
   975  */
       
   976 function taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, &$form_state) {
       
   977   db_update('taxonomy_term_data')
       
   978     ->fields(array('weight' => 0))
       
   979     ->condition('vid', $form_state['values']['vid'])
       
   980     ->execute();
       
   981   drupal_set_message(t('Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name'])));
       
   982   watchdog('taxonomy', 'Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
       
   983   $form_state['redirect'] = 'admin/structure/taxonomy/' . $form_state['values']['machine_name'];
       
   984 }