web/drupal/modules/taxonomy/taxonomy.admin.inc
branchdrupal
changeset 74 0ff3ba646492
equal deleted inserted replaced
73:fcf75e232c5b 74:0ff3ba646492
       
     1 <?php
       
     2 // $Id: taxonomy.admin.inc,v 1.22.2.3 2009/02/25 12:53:24 goba Exp $
       
     3 
       
     4 /**
       
     5  * @file
       
     6  * Administrative page callbacks for the taxonomy module.
       
     7  */
       
     8 
       
     9 /**
       
    10  * Form builder to list and manage vocabularies.
       
    11  *
       
    12  * @ingroup forms
       
    13  * @see taxonomy_overview_vocabularies_submit()
       
    14  * @see theme_taxonomy_overview_vocabularies()
       
    15  */
       
    16 function taxonomy_overview_vocabularies() {
       
    17   $vocabularies = taxonomy_get_vocabularies();
       
    18   $form = array('#tree' => TRUE);
       
    19   foreach ($vocabularies as $vocabulary) {
       
    20     $types = array();
       
    21     foreach ($vocabulary->nodes as $type) {
       
    22       $node_type = node_get_types('name', $type);
       
    23       $types[] = $node_type ? check_plain($node_type) : check_plain($type);
       
    24     }
       
    25     $form[$vocabulary->vid]['#vocabulary'] = (array)$vocabulary;
       
    26     $form[$vocabulary->vid]['name'] = array('#value' => check_plain($vocabulary->name));
       
    27     $form[$vocabulary->vid]['types'] = array('#value' => implode(', ', $types));
       
    28     $form[$vocabulary->vid]['weight'] = array('#type' => 'weight', '#delta' => 10, '#default_value' => $vocabulary->weight);
       
    29     $form[$vocabulary->vid]['edit'] = array('#value' => l(t('edit vocabulary'), "admin/content/taxonomy/edit/vocabulary/$vocabulary->vid"));
       
    30     $form[$vocabulary->vid]['list'] = array('#value' => l(t('list terms'), "admin/content/taxonomy/$vocabulary->vid"));
       
    31     $form[$vocabulary->vid]['add'] = array('#value' => l(t('add terms'), "admin/content/taxonomy/$vocabulary->vid/add/term"));
       
    32   }
       
    33 
       
    34   // Only make this form include a submit button and weight if more than one
       
    35   // vocabulary exists.
       
    36   if (count($vocabularies) > 1) {
       
    37     $form['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_save_vocabulary($form[$vid]['#vocabulary']);
       
    55     }
       
    56   }
       
    57 }
       
    58 
       
    59 /**
       
    60  * Theme the vocabulary overview as a sortable list of vocabularies.
       
    61  *
       
    62  * @ingroup themeable
       
    63  * @see taxonomy_overview_vocabularies()
       
    64  */
       
    65 function theme_taxonomy_overview_vocabularies($form) {
       
    66   $rows = array();
       
    67   foreach (element_children($form) as $key) {
       
    68     if (isset($form[$key]['name'])) {
       
    69       $vocabulary = &$form[$key];
       
    70 
       
    71       $row = array();
       
    72       $row[] = drupal_render($vocabulary['name']);
       
    73       $row[] = drupal_render($vocabulary['types']);
       
    74       if (isset($vocabulary['weight'])) {
       
    75         $vocabulary['weight']['#attributes']['class'] = 'vocabulary-weight';
       
    76         $row[] = drupal_render($vocabulary['weight']);
       
    77       }
       
    78       $row[] = drupal_render($vocabulary['edit']);
       
    79       $row[] = drupal_render($vocabulary['list']);
       
    80       $row[] = drupal_render($vocabulary['add']);
       
    81       $rows[] = array('data' => $row, 'class' => 'draggable');
       
    82     }
       
    83   }
       
    84   if (empty($rows)) {
       
    85     $rows[] = array(array('data' => t('No vocabularies available.'), 'colspan' => '5'));
       
    86   }
       
    87 
       
    88   $header = array(t('Name'), t('Type'));
       
    89   if (isset($form['submit'])) {
       
    90     $header[] = t('Weight');
       
    91     drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'vocabulary-weight');
       
    92   }
       
    93   $header[] = array('data' => t('Operations'), 'colspan' => '3');
       
    94   return theme('table', $header, $rows, array('id' => 'taxonomy')) . drupal_render($form);
       
    95 }
       
    96 
       
    97 /**
       
    98  * Display form for adding and editing vocabularies.
       
    99  *
       
   100  * @ingroup forms
       
   101  * @see taxonomy_form_vocabulary_submit()
       
   102  */
       
   103 function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
       
   104   $edit += array(
       
   105     'name' => '',
       
   106     'description' => '',
       
   107     'help' => '',
       
   108     'nodes' => array(),
       
   109     'hierarchy' => 0,
       
   110     'relations' => 0,
       
   111     'tags' => 0,
       
   112     'multiple' => 0,
       
   113     'required' => 0,
       
   114     'weight' => 0,
       
   115   );
       
   116   $form['identification'] = array(
       
   117     '#type' => 'fieldset',
       
   118     '#title' => t('Identification'),
       
   119     '#collapsible' => TRUE,
       
   120   );
       
   121   $form['identification']['name'] = array('#type' => 'textfield',
       
   122     '#title' => t('Vocabulary name'),
       
   123     '#default_value' => $edit['name'],
       
   124     '#maxlength' => 255,
       
   125     '#description' => t('The name for this vocabulary, e.g., <em>"Tags"</em>.'),
       
   126     '#required' => TRUE,
       
   127   );
       
   128   $form['identification']['description'] = array('#type' => 'textarea',
       
   129     '#title' => t('Description'),
       
   130     '#default_value' => $edit['description'],
       
   131     '#description' => t('Description of the vocabulary; can be used by modules.'),
       
   132   );
       
   133   $form['identification']['help'] = array('#type' => 'textfield',
       
   134     '#title' => t('Help text'),
       
   135     '#maxlength' => 255,
       
   136     '#default_value' => $edit['help'],
       
   137     '#description' => t('Instructions to present to the user when selecting terms, e.g., <em>"Enter a comma separated list of words"</em>.'),
       
   138   );
       
   139   $form['content_types'] = array(
       
   140     '#type' => 'fieldset',
       
   141     '#title' => t('Content types'),
       
   142     '#collapsible' => TRUE,
       
   143   );
       
   144   $form['content_types']['nodes'] = array('#type' => 'checkboxes',
       
   145     '#title' => t('Content types'),
       
   146     '#default_value' => $edit['nodes'],
       
   147     '#options' => array_map('check_plain', node_get_types('names')),
       
   148     '#description' => t('Select content types to categorize using this vocabulary.'),
       
   149   );
       
   150   $form['settings'] = array(
       
   151     '#type' => 'fieldset',
       
   152     '#title' => t('Settings'),
       
   153     '#collapsible' => TRUE,
       
   154   );
       
   155   $form['settings']['tags'] = array('#type' => 'checkbox',
       
   156     '#title' => t('Tags'),
       
   157     '#default_value' => $edit['tags'],
       
   158     '#description' => t('Terms are created by users when submitting posts by typing a comma separated list.'),
       
   159   );
       
   160   $form['settings']['multiple'] = array('#type' => 'checkbox',
       
   161     '#title' => t('Multiple select'),
       
   162     '#default_value' => $edit['multiple'],
       
   163     '#description' => t('Allows posts to have more than one term from this vocabulary (always true for tags).'),
       
   164   );
       
   165   $form['settings']['required'] = array('#type' => 'checkbox',
       
   166     '#title' => t('Required'),
       
   167     '#default_value' => $edit['required'],
       
   168     '#description' => t('At least one term in this vocabulary must be selected when submitting a post.'),
       
   169   );
       
   170   $form['settings']['weight'] = array('#type' => 'weight',
       
   171     '#title' => t('Weight'),
       
   172     '#default_value' => $edit['weight'],
       
   173     '#description' => t('Vocabularies are displayed in ascending order by weight.'),
       
   174   );
       
   175   // Set the hierarchy to "multiple parents" by default. This simplifies the
       
   176   // vocabulary form and standardizes the term form.
       
   177   $form['hierarchy'] = array('#type' => 'value',
       
   178     '#value' => '0',
       
   179   );
       
   180   // Enable "related terms" by default.
       
   181   $form['relations'] = array('#type' => 'value',
       
   182     '#value' => '1',
       
   183   );
       
   184 
       
   185   $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
       
   186   if (isset($edit['vid'])) {
       
   187     $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
       
   188     $form['vid'] = array('#type' => 'value', '#value' => $edit['vid']);
       
   189     $form['module'] = array('#type' => 'value', '#value' => $edit['module']);
       
   190   }
       
   191   return $form;
       
   192 }
       
   193 
       
   194 /**
       
   195  * Accept the form submission for a vocabulary and save the results.
       
   196  */
       
   197 function taxonomy_form_vocabulary_submit($form, &$form_state) {
       
   198   // Fix up the nodes array to remove unchecked nodes.
       
   199   $form_state['values']['nodes'] = array_filter($form_state['values']['nodes']);
       
   200   switch (taxonomy_save_vocabulary($form_state['values'])) {
       
   201     case SAVED_NEW:
       
   202       drupal_set_message(t('Created new vocabulary %name.', array('%name' => $form_state['values']['name'])));
       
   203       watchdog('taxonomy', 'Created new vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/vocabulary/'. $form_state['values']['vid']));
       
   204       break;
       
   205     case SAVED_UPDATED:
       
   206       drupal_set_message(t('Updated vocabulary %name.', array('%name' => $form_state['values']['name'])));
       
   207       watchdog('taxonomy', 'Updated vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/vocabulary/'. $form_state['values']['vid']));
       
   208       break;
       
   209   }
       
   210 
       
   211   $form_state['vid'] = $form_state['values']['vid'];
       
   212   $form_state['redirect'] = 'admin/content/taxonomy';
       
   213   return;
       
   214 }
       
   215 
       
   216 /**
       
   217  * Page to edit a vocabulary.
       
   218  */
       
   219 function taxonomy_admin_vocabulary_edit($vocabulary) {
       
   220   if ((isset($_POST['op']) && $_POST['op'] == t('Delete')) || isset($_POST['confirm'])) {
       
   221     return drupal_get_form('taxonomy_vocabulary_confirm_delete', $vocabulary->vid);
       
   222   }
       
   223   return drupal_get_form('taxonomy_form_vocabulary', (array)$vocabulary);
       
   224 }
       
   225 
       
   226 /**
       
   227  * Page to edit a vocabulary term.
       
   228  */
       
   229 function taxonomy_admin_term_edit($tid) {
       
   230   if ($term = (array)taxonomy_get_term($tid)) {
       
   231     return drupal_get_form('taxonomy_form_term', taxonomy_vocabulary_load($term['vid']), $term);
       
   232   }
       
   233   return drupal_not_found();
       
   234 }
       
   235 
       
   236 /**
       
   237  * Form builder for the taxonomy terms overview.
       
   238  *
       
   239  * Display a tree of all the terms in a vocabulary, with options to edit
       
   240  * each one. The form is made drag and drop by the theme function.
       
   241  *
       
   242  * @ingroup forms
       
   243  * @see taxonomy_overview_terms_submit()
       
   244  * @see theme_taxonomy_overview_terms()
       
   245  */
       
   246 function taxonomy_overview_terms(&$form_state, $vocabulary) {
       
   247   global $pager_page_array, $pager_total, $pager_total_items;
       
   248 
       
   249   // Check for confirmation forms.
       
   250   if (isset($form_state['confirm_reset_alphabetical'])) {
       
   251     return taxonomy_vocabulary_confirm_reset_alphabetical($form_state, $vocabulary->vid);
       
   252   }
       
   253 
       
   254   drupal_set_title(t('Terms in %vocabulary', array('%vocabulary' => $vocabulary->name)));
       
   255   $form = array(
       
   256     '#vocabulary' => (array)$vocabulary,
       
   257     '#tree' => TRUE,
       
   258     '#parent_fields' => FALSE,
       
   259   );
       
   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_peddle    = NULL;
       
   272   $forward_peddle = 0;
       
   273 
       
   274   // An array of the terms to be displayed on this page.
       
   275   $current_page = array();
       
   276 
       
   277   // Case for free tagging.
       
   278   if ($vocabulary->tags) {
       
   279     // We are not calling taxonomy_get_tree because that might fail with a big
       
   280     // number of tags in the freetagging vocabulary.
       
   281     $results = pager_query(db_rewrite_sql('SELECT t.*, h.parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $page_increment, 0, NULL, $vocabulary->vid);
       
   282     $total_entries = db_query(db_rewrite_sql('SELECT count(*) FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d', 't', 'tid'), $page_increment, 0, NULL, $vocabulary->vid);
       
   283     while ($term = db_fetch_object($results)) {
       
   284       $key = 'tid:'. $term->tid .':0';
       
   285       $current_page[$key] = $term;
       
   286       $page_entries++;
       
   287     }
       
   288   }
       
   289   // Case for restricted vocabulary.
       
   290   else {
       
   291     $term_deltas = array();
       
   292     $tree = taxonomy_get_tree($vocabulary->vid);
       
   293     $term = current($tree);
       
   294     do {
       
   295       // In case this tree is completely empty.
       
   296       if (empty($term)) {
       
   297         break;
       
   298       }
       
   299       // Count entries before the current page.
       
   300       if ($page && ($page * $page_increment) > $before_entries && !isset($back_peddle)) {
       
   301         $before_entries++;
       
   302         continue;
       
   303       }
       
   304       // Count entries after the current page.
       
   305       elseif ($page_entries > $page_increment && isset($complete_tree)) {
       
   306         $after_entries++;
       
   307         continue;
       
   308       }
       
   309 
       
   310       // Do not let a term start the page that is not at the root.
       
   311       if (isset($term->depth) && ($term->depth > 0) && !isset($back_peddle)) {
       
   312         $back_peddle = 0;
       
   313         while ($pterm = prev($tree)) {
       
   314           $before_entries--;
       
   315           $back_peddle++;
       
   316           if ($pterm->depth == 0) {
       
   317             prev($tree);
       
   318             continue 2; // Jump back to the start of the root level parent.
       
   319           }
       
   320         }
       
   321       }
       
   322       $back_peddle = isset($back_peddle) ? $back_peddle : 0;
       
   323 
       
   324       // Continue rendering the tree until we reach the a new root item.
       
   325       if ($page_entries >= $page_increment + $back_peddle + 1 && $term->depth == 0 && $root_entries > 1) {
       
   326         $complete_tree = TRUE;
       
   327         // This new item at the root level is the first item on the next page.
       
   328         $after_entries++;
       
   329         continue;
       
   330       }
       
   331       if ($page_entries >= $page_increment + $back_peddle) {
       
   332         $forward_peddle++;
       
   333       }
       
   334 
       
   335       // Finally, if we've gotten down this far, we're rendering a term on this page.
       
   336       $page_entries++;
       
   337       $term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0;
       
   338       $key = 'tid:'. $term->tid .':'. $term_deltas[$term->tid];
       
   339 
       
   340       // Keep track of the first term displayed on this page.
       
   341       if ($page_entries == 1) {
       
   342         $form['#first_tid'] = $term->tid;
       
   343       }
       
   344       // Keep a variable to make sure at least 2 root elements are displayed.
       
   345       if ($term->parents[0] == 0) {
       
   346         $root_entries++;
       
   347       }
       
   348       $current_page[$key] = $term;
       
   349     } while ($term = next($tree));
       
   350 
       
   351     // Because we didn't use a pager query, set the necessary pager variables.
       
   352     $total_entries = $before_entries + $page_entries + $after_entries;
       
   353     $pager_total_items[0] = $total_entries;
       
   354     $pager_page_array[0] = $page;
       
   355     $pager_total[0] = ceil($total_entries / $page_increment);
       
   356   }
       
   357 
       
   358   // If this form was already submitted once, it's probably hit a validation
       
   359   // error. Ensure the form is rebuilt in the same order as the user submitted.
       
   360   if (!empty($form_state['post'])) {
       
   361     $order = array_flip(array_keys($form_state['post'])); // Get the $_POST order.
       
   362     $current_page = array_merge($order, $current_page); // Update our form with the new order.
       
   363     foreach ($current_page as $key => $term) {
       
   364       // Verify this is a term for the current page and set at the current depth.
       
   365       if (is_array($form_state['post'][$key]) && is_numeric($form_state['post'][$key]['tid'])) {
       
   366         $current_page[$key]->depth = $form_state['post'][$key]['depth'];
       
   367       }
       
   368       else {
       
   369         unset($current_page[$key]);
       
   370       }
       
   371     }
       
   372   }
       
   373 
       
   374   // Build the actual form.
       
   375   foreach ($current_page as $key => $term) {
       
   376     // Save the term for the current page so we don't have to load it a second time.
       
   377     $form[$key]['#term'] = (array)$term;
       
   378     if (isset($term->parents)) {
       
   379       $form[$key]['#term']['parent'] = $term->parent = $term->parents[0];
       
   380       unset($form[$key]['#term']['parents'], $term->parents);
       
   381     }
       
   382 
       
   383     $form[$key]['view'] = array('#value' => l($term->name, "taxonomy/term/$term->tid"));
       
   384     if (!$vocabulary->tags && $vocabulary->hierarchy < 2 && count($tree) > 1) {
       
   385       $form['#parent_fields'] = TRUE;
       
   386       $form[$key]['tid'] = array(
       
   387         '#type' => 'hidden',
       
   388         '#value' => $term->tid
       
   389       );
       
   390       $form[$key]['parent'] = array(
       
   391         '#type' => 'hidden',
       
   392         // Yes, default_value on a hidden. It needs to be changeable by the javascript.
       
   393         '#default_value' => $term->parent,
       
   394       );
       
   395       $form[$key]['depth'] = array(
       
   396         '#type' => 'hidden',
       
   397         // Same as above, the depth is modified by javascript, so it's a default_value.
       
   398         '#default_value' => $term->depth,
       
   399       );
       
   400     }
       
   401     $form[$key]['edit'] = array('#value' => l(t('edit'), "admin/content/taxonomy/edit/term/$term->tid", array('query' => drupal_get_destination())));
       
   402   }
       
   403 
       
   404   $form['#total_entries'] = $total_entries;
       
   405   $form['#page_increment'] = $page_increment;
       
   406   $form['#page_entries'] = $page_entries;
       
   407   $form['#back_peddle'] = $back_peddle;
       
   408   $form['#forward_peddle'] = $forward_peddle;
       
   409   $form['#empty_text'] = t('No terms available.');
       
   410 
       
   411   if (!$vocabulary->tags && $vocabulary->hierarchy < 2 && count($tree) > 1) {
       
   412     $form['submit'] = array(
       
   413       '#type' => 'submit',
       
   414       '#value' => t('Save')
       
   415     );
       
   416     $form['reset_alphabetical'] = array(
       
   417       '#type' => 'submit',
       
   418       '#value' => t('Reset to alphabetical')
       
   419     );
       
   420     $form['destination'] = array(
       
   421       '#type' => 'hidden',
       
   422       '#value' => $_GET['q'] . (isset($_GET['page']) ? '?page='. $_GET['page'] : '')
       
   423     );
       
   424   }
       
   425 
       
   426   return $form;
       
   427 }
       
   428 
       
   429 /**
       
   430  * Submit handler for terms overview form.
       
   431  *
       
   432  * Rather than using a textfield or weight field, this form depends entirely
       
   433  * upon the order of form elements on the page to determine new weights.
       
   434  *
       
   435  * Because there might be hundreds or thousands of taxonomy terms that need to
       
   436  * be ordered, terms are weighted from 0 to the number of terms in the
       
   437  * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
       
   438  * lowest to highest, but are not necessarily sequential. Numbers may be skipped
       
   439  * when a term has children so that reordering is minimal when a child is
       
   440  * added or removed from a term.
       
   441  *
       
   442  * @see taxonomy_overview_terms()
       
   443  */
       
   444 function taxonomy_overview_terms_submit($form, &$form_state) {
       
   445   if ($form_state['clicked_button']['#value'] == t('Reset to alphabetical')) {
       
   446     // Execute the reset action.
       
   447     if ($form_state['values']['reset_alphabetical'] === TRUE) {
       
   448       return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
       
   449     }
       
   450     // Rebuild the form to confirm the reset action.
       
   451     $form_state['rebuild'] = TRUE;
       
   452     $form_state['confirm_reset_alphabetical'] = TRUE;
       
   453     return;
       
   454   }
       
   455 
       
   456   $order = array_flip(array_keys($form['#post'])); // Get the $_POST order.
       
   457   $form_state['values'] = array_merge($order, $form_state['values']); // Update our original form with the new order.
       
   458 
       
   459   $vocabulary = $form['#vocabulary'];
       
   460   $hierarchy = 0; // Update the current hierarchy type as we go.
       
   461 
       
   462   $changed_terms = array();
       
   463   $tree = taxonomy_get_tree($vocabulary['vid']);
       
   464 
       
   465   if (empty($tree)) {
       
   466     return;
       
   467   }
       
   468 
       
   469   // Build a list of all terms that need to be updated on previous pages.
       
   470   $weight = 0;
       
   471   $term = (array)$tree[0];
       
   472   while ($term['tid'] != $form['#first_tid']) {
       
   473     if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
       
   474       $term['parent'] = $term['parents'][0];
       
   475       $term['weight'] = $weight;
       
   476       $changed_terms[$term['tid']] = $term;
       
   477     }
       
   478     $weight++;
       
   479     $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
       
   480     $term = (array)$tree[$weight];
       
   481   }
       
   482 
       
   483   // Renumber the current page weights and assign any new parents.
       
   484   $level_weights = array();
       
   485   foreach ($form_state['values'] as $tid => $values) {
       
   486     if (isset($form[$tid]['#term'])) {
       
   487       $term = $form[$tid]['#term'];
       
   488       // Give terms at the root level a weight in sequence with terms on previous pages.
       
   489       if ($values['parent'] == 0 && $term['weight'] != $weight) {
       
   490         $term['weight'] = $weight;
       
   491         $changed_terms[$term['tid']] = $term;
       
   492       }
       
   493       // Terms not at the root level can safely start from 0 because they're all on this page.
       
   494       elseif ($values['parent'] > 0) {
       
   495         $level_weights[$values['parent']] = isset($level_weights[$values['parent']]) ? $level_weights[$values['parent']] + 1 : 0;
       
   496         if ($level_weights[$values['parent']] != $term['weight']) {
       
   497           $term['weight'] = $level_weights[$values['parent']];
       
   498           $changed_terms[$term['tid']] = $term;
       
   499         }
       
   500       }
       
   501       // Update any changed parents.
       
   502       if ($values['parent'] != $term['parent']) {
       
   503         $term['parent'] = $values['parent'];
       
   504         $changed_terms[$term['tid']] = $term;
       
   505       }
       
   506       $hierarchy = $term['parent'] != 0 ? 1 : $hierarchy;
       
   507       $weight++;
       
   508     }
       
   509   }
       
   510 
       
   511   // Build a list of all terms that need to be updated on following pages.
       
   512   for ($weight; $weight < count($tree); $weight++) {
       
   513     $term = (array)$tree[$weight];
       
   514     if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
       
   515       $term['parent'] = $term['parents'][0];
       
   516       $term['weight'] = $weight;
       
   517       $changed_terms[$term['tid']] = $term;
       
   518     }
       
   519     $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
       
   520   }
       
   521 
       
   522   // Save all updated terms.
       
   523   foreach ($changed_terms as $term) {
       
   524     taxonomy_save_term($term);
       
   525   }
       
   526 
       
   527   // Update the vocabulary hierarchy to flat or single hierarchy.
       
   528   if ($vocabulary['hierarchy'] != $hierarchy) {
       
   529     $vocabulary['hierarchy'] = $hierarchy;
       
   530     taxonomy_save_vocabulary($vocabulary);
       
   531   }
       
   532 }
       
   533 
       
   534 /**
       
   535  * Theme the terms overview as a sortable list of terms.
       
   536  *
       
   537  * @ingroup themeable
       
   538  * @see taxonomy_overview_terms()
       
   539  */
       
   540 function theme_taxonomy_overview_terms($form) {
       
   541   $page_increment  = $form['#page_increment'];
       
   542   $page_entries    = $form['#page_entries'];
       
   543   $back_peddle     = $form['#back_peddle'];
       
   544   $forward_peddle  = $form['#forward_peddle'];
       
   545 
       
   546   // Add drag and drop if parent fields are present in the form.
       
   547   if ($form['#parent_fields']) {
       
   548     drupal_add_tabledrag('taxonomy', 'match', 'parent', 'term-parent', 'term-parent', 'term-id', FALSE);
       
   549     drupal_add_tabledrag('taxonomy', 'depth', 'group', 'term-depth', NULL, NULL, FALSE);
       
   550     drupal_add_js(drupal_get_path('module', 'taxonomy') .'/taxonomy.js');
       
   551     drupal_add_js(array('taxonomy' => array('backPeddle' => $back_peddle, 'forwardPeddle' => $forward_peddle)), 'setting');
       
   552     drupal_add_css(drupal_get_path('module', 'taxonomy') .'/taxonomy.css');
       
   553   }
       
   554 
       
   555   $errors = form_get_errors() != FALSE ? form_get_errors() : array();
       
   556   $rows = array();
       
   557   foreach (element_children($form) as $key) {
       
   558     if (isset($form[$key]['#term'])) {
       
   559       $term = &$form[$key];
       
   560 
       
   561       $row = array();
       
   562       $row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', $term['#term']['depth']) : '') . drupal_render($term['view']);
       
   563       if ($form['#parent_fields']) {
       
   564         $term['tid']['#attributes']['class'] = 'term-id';
       
   565         $term['parent']['#attributes']['class'] = 'term-parent';
       
   566         $term['depth']['#attributes']['class'] = 'term-depth';
       
   567         $row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']);
       
   568       }
       
   569       $row[] = drupal_render($term['edit']);
       
   570 
       
   571       $row = array('data' => $row);
       
   572       $rows[$key] = $row;
       
   573     }
       
   574   }
       
   575 
       
   576   // Add necessary classes to rows.
       
   577   $row_position = 0;
       
   578   foreach ($rows as $key => $row) {
       
   579     $classes = array();
       
   580     if (isset($form['#parent_fields'])) {
       
   581       $classes[] = 'draggable';
       
   582     }
       
   583 
       
   584     // Add classes that mark which terms belong to previous and next pages.
       
   585     if ($row_position < $back_peddle || $row_position >= $page_entries - $forward_peddle) {
       
   586       $classes[] = 'taxonomy-term-preview';
       
   587     }
       
   588 
       
   589     if ($row_position !== 0 && $row_position !== count($rows) - 1) {
       
   590       if ($row_position == $back_peddle - 1 || $row_position == $page_entries - $forward_peddle - 1) {
       
   591         $classes[] = 'taxonomy-term-divider-top';
       
   592       }
       
   593       elseif ($row_position == $back_peddle || $row_position == $page_entries - $forward_peddle) {
       
   594         $classes[] = 'taxonomy-term-divider-bottom';
       
   595       }
       
   596     }
       
   597 
       
   598     // Add an error class if this row contains a form error.
       
   599     foreach ($errors as $error_key => $error) {
       
   600       if (strpos($error_key, $key) === 0) {
       
   601         $classes[] = 'error';
       
   602       }
       
   603     }
       
   604     $rows[$key]['class'] = implode(' ', $classes);
       
   605     $row_position++;
       
   606   }
       
   607 
       
   608   if (empty($rows)) {
       
   609     $rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '2'));
       
   610   }
       
   611 
       
   612   $header = array(t('Name'), t('Operations'));
       
   613   $output = theme('table', $header, $rows, array('id' => 'taxonomy'));
       
   614   $output .= drupal_render($form);
       
   615   $output .= theme('pager', NULL, $page_increment);
       
   616 
       
   617   return $output;
       
   618 }
       
   619 
       
   620 /**
       
   621  * Menu callback; return the edit form for a new term after setting the title.
       
   622  */
       
   623 function taxonomy_add_term_page($vocabulary) {
       
   624   drupal_set_title(t('Add term to %vocabulary', array('%vocabulary' => $vocabulary->name)));
       
   625   return drupal_get_form('taxonomy_form_term' , $vocabulary);
       
   626 }
       
   627 
       
   628 /**
       
   629  * Form function for the term edit form.
       
   630  *
       
   631  * @ingroup forms
       
   632  * @see taxonomy_form_term_submit()
       
   633  */
       
   634 function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
       
   635   $edit += array(
       
   636     'name' => '',
       
   637     'description' => '',
       
   638     'tid' => NULL,
       
   639     'weight' => 0,
       
   640   );
       
   641 
       
   642   $parent = array_keys(taxonomy_get_parents($edit['tid']));
       
   643   $form['#term'] = $edit;
       
   644   $form['#term']['parent'] = $parent;
       
   645   $form['#vocabulary'] = (array)$vocabulary;
       
   646   $form['#vocabulary']['nodes'] = drupal_map_assoc($vocabulary->nodes);;
       
   647 
       
   648   // Check for confirmation forms.
       
   649   if (isset($form_state['confirm_delete'])) {
       
   650     return array_merge($form, taxonomy_term_confirm_delete($form_state, $edit['tid']));
       
   651   }
       
   652   elseif (isset($form_state['confirm_parents'])) {
       
   653     return array_merge($form, taxonomy_term_confirm_parents($form_state, $vocabulary));
       
   654   }
       
   655 
       
   656   $form['identification'] = array(
       
   657     '#type' => 'fieldset',
       
   658     '#title' => t('Identification'),
       
   659     '#collapsible' => TRUE,
       
   660   );
       
   661   $form['identification']['name'] = array(
       
   662     '#type' => 'textfield',
       
   663     '#title' => t('Term name'),
       
   664     '#default_value' => $edit['name'],
       
   665     '#maxlength' => 255,
       
   666     '#description' => t('The name of this term.'),
       
   667     '#required' => TRUE);
       
   668   $form['identification']['description'] = array(
       
   669     '#type' => 'textarea',
       
   670     '#title' => t('Description'),
       
   671     '#default_value' => $edit['description'],
       
   672     '#description' => t('A description of the term. To be displayed on taxonomy/term pages and RSS feeds.'));
       
   673 
       
   674   $form['advanced'] = array(
       
   675     '#type' => 'fieldset',
       
   676     '#title' => t('Advanced options'),
       
   677     '#collapsible' => TRUE,
       
   678     '#collapsed' => $vocabulary->hierarchy > 1 ? FALSE : TRUE,
       
   679   );
       
   680 
       
   681   // taxonomy_get_tree and taxonomy_get_parents may contain large numbers of
       
   682   // items so we check for taxonomy_override_selector before loading the
       
   683   // full vocabulary. Contrib modules can then intercept before
       
   684   // hook_form_alter to provide scalable alternatives.
       
   685   if (!variable_get('taxonomy_override_selector', FALSE)) {
       
   686     $parent = array_keys(taxonomy_get_parents($edit['tid']));
       
   687     $children = taxonomy_get_tree($vocabulary->vid, $edit['tid']);
       
   688 
       
   689     // A term can't be the child of itself, nor of its children.
       
   690     foreach ($children as $child) {
       
   691       $exclude[] = $child->tid;
       
   692     }
       
   693     $exclude[] = $edit['tid'];
       
   694 
       
   695     $form['advanced']['parent'] = _taxonomy_term_select(t('Parents'), 'parent', $parent, $vocabulary->vid, t('Parent terms') .'.', 1, '<'. t('root') .'>', $exclude);
       
   696     $form['advanced']['relations'] = _taxonomy_term_select(t('Related terms'), 'relations', array_keys(taxonomy_get_related($edit['tid'])), $vocabulary->vid, NULL, 1, '<'. t('none') .'>', array($edit['tid']));
       
   697   }
       
   698   $form['advanced']['synonyms'] = array(
       
   699     '#type' => 'textarea',
       
   700     '#title' => t('Synonyms'),
       
   701     '#default_value' => implode("\n", taxonomy_get_synonyms($edit['tid'])),
       
   702     '#description' => t('Synonyms of this term, one synonym per line.'));
       
   703   $form['advanced']['weight'] = array(
       
   704     '#type' => 'textfield',
       
   705     '#title' => t('Weight'),
       
   706     '#size' => 6,
       
   707     '#default_value' => $edit['weight'],
       
   708     '#description' => t('Terms are displayed in ascending order by weight.'),
       
   709     '#required' => TRUE);
       
   710   $form['vid'] = array(
       
   711     '#type' => 'value',
       
   712     '#value' => $vocabulary->vid);
       
   713   $form['submit'] = array(
       
   714     '#type' => 'submit',
       
   715     '#value' => t('Save'));
       
   716 
       
   717   if ($edit['tid']) {
       
   718     $form['delete'] = array(
       
   719       '#type' => 'submit',
       
   720       '#value' => t('Delete'));
       
   721     $form['tid'] = array(
       
   722       '#type' => 'value',
       
   723       '#value' => $edit['tid']);
       
   724   }
       
   725   else {
       
   726     $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
       
   727   }
       
   728 
       
   729   return $form;
       
   730 }
       
   731 
       
   732 /**
       
   733  * Validation handler for the term edit form. Ensure numeric weight values.
       
   734  *
       
   735  * @see taxonomy_form_term()
       
   736  */
       
   737 function taxonomy_form_term_validate($form, &$form_state) {
       
   738   if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
       
   739     form_set_error('weight', t('Weight value must be numeric.'));
       
   740   }
       
   741 }
       
   742 
       
   743 /**
       
   744  * Submit handler to insert or update a term.
       
   745  *
       
   746  * @see taxonomy_form_term()
       
   747  */
       
   748 function taxonomy_form_term_submit($form, &$form_state) {
       
   749   if ($form_state['clicked_button']['#value'] == t('Delete')) {
       
   750     // Execute the term deletion.
       
   751     if ($form_state['values']['delete'] === TRUE) {
       
   752       return taxonomy_term_confirm_delete_submit($form, $form_state);
       
   753     }
       
   754     // Rebuild the form to confirm term deletion.
       
   755     $form_state['rebuild'] = TRUE;
       
   756     $form_state['confirm_delete'] = TRUE;
       
   757     return;
       
   758   }
       
   759   // Rebuild the form to confirm enabling multiple parents.
       
   760   elseif ($form_state['clicked_button']['#value'] == t('Save') && !$form['#vocabulary']['tags'] && count($form_state['values']['parent']) > 1 && $form['#vocabulary']['hierarchy'] < 2) {
       
   761     $form_state['rebuild'] = TRUE;
       
   762     $form_state['confirm_parents'] = TRUE;
       
   763     return;
       
   764   }
       
   765 
       
   766   switch (taxonomy_save_term($form_state['values'])) {
       
   767     case SAVED_NEW:
       
   768       drupal_set_message(t('Created new term %term.', array('%term' => $form_state['values']['name'])));
       
   769       watchdog('taxonomy', 'Created new term %term.', array('%term' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/term/'. $form_state['values']['tid']));
       
   770       break;
       
   771     case SAVED_UPDATED:
       
   772       drupal_set_message(t('Updated term %term.', array('%term' => $form_state['values']['name'])));
       
   773       watchdog('taxonomy', 'Updated term %term.', array('%term' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/term/'. $form_state['values']['tid']));
       
   774       break;
       
   775   }
       
   776 
       
   777   if (!$form['#vocabulary']['tags']) {
       
   778     $current_parent_count = count($form_state['values']['parent']);
       
   779     $previous_parent_count = count($form['#term']['parent']);
       
   780     // Root doesn't count if it's the only parent.
       
   781     if ($current_parent_count == 1 && isset($form_state['values']['parent'][''])) {
       
   782       $current_parent_count = 0;
       
   783       $form_state['values']['parent'] = array();
       
   784     }
       
   785 
       
   786     // If the number of parents has been reduced to one or none, do a check on the
       
   787     // parents of every term in the vocabulary value.
       
   788     if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
       
   789       taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
       
   790     }
       
   791     // If we've increased the number of parents and this is a single or flat
       
   792     // hierarchy, update the vocabulary immediately.
       
   793     elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']['hierarchy'] < 2) {
       
   794       $form['#vocabulary']['hierarchy'] = $current_parent_count == 1 ? 1 : 2;
       
   795       taxonomy_save_vocabulary($form['#vocabulary']);
       
   796     }
       
   797   }
       
   798 
       
   799   $form_state['tid'] = $form_state['values']['tid'];
       
   800   $form_state['redirect'] = 'admin/content/taxonomy';
       
   801   return;
       
   802 }
       
   803 
       
   804 /**
       
   805  * Form builder for the confirmation of multiple term parents.
       
   806  *
       
   807  * @ingroup forms
       
   808  * @see taxonomy_form_term()
       
   809  */
       
   810 function taxonomy_term_confirm_parents(&$form_state, $vocabulary) {
       
   811   $form = array();
       
   812   foreach (element_children($form_state['values']) as $key) {
       
   813     $form[$key] = array(
       
   814       '#type' => 'value',
       
   815       '#value' => $form_state['values'][$key],
       
   816     );
       
   817   }
       
   818   $question = t('Set multiple term parents?');
       
   819   $description = '<p>'. t("Adding multiple parents to a term will cause the %vocabulary vocabulary to look for multiple parents on every term. Because multiple parents are not supported when using the drag and drop outline interface, drag and drop will be disabled if you enable this option. If you choose to have multiple parents, you will only be able to set parents by using the term edit form.", array('%vocabulary' => $vocabulary->name)) .'</p>';
       
   820   $description .= '<p>'. t("You may re-enable the drag and drop interface at any time by reducing multiple parents to a single parent for the terms in this vocabulary.") .'</p>';
       
   821   return confirm_form($form, $question, drupal_get_destination(), $description, t('Set multiple parents'));
       
   822 }
       
   823 
       
   824 /**
       
   825  * Form builder for the term delete form.
       
   826  *
       
   827  * @ingroup forms
       
   828  * @see taxonomy_term_confirm_delete_submit()
       
   829  */
       
   830 function taxonomy_term_confirm_delete(&$form_state, $tid) {
       
   831   $term = taxonomy_get_term($tid);
       
   832 
       
   833   $form['type'] = array('#type' => 'value', '#value' => 'term');
       
   834   $form['name'] = array('#type' => 'value', '#value' => $term->name);
       
   835   $form['tid'] = array('#type' => 'value', '#value' => $tid);
       
   836   $form['delete'] = array('#type' => 'value', '#value' => TRUE);
       
   837   return confirm_form($form,
       
   838                   t('Are you sure you want to delete the term %title?',
       
   839                   array('%title' => $term->name)),
       
   840                   'admin/content/taxonomy',
       
   841                   t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
       
   842                   t('Delete'),
       
   843                   t('Cancel'));
       
   844 }
       
   845 
       
   846 /**
       
   847  * Submit handler to delete a term after confirmation.
       
   848  *
       
   849  * @see taxonomy_term_confirm_delete()
       
   850  */
       
   851 function taxonomy_term_confirm_delete_submit($form, &$form_state) {
       
   852   taxonomy_del_term($form_state['values']['tid']);
       
   853   taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
       
   854   drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name'])));
       
   855   watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
       
   856   $form_state['redirect'] = 'admin/content/taxonomy';
       
   857   return;
       
   858 }
       
   859 
       
   860 /**
       
   861  * Form builder for the vocabulary delete confirmation form.
       
   862  *
       
   863  * @ingroup forms
       
   864  * @see taxonomy_vocabulary_confirm_delete_submit()
       
   865  */
       
   866 function taxonomy_vocabulary_confirm_delete(&$form_state, $vid) {
       
   867   $vocabulary = taxonomy_vocabulary_load($vid);
       
   868 
       
   869   $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
       
   870   $form['vid'] = array('#type' => 'value', '#value' => $vid);
       
   871   $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
       
   872   return confirm_form($form,
       
   873                   t('Are you sure you want to delete the vocabulary %title?',
       
   874                   array('%title' => $vocabulary->name)),
       
   875                   'admin/content/taxonomy',
       
   876                   t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
       
   877                   t('Delete'),
       
   878                   t('Cancel'));
       
   879 }
       
   880 
       
   881 /**
       
   882  * Submit handler to delete a vocabulary after confirmation.
       
   883  *
       
   884  * @see taxonomy_vocabulary_confirm_delete()
       
   885  */
       
   886 function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
       
   887   $status = taxonomy_del_vocabulary($form_state['values']['vid']);
       
   888   drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
       
   889   watchdog('taxonomy', 'Deleted vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
       
   890   $form_state['redirect'] = 'admin/content/taxonomy';
       
   891   return;
       
   892 }
       
   893 
       
   894 /**
       
   895  * Form builder to confirm reseting a vocabulary to alphabetical order.
       
   896  *
       
   897  * @ingroup forms
       
   898  * @see taxonomy_vocabulary_confirm_reset_alphabetical_submit()
       
   899  */
       
   900 function taxonomy_vocabulary_confirm_reset_alphabetical(&$form_state, $vid) {
       
   901   $vocabulary = taxonomy_vocabulary_load($vid);
       
   902 
       
   903   $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
       
   904   $form['vid'] = array('#type' => 'value', '#value' => $vid);
       
   905   $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
       
   906   $form['reset_alphabetical'] = array('#type' => 'value', '#value' => TRUE);
       
   907   return confirm_form($form,
       
   908                   t('Are you sure you want to reset the vocabulary %title to alphabetical order?',
       
   909                   array('%title' => $vocabulary->name)),
       
   910                   'admin/content/taxonomy/'. $vid,
       
   911                   t('Resetting a vocabulary will discard all custom ordering and sort items alphabetically.'),
       
   912                   t('Reset to alphabetical'),
       
   913                   t('Cancel'));
       
   914 }
       
   915 
       
   916 /**
       
   917  * Submit handler to reset a vocabulary to alphabetical order after confirmation.
       
   918  *
       
   919  * @see taxonomy_vocabulary_confirm_reset_alphabetical()
       
   920  */
       
   921 function taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, &$form_state) {
       
   922   db_query('UPDATE {term_data} t SET weight = 0 WHERE vid = %d', $form_state['values']['vid']);
       
   923   drupal_set_message(t('Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name'])));
       
   924   watchdog('taxonomy', 'Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
       
   925   $form_state['redirect'] = 'admin/content/taxonomy/'. $form_state['values']['vid'];
       
   926 }