diff --git a/includes/common.inc b/includes/common.inc
index e678ab28106ae0d603e9d3f5236a1f3ff757c011..6bc3b328f052392c320b80aba9dfc2f3b6d1da2d 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -2012,9 +2012,10 @@ function drupal_get_js($scope = 'header', $javascript = NULL) {
  *   group. Depth updates the target element with the current indentation.
  * @param $relationship
  *   String describing where the $action variable should be performed. Either
- *   'parent', 'sibling', or 'self'. Parent will only look for fields up the
- *   tree. Sibling will look for fields in the same group in rows above and
- *   below it. Self affects the dragged row itself.
+ *   'parent', 'sibling', 'group', or 'self'. Parent will only look for fields
+ *   up the tree. Sibling will look for fields in the same group in rows above
+ *   and below it. Self affects the dragged row itself. Group affects the
+ *   dragged row, plus any children below it (the entire dragged group).
  * @param $group
  *   A class name applied on all related form elements for this action.
  * @param $subgroup
diff --git a/misc/tabledrag.js b/misc/tabledrag.js
index 1d80b2020533a887eac51ccb1095bd56d63e4440..79711333257b2030adb5b62f1cea8e8643324a65 100644
--- a/misc/tabledrag.js
+++ b/misc/tabledrag.js
@@ -413,7 +413,20 @@ Drupal.tableDrag.prototype.dropRow = function(event) {
     var droppedRow = self.rowObject.element;
     // The row is already in the right place so we just release it.
     if (self.rowObject.changed == true) {
+      // Update the fields in the dropped row.
       self.updateFields(droppedRow);
+
+      // If a setting exists for affecting the entire group, update all the
+      // fields in the entire dragged group.
+      for (var group in self.tableSettings) {
+        var rowSettings = self.rowSettings(group, droppedRow);
+        if (rowSettings.relationship == 'group') {
+          for (n in self.rowObject.children) {
+            self.updateField(self.rowObject.children[n], group);
+          }
+        }
+      }
+
       self.rowObject.markChanged();
       if (self.changed == false) {
         $(Drupal.theme('tableDragChangedWarning')).insertAfter(self.table).hide().fadeIn('slow');
@@ -562,114 +575,127 @@ Drupal.tableDrag.prototype.updateFields = function(changedRow) {
   for (var group in this.tableSettings) {
     // Each group may have a different setting for relationship, so we find
     // the source rows for each seperately.
-    var rowSettings = this.rowSettings(group, changedRow);
+    this.updateField(changedRow, group);
+  }
+}
 
-    // Set the row as it's own target.
-    if (rowSettings.relationship == 'self') {
-      var sourceRow = changedRow;
-    }
-    // Siblings are easy, check previous and next rows.
-    else if (rowSettings.relationship == 'sibling') {
-      var previousRow = $(changedRow).prev('tr').get(0);
-      var nextRow = $(changedRow).next('tr').get(0);
-      var sourceRow = changedRow;
-      if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
-        if (this.indentEnabled) {
-          if ($('.indentations', previousRow).size() == $('.indentations', changedRow)) {
-            sourceRow = previousRow;
-          }
-        }
-        else {
+/**
+ * After the row is dropped, update a single table field according to specific
+ * settings.
+ *
+ * @param changedRow
+ *   DOM object for the row that was just dropped.
+ * @param group
+ *   The settings group on which field updates will occur.
+ */
+Drupal.tableDrag.prototype.updateField = function(changedRow, group) {
+  var rowSettings = this.rowSettings(group, changedRow);
+
+  // Set the row as it's own target.
+  if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') {
+    var sourceRow = changedRow;
+  }
+  // Siblings are easy, check previous and next rows.
+  else if (rowSettings.relationship == 'sibling') {
+    var previousRow = $(changedRow).prev('tr').get(0);
+    var nextRow = $(changedRow).next('tr').get(0);
+    var sourceRow = changedRow;
+    if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
+      if (this.indentEnabled) {
+        if ($('.indentations', previousRow).size() == $('.indentations', changedRow)) {
           sourceRow = previousRow;
         }
       }
-      else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
-        if (this.indentEnabled) {
-          if ($('.indentations', nextRow).size() == $('.indentations', changedRow)) {
-            sourceRow = nextRow;
-          }
-        }
-        else {
-          sourceRow = nextRow;
-        }
+      else {
+        sourceRow = previousRow;
       }
     }
-    // Parents, look up the tree until we find a field not in this group.
-    // Go up as many parents as indentations in the changed row.
-    else if (rowSettings.relationship == 'parent') {
-      var previousRow = $(changedRow).prev('tr');
-      while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
-        previousRow = previousRow.prev('tr');
-      }
-      // If we found a row.
-      if (previousRow.length) {
-        sourceRow = previousRow[0];
+    else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
+      if (this.indentEnabled) {
+        if ($('.indentations', nextRow).size() == $('.indentations', changedRow)) {
+          sourceRow = nextRow;
+        }
       }
-      // Otherwise we went all the way to the left of the table without finding
-      // a parent, meaning this item has been placed at the root level.
       else {
-        // Use the first row in the table as source, because it's garanteed to
-        // be at the root level. Find the first item, then compare this row
-        // against it as a sibling.
-        sourceRow = $('tr.draggable:first').get(0);
-        if (sourceRow == this.rowObject.element) {
-          sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
-        }
-        var useSibling = true;
+        sourceRow = nextRow;
       }
     }
+  }
+  // Parents, look up the tree until we find a field not in this group.
+  // Go up as many parents as indentations in the changed row.
+  else if (rowSettings.relationship == 'parent') {
+    var previousRow = $(changedRow).prev('tr');
+    while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
+      previousRow = previousRow.prev('tr');
+    }
+    // If we found a row.
+    if (previousRow.length) {
+      sourceRow = previousRow[0];
+    }
+    // Otherwise we went all the way to the left of the table without finding
+    // a parent, meaning this item has been placed at the root level.
+    else {
+      // Use the first row in the table as source, because it's garanteed to
+      // be at the root level. Find the first item, then compare this row
+      // against it as a sibling.
+      sourceRow = $('tr.draggable:first').get(0);
+      if (sourceRow == this.rowObject.element) {
+        sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
+      }
+      var useSibling = true;
+    }
+  }
 
-    // Because we may have moved the row from one category to another,
-    // take a look at our sibling and borrow its sources and targets.
-    this.copyDragClasses(sourceRow, changedRow, group);
-    rowSettings = this.rowSettings(group, changedRow);
+  // Because we may have moved the row from one category to another,
+  // take a look at our sibling and borrow its sources and targets.
+  this.copyDragClasses(sourceRow, changedRow, group);
+  rowSettings = this.rowSettings(group, changedRow);
 
-    // In the case that we're looking for a parent, but the row is at the top
-    // of the tree, copy our sibling's values.
-    if (useSibling) {
-      rowSettings.relationship = 'sibling';
-      rowSettings.source = rowSettings.target;
-    }
+  // In the case that we're looking for a parent, but the row is at the top
+  // of the tree, copy our sibling's values.
+  if (useSibling) {
+    rowSettings.relationship = 'sibling';
+    rowSettings.source = rowSettings.target;
+  }
 
-    var targetClass = '.' + rowSettings.target;
-    var targetElement = $(targetClass, changedRow).get(0);
-
-    // Check if a target element exists in this row.
-    if (targetElement) {
-      var sourceClass = '.' + rowSettings.source;
-      var sourceElement = $(sourceClass, sourceRow).get(0);
-      switch (rowSettings.action) {
-        case 'depth':
-          // Get the depth of the target row.
-          targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size();
-          break;
-        case 'match':
-          // Update the value.
-          targetElement.value = sourceElement.value;
-          break;
-        case 'order':
-          var siblings = this.rowObject.findSiblings(rowSettings);
-          if ($(targetElement).is('select')) {
-            // Get a list of acceptable values.
-            var values = new Array();
-            $('option', targetElement).each(function() {
-              values.push(this.value);
-            });
-            // Populate the values in the siblings.
-            $(targetClass, siblings).each(function() {
-              this.value = values.shift();
-            });
-          }
-          else {
-            // Assume a numeric input field.
-            var weight = parseInt($(targetClass, siblings[0]).val()) || 0;
-            $(targetClass, siblings).each(function() {
-              this.value = weight;
-              weight++;
-            });
-          }
-          break;
-      }
+  var targetClass = '.' + rowSettings.target;
+  var targetElement = $(targetClass, changedRow).get(0);
+
+  // Check if a target element exists in this row.
+  if (targetElement) {
+    var sourceClass = '.' + rowSettings.source;
+    var sourceElement = $(sourceClass, sourceRow).get(0);
+    switch (rowSettings.action) {
+      case 'depth':
+        // Get the depth of the target row.
+        targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size();
+        break;
+      case 'match':
+        // Update the value.
+        targetElement.value = sourceElement.value;
+        break;
+      case 'order':
+        var siblings = this.rowObject.findSiblings(rowSettings);
+        if ($(targetElement).is('select')) {
+          // Get a list of acceptable values.
+          var values = new Array();
+          $('option', targetElement).each(function() {
+            values.push(this.value);
+          });
+          // Populate the values in the siblings.
+          $(targetClass, siblings).each(function() {
+            this.value = values.shift();
+          });
+        }
+        else {
+          // Assume a numeric input field.
+          var weight = parseInt($(targetClass, siblings[0]).val()) || 0;
+          $(targetClass, siblings).each(function() {
+            this.value = weight;
+            weight++;
+          });
+        }
+        break;
     }
   }
 };
diff --git a/modules/comment/comment.install b/modules/comment/comment.install
index 0549195303ac8b9c5df57b8a433e3bcd279354a3..3d2b9e7435b6831ef6f2977a48db701d7047e907 100644
--- a/modules/comment/comment.install
+++ b/modules/comment/comment.install
@@ -24,38 +24,6 @@ function comment_update_1() {
   return array();
 }
 
-function comment_update_6001() {
-  $ret[] = update_sql("ALTER TABLE {comments} DROP score");
-  $ret[] = update_sql("ALTER TABLE {comments} DROP users");
-  return $ret;
-}
-
-/**
- * Changed comment settings from global to per-node -- copy global
- * settings to all node types.
- */
-function comment_update_6002() {
-  $settings = array(
-    'comment_default_mode' => COMMENT_MODE_THREADED_EXPANDED,
-    'comment_default_order' => COMMENT_ORDER_NEWEST_FIRST,
-    'comment_default_per_page' => 50,
-    'comment_controls' => COMMENT_CONTROLS_HIDDEN,
-    'comment_anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT,
-    'comment_subject_field' => 1,
-    'comment_preview' => COMMENT_PREVIEW_REQUIRED,
-    'comment_form_location' => COMMENT_FORM_SEPARATE_PAGE,
-  );
-  $types = node_get_types();
-  foreach ($settings as $setting => $default) {
-    $value = variable_get($setting, $default);
-    foreach ($types as $type => $object) {
-      variable_set($setting .'_'. $type, $value);
-    }
-    variable_del($setting);
-  }
-  return array();
-}
-
 /**
  * Implementation of hook_schema().
  */
diff --git a/modules/forum/forum.admin.inc b/modules/forum/forum.admin.inc
index 099dae094e64552dc0b250d8d1db37e8ff979ff2..6b0bcb8f86bfc02e54baf76d560da6e8d1c49198 100644
--- a/modules/forum/forum.admin.inc
+++ b/modules/forum/forum.admin.inc
@@ -214,26 +214,33 @@ function forum_admin_settings() {
 /**
  * Returns an overview list of existing forums and containers
  */
-function forum_overview() {
-  $header = array(t('Name'), t('Operations'));
+function forum_overview(&$form_state) {
+  include_once(drupal_get_path('module', 'taxonomy') .'/taxonomy.admin.inc');
 
   $vid = variable_get('forum_nav_vocabulary', '');
-  $tree = taxonomy_get_tree($vid);
-  if ($tree) {
-    foreach ($tree as $term) {
-      if (in_array($term->tid, variable_get('forum_containers', array()))) {
-        $rows[] = array(str_repeat(' -- ', $term->depth) .' '. l($term->name, 'forum/'. $term->tid), l(t('edit container'), 'admin/content/forum/edit/container/'. $term->tid));
+  $vocabulary = taxonomy_vocabulary_load($vid);
+  $form = taxonomy_overview_terms($form_state, $vocabulary);
+  drupal_set_title('Forums');
+
+  foreach (element_children($form) as $key) {
+    if (isset($form[$key]['#term'])) {
+      $term = $form[$key]['#term'];
+      $form[$key]['view']['#value'] = l($term['name'], 'forum/'. $term['tid']);
+      if (in_array($form[$key]['#term']['tid'], variable_get('forum_containers', array()))) {
+        $form[$key]['edit']['#value'] = l(t('edit container'), 'admin/content/forum/edit/container/'. $term['tid']);
       }
       else {
-        $rows[] = array(str_repeat(' -- ', $term->depth) .' '. l($term->name, 'forum/'. $term->tid), l(t('edit forum'), 'admin/content/forum/edit/forum/'. $term->tid));
-       }
-
+        $form[$key]['edit']['#value'] = l(t('edit forum'), 'admin/content/forum/edit/forum/'. $term['tid']);
+      }
     }
   }
-  else {
-    $rows[] = array(array('data' => '<em>'. t('There are no existing containers or forums. You may add some on the <a href="@container">add container</a> or <a href="@forum">add forum</a> pages.', array('@container' => url('admin/content/forum/add/container'), '@forum' => url('admin/content/forum/add/forum'))) .'</em>', 'colspan' => 2));
-  }
-  return theme('table', $header, $rows);
+
+  // The form needs to have submit and validate handlers set explicitly.
+  $form['#theme'] = 'taxonomy_overview_terms';
+  $form['#submit'] = array('taxonomy_overview_terms_submit'); // Use the existing taxonomy overview submit handler.
+  $form['#validate'] = array('taxonomy_overview_terms_validate');
+  $form['#empty_text'] = '<em>'. t('There are no existing containers or forums. You may add some on the <a href="@container">add container</a> or <a href="@forum">add forum</a> pages.', array('@container' => url('admin/content/forum/add/container'), '@forum' => url('admin/content/forum/add/forum'))) .'</em>';
+  return $form;
 }
 
 /**
diff --git a/modules/forum/forum.module b/modules/forum/forum.module
index 6647daff65669ba04fe3b52346ab546d9ae13217..46f0a9615902afe449ed89f4f9e7373cc29a1b1d 100644
--- a/modules/forum/forum.module
+++ b/modules/forum/forum.module
@@ -92,7 +92,8 @@ function forum_menu() {
   $items['admin/content/forum'] = array(
     'title' => 'Forums',
     'description' => 'Control forums and their hierarchy and change forum settings.',
-    'page callback' => 'forum_overview',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('forum_overview'),
     'access arguments' => array('administer forums'),
     'file' => 'forum.admin.inc',
   );
diff --git a/modules/locale/locale.install b/modules/locale/locale.install
index 239ff9fd6c862f72592c6f26b93cdbebfbb8325f..245f0a9e39ec26f1b51bea4ac9d9044cb79340ff 100644
--- a/modules/locale/locale.install
+++ b/modules/locale/locale.install
@@ -15,126 +15,6 @@ function locale_install() {
   db_query("INSERT INTO {languages} (language, name, native, direction, enabled, weight, javascript) VALUES ('en', 'English', 'English', '0', '1', '0', '')");
 }
 
-/**
- * @defgroup updates-5.x-to-6.x Locale updates from 5.x to 6.x
- * @{
- */
-
-/**
- * {locales_meta} table became {languages}.
- */
-function locale_update_6001() {
-  $ret = array();
-  switch ($GLOBALS['db_type']) {
-    case 'mysql':
-    case 'mysqli':
-      $ret[] = update_sql("CREATE TABLE {languages} (
-        language varchar(12) NOT NULL default '',
-        name varchar(64) NOT NULL default '',
-        native varchar(64) NOT NULL default '',
-        direction int NOT NULL default '0',
-        enabled int NOT NULL default '0',
-        plurals int NOT NULL default '0',
-        formula varchar(128) NOT NULL default '',
-        domain varchar(128) NOT NULL default '',
-        prefix varchar(128) NOT NULL default '',
-        weight int NOT NULL default '0',
-        PRIMARY KEY (language)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-      break;
-    case 'pgsql':
-      $ret[] = update_sql("CREATE TABLE {languages} (
-        language varchar(12) NOT NULL default '',
-        name varchar(64) NOT NULL default '',
-        native varchar(64) NOT NULL default '',
-        direction int NOT NULL default '0',
-        enabled int NOT NULL default '0',
-        plurals int NOT NULL default '0',
-        formula varchar(128) NOT NULL default '',
-        domain varchar(128) NOT NULL default '',
-        prefix varchar(128) NOT NULL default '',
-        weight int NOT NULL default '0',
-        PRIMARY KEY (language)
-      )");
-      break;
-  }
-
-  // Save the languages
-  $ret[] = update_sql("INSERT INTO {languages} (language, name, native, direction, enabled, plurals, formula, domain, prefix, weight) SELECT locale, name, name, 0, enabled, plurals, formula, '', locale, 0 FROM {locales_meta}");
-
-  // Save the language count in the variable table
-  $count = db_result(db_query('SELECT COUNT(*) FROM {languages} WHERE enabled = 1'));
-  variable_set('language_count', $count);
-
-  // Save the default language in the variable table
-  $default = db_fetch_object(db_query('SELECT * FROM {locales_meta} WHERE isdefault = 1'));
-  variable_set('language_default', (object) array('language' => $default->locale, 'name' => $default->name, 'native' => '', 'direction' => 0, 'enabled' => 1, 'plurals' => $default->plurals, 'formula' => $default->formula, 'domain' => '', 'prefix' => $default->locale, 'weight' => 0));
-
-  $ret[] = update_sql("DROP TABLE {locales_meta}");
-  return $ret;
-}
-
-/**
- * Change locale column to language. The language column is added by
- * update_fix_d6_requirements() in update.php to avoid a large number
- * of error messages from update.php.  All we need to do here is copy
- * locale to language and then drop locale.
- */
-function locale_update_6002() {
-  $ret = array();
-  $ret[] = update_sql('UPDATE {locales_target} SET language = locale');
-  db_drop_field($ret, 'locales_target', 'locale');
-  return $ret;
-}
-
-/**
- * Adds a column to store the filename of the JavaScript translation file.
- */
-function locale_update_6003() {
-  $ret = array();
-  db_add_field($ret, 'languages', 'javascript', array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''));
-  return $ret;
-}
-
-/**
- * Remove empty translations, we don't need these anymore.
- */
-function locale_update_6004() {
-  $ret = array();
-  $ret[] = update_sql("DELETE FROM {locales_target} WHERE translation = ''");
-  return $ret;
-}
-
-/**
- * Prune strings with no translations (will be automatically re-registered if still in use)
- */
-function locale_update_6005() {
-  $ret = array();
-  $ret[] = update_sql("DELETE FROM {locales_source} WHERE lid NOT IN (SELECT lid FROM {locales_target})");
-  return $ret;
-}
-
-/**
- * Fix remaining inconsistent indexes.
- */
-function locale_update_6006() {
-  $ret = array();
-  db_add_index($ret, 'locales_target', 'language', array('language'));
-
-  switch ($GLOBALS['db_type']) {
-    case 'pgsql':
-      db_drop_index($ret, 'locales_source', 'source');
-      db_add_index($ret, 'locales_source', 'source', array(array('source', 30)));
-      break;
-  }
-
-  return $ret;
-}
-
-/**
- * @} End of "defgroup updates-5.x-to-6.x"
- */
-
 /**
  * Implementation of hook_uninstall().
  */
diff --git a/modules/system/system.install b/modules/system/system.install
index 1f036b28fcd70d8aaf540d45e3e3cac44f6d04b0..d084f18dbc0925b5b831454e589c7ccb790d8f62 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -2735,6 +2735,387 @@ function system_update_6039() {
 }
 
 
+/**
+ * Remove unused fields from comment tables
+ */
+function system_update_6038() {
+  $ret = array();
+  if (db_table_exists('comments')) {
+    $ret[] = update_sql("ALTER TABLE {comments} DROP score");
+    $ret[] = update_sql("ALTER TABLE {comments} DROP users");
+  }
+  return $ret;
+}
+
+/**
+ * Changed comment settings from global to per-node -- copy global
+ * settings to all node types.
+ */
+function system_update_6039() {
+  $ret = array();
+  $settings = array(
+    'comment_default_mode' => COMMENT_MODE_THREADED_EXPANDED,
+    'comment_default_order' => COMMENT_ORDER_NEWEST_FIRST,
+    'comment_default_per_page' => 50,
+    'comment_controls' => COMMENT_CONTROLS_HIDDEN,
+    'comment_anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT,
+    'comment_subject_field' => 1,
+    'comment_preview' => COMMENT_PREVIEW_REQUIRED,
+    'comment_form_location' => COMMENT_FORM_SEPARATE_PAGE,
+  );
+  $types = node_get_types();
+  foreach ($settings as $setting => $default) {
+    $value = variable_get($setting, $default);
+    foreach ($types as $type => $object) {
+      variable_set($setting .'_'. $type, $value);
+    }
+    variable_del($setting);
+  }
+  return array();
+}
+
+/**
+ * This function moves any existing book hierarchy into the new structure used
+ * in the 6.x module.  Rather than storing the hierarchy in the {book} table,
+ * the menu API is used to store the hierarchy in the {menu_links} table and the
+ * {book} table serves to uniquely connect a node to a menu link.
+ *
+ * In order to accomplish this, the current hierarchy is processed using a stack.
+ * The stack insures that each parent is processed before any of its children
+ * in the book hierarchy, and is compatible with batched update processing.
+ *
+ */
+function system_update_6040() {
+  $ret = array();
+  
+  if (!db_table_exists('book')) {
+    return $ret;
+  }
+
+  // Set up for a multi-part update.
+  if (!isset($_SESSION['system_update_6040'])) {
+
+    $schema['book'] = array(
+      'fields' => array(
+        'mlid'    => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+        'nid'     => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+        'bid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      ),
+      'indexes' => array(
+        'nid'     => array('nid'),
+        'bid' => array('bid')
+      ),
+      'primary key' => array('mlid'),
+    );
+    // Add the node type.
+    _book_install_type_create();
+
+    // Fix role permissions to account for the changed names
+    // Setup the array holding strings to match and the corresponding
+    // strings to replace them with.
+    $replace = array(
+      'outline posts in books' => 'administer book outlines',
+      'create book pages' => 'create book content',
+      'edit book pages' => 'edit book content',
+      'edit own book pages' => 'edit own book content',
+      'see printer-friendly version' => 'access printer-friendly version',
+    );
+
+    // Loop over all the roles, and do the necessary transformations.
+    $query = db_query("SELECT rid, perm FROM {permission} ORDER BY rid");
+    while ($role = db_fetch_object($query)) {
+      // Replace all the old permissions with the corresponding new permissions.
+      $fixed_perm = strtr($role->perm, $replace);
+      // If the user could previously create book pages, they should get the new
+      // 'add content to books' permission.
+      if (strpos($role->perm, 'create book pages') !== FALSE) {
+        $fixed_perm .= ', add content to books';
+      }
+      // Only save if the permissions have changed.
+      if ($fixed_perm != $role->perm) {
+        $ret[] = update_sql("UPDATE {permission} SET perm = '$fixed_perm' WHERE rid = $role->rid");
+      }
+    }
+
+    // Determine whether there are any existing nodes in the book hierarchy.
+    if (db_result(db_query("SELECT COUNT(*) FROM {book}"))) {
+      // Temporary table for the old book hierarchy; we'll discard revision info.
+      $schema['book_temp'] = array(
+        'fields' => array(
+          'nid'    => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'parent' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+          'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny')
+        ),
+        'indexes' => array(
+          'parent' => array('parent')
+        ),
+        'primary key' => array('nid'),
+      );
+
+      db_create_table($ret, 'book_temp', $schema['book_temp']);
+
+      // Insert each node in the old table into the temporary table.
+      $ret[] = update_sql("INSERT INTO {book_temp} (nid, parent, weight) SELECT b.nid, b.parent, b.weight FROM {book} b INNER JOIN {node} n on b.vid = n.vid");
+      $ret[] = update_sql("DROP TABLE {book}");
+
+      db_create_table($ret, 'book', $schema['book']);
+
+      $_SESSION['system_update_6040_orphans']['from'] = 0;
+      $_SESSION['system_update_6040'] = array();
+      $result = db_query("SELECT * from {book_temp} WHERE parent = 0");
+
+      // Collect all books - top-level nodes.
+      while ($a = db_fetch_array($result)) {
+        $_SESSION['book_update_6000'][] = $a;
+      }
+      $ret['#finished'] = 0;
+      return $ret;
+    }
+    else {
+      // No exising nodes in the hierarchy, so drop the table and re-create it.
+      $ret[] = update_sql("DROP TABLE {book}");
+      db_create_table($ret, 'book', $schema['book']);
+      return $ret;
+    }
+  }
+  elseif ($_SESSION['system_update_6040_orphans']) {
+    // Do the first batched part of the update - collect orphans.
+    $update_count = 400; // Update this many at a time
+
+    $result = db_query_range("SELECT * FROM {book_temp}", $_SESSION['system_update_6040_orphans']['from'], $update_count);
+    $has_rows = FALSE;
+    // Go through the next $update_count book pages and locate the orphans.
+    while ($book = db_fetch_array($result)) {
+      $has_rows = TRUE;
+      // Orphans are defined as nodes whose parent does not exist in the table.
+      if ($book['parent'] && !db_result(db_query("SELECT COUNT(*) FROM {book_temp} WHERE nid = %d", $book['parent']))) {
+        if (empty($_SESSION['system_update_6040_orphans']['book'])) {
+          // The first orphan becomes the parent for all other orphans.
+          $book['parent'] = 0;
+          $_SESSION['system_update_6040_orphans']['book'] = $book;
+          $ret[] = array('success' => TRUE, 'query' => t('Relocated orphan book pages.'));
+        }
+        else {
+          // Re-assign the parent value of the book, and add it to the stack.
+          $book['parent'] = $_SESSION['system_update_6040_orphans']['book']['nid'];
+          $_SESSION['system_update_6040'][] = $book;
+        }
+      }
+    }
+    if ($has_rows) {
+      $_SESSION['system_update_6040_orphans']['from'] += $update_count;
+    }
+    else {
+      // Done with this part
+      if (!empty($_SESSION['system_update_6040_orphans']['book'])) {
+        // The orphans' parent is added last, so it will be processed first.
+        $_SESSION['system_update_6040'][] = $_SESSION['book_update_6000_orphans']['book'];
+      }
+      $_SESSION['system_update_6040_orphans'] = FALSE;
+    }
+    $ret['#finished'] = 0;
+    return $ret;
+  }
+  else {
+    // Do the next batched part of the update
+    $update_count = 100; // Update this many at a time
+
+    while ($update_count && $_SESSION['system_update_6040']) {
+      // Get the last node off the stack.
+      $book = array_pop($_SESSION['system_update_6040']);
+
+      // Add all of this node's children to the stack
+      $result = db_query("SELECT * FROM {book_temp} WHERE parent = %d", $book['nid']);
+      while ($a = db_fetch_array($result)) {
+        $_SESSION['system_update_6040'][] = $a;
+      }
+
+      if ($book['parent']) {
+        // If its not a top level page, get its parent's mlid.
+        $parent = db_fetch_array(db_query("SELECT b.mlid AS plid, b.bid FROM {book} b WHERE b.nid = %d", $book['parent']));
+        $book = array_merge($book, $parent);
+      }
+      else {
+        // There is not a parent - this is a new book.
+        $book['plid'] = 0;
+        $book['bid'] = $book['nid'];
+      }
+
+      $book += array(
+        'module' => 'book',
+        'link_path' => 'node/'. $book['nid'],
+        'router_path' => 'node/%',
+        'menu_name' => book_menu_name($book['bid']),
+      );
+      $book = array_merge($book, db_fetch_array(db_query("SELECT title AS link_title FROM {node} WHERE nid = %d", $book['nid'])));
+
+      // Items with depth > MENU_MAX_DEPTH cannot be saved.
+      if (menu_link_save($book)) {
+        db_query("INSERT INTO {book} (mlid, nid, bid) VALUES (%d, %d, %d)", $book['mlid'], $book['nid'], $book['bid']);
+      }
+      else {
+        // The depth was greater then MENU_MAX_DEPTH, so attach it to the
+        // closest valid parent.
+        $book['plid'] = db_result(db_query("SELECT plid FROM {menu_links} WHERE mlid = %d", $book['plid']));
+        if (menu_link_save($book)) {
+          db_query("INSERT INTO {book} (mlid, nid, bid) VALUES (%d, %d, %d)", $book['mlid'], $book['nid'], $book['bid']);
+        }
+      }
+      $update_count--;
+    }
+    $ret['#finished'] = 0;
+  }
+
+  if (empty($_SESSION['system_update_6040'])) {
+    $ret['#finished'] = 1;
+    $ret[] = array('success' => TRUE, 'query' => t('Relocated existing book pages.'));
+    $ret[] = update_sql("DROP TABLE {book_temp}");
+    unset($_SESSION['system_update_6040']);
+    unset($_SESSION['system_update_6040_orphans']);
+  }
+
+  return $ret;
+}
+
+/**
+ * {locales_meta} table became {languages}.
+ */
+function system_update_6041() {
+  $ret = array();
+
+  if (!db_table_exists('locales_meta')) {
+    return $ret;
+  }
+  
+  $schema['languages'] = array(
+    'fields' => array(
+      'language' => array(
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'native' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'direction' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'enabled' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'plurals' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'formula' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'domain' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'prefix' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'javascript' => array( //Adds a column to store the filename of the JavaScript translation file.
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+    ),
+    'primary key' => array('language'),
+  );
+  
+  db_create_table($ret, 'languages', $schema['languages']);
+
+  // Save the languages
+  $ret[] = update_sql("INSERT INTO {languages} (language, name, native, direction, enabled, plurals, formula, domain, prefix, weight) SELECT locale, name, name, 0, enabled, plurals, formula, '', locale, 0 FROM {locales_meta}");
+
+  // Save the language count in the variable table
+  $count = db_result(db_query('SELECT COUNT(*) FROM {languages} WHERE enabled = 1'));
+  variable_set('language_count', $count);
+
+  // Save the default language in the variable table
+  $default = db_fetch_object(db_query('SELECT * FROM {locales_meta} WHERE isdefault = 1'));
+  variable_set('language_default', (object) array('language' => $default->locale, 'name' => $default->name, 'native' => '', 'direction' => 0, 'enabled' => 1, 'plurals' => $default->plurals, 'formula' => $default->formula, 'domain' => '', 'prefix' => $default->locale, 'weight' => 0));
+
+  $ret[] = update_sql("DROP TABLE {locales_meta}");
+  return $ret;
+}
+
+/**
+ * Change locale column to language. The language column is added by
+ * update_fix_d6_requirements() in update.php to avoid a large number
+ * of error messages from update.php.  All we need to do here is copy
+ * locale to language and then drop locale.
+ */
+function system_update_6042() {
+  $ret = array();
+  if (db_table_exists('locales_target')) {
+    $ret[] = update_sql('UPDATE {locales_target} SET language = locale');
+    db_drop_field($ret, 'locales_target', 'locale');
+    // Remove empty translations, we don't need these anymore.
+    $ret[] = update_sql("DELETE FROM {locales_target} WHERE translation = ''");
+  }
+  return $ret;
+}
+
+/**
+ * Prune strings with no translations (will be automatically re-registered if still in use)
+ */
+function system_update_6043() {
+  $ret = array();
+  if (db_table_exists('locales_source')) {
+    $ret[] = update_sql("DELETE FROM {locales_source} WHERE lid NOT IN (SELECT lid FROM {locales_target})");
+  }
+  return $ret;
+}
+
+/**
+ * Fix remaining inconsistent indexes.
+ */
+function system_update_6044() {
+  $ret = array();
+  if (db_table_exists('locales_target')) {
+    db_add_index($ret, 'locales_target', 'language', array('language'));
+  }
+  if (db_table_exists('locales_source')) {
+    db_drop_index($ret, 'locales_source', 'source');
+    db_add_index($ret, 'locales_source', 'source', array(array('source', 30)));
+  }
+
+  return $ret;
+}
+
 /**
  * @} End of "defgroup updates-5.x-to-6.x"
  * The next series of updates should start at 7000.
diff --git a/modules/taxonomy/taxonomy.admin.inc b/modules/taxonomy/taxonomy.admin.inc
index d0ca2016a0ff4a5f106b75a275abe823726d7a30..178818084ebc638138baec11009c2caed8b0ded4 100644
--- a/modules/taxonomy/taxonomy.admin.inc
+++ b/modules/taxonomy/taxonomy.admin.inc
@@ -7,30 +7,79 @@
  */
 
 /**
- * List and manage vocabularies.
+ * Form builder to list and manage vocabularies.
+ * 
+ * @ingroup forms
+ * @see taxonomy_overview_vocabularies_submit().
+ * @see theme_taxonomy_overview_vocabularies().
  */
 function taxonomy_overview_vocabularies() {
   $vocabularies = taxonomy_get_vocabularies();
-  $rows = array();
+  $form = array('#tree' => TRUE);
   foreach ($vocabularies as $vocabulary) {
     $types = array();
     foreach ($vocabulary->nodes as $type) {
       $node_type = node_get_types('name', $type);
       $types[] = $node_type ? $node_type : $type;
     }
-    $rows[] = array('name' => check_plain($vocabulary->name),
-      'type' => implode(', ', $types),
-      'edit' => l(t('edit vocabulary'), "admin/content/taxonomy/edit/vocabulary/$vocabulary->vid"),
-      'list' => l(t('list terms'), "admin/content/taxonomy/$vocabulary->vid"),
-      'add' => l(t('add terms'), "admin/content/taxonomy/$vocabulary->vid/add/term")
-    );
+    $form[$vocabulary->vid]['#vocabulary'] = (array)$vocabulary;
+    $form[$vocabulary->vid]['name'] = array('#value' => check_plain($vocabulary->name));
+    $form[$vocabulary->vid]['types'] = array('#value' => implode(', ', $types));
+    $form[$vocabulary->vid]['weight'] = array('#type' => 'weight', '#delta' => 10, '#default_value' => $vocabulary->weight);
+    $form[$vocabulary->vid]['edit'] = array('#value' => l(t('edit vocabulary'), "admin/content/taxonomy/edit/vocabulary/$vocabulary->vid"));
+    $form[$vocabulary->vid]['list'] = array('#value' => l(t('list terms'), "admin/content/taxonomy/$vocabulary->vid"));
+    $form[$vocabulary->vid]['add'] = array('#value' => l(t('add terms'), "admin/content/taxonomy/$vocabulary->vid/add/term"));
+  }
+
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
+  return $form;
+}
+
+/**
+ * Submit handler for vocabularies overview. Updates changed vocabulary weights.
+ * 
+ * @see taxonomy_overview_vocabularies().
+ */
+function taxonomy_overview_vocabularies_submit($form, &$form_state) {
+  foreach ($form_state['values'] as $vid => $vocabulary) {
+    if (is_numeric($vid) && $form[$vid]['#vocabulary']['weight'] != $form_state['values'][$vid]['weight']) {
+      $form[$vid]['#vocabulary']['weight'] = $form_state['values'][$vid]['weight'];
+      taxonomy_save_vocabulary($form[$vid]['#vocabulary']);
+    }
+  }
+}
+
+/**
+ * Theme the vocabulary overview as a sortable list of vocabularies.
+ * 
+ * @ingroup themeable
+ * @see taxonomy_overview_vocabularies().
+ */
+function theme_taxonomy_overview_vocabularies($form) {
+  drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'vocabulary-weight');
+
+  $rows = array();
+  foreach (element_children($form) as $key) {
+    if (isset($form[$key]['name'])) {
+      $vocabulary = &$form[$key];
+      $vocabulary['weight']['#attributes']['class'] = 'vocabulary-weight';
+
+      $row = array();
+      $row[] = drupal_render($vocabulary['name']);
+      $row[] = drupal_render($vocabulary['types']);
+      $row[] = drupal_render($vocabulary['weight']);
+      $row[] = drupal_render($vocabulary['edit']);
+      $row[] = drupal_render($vocabulary['list']);
+      $row[] = drupal_render($vocabulary['add']);
+      $rows[] = array('data' => $row, 'class' => 'draggable');
+    }
   }
   if (empty($rows)) {
-    $rows[] = array(array('data' => t('No terms available.'), 'colspan' => '5'));
+    $rows[] = array(array('data' => t('No terms available.'), 'colspan' => '6'));
   }
-  $header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3'));
+  $header = array(t('Name'), t('Type'), t('Weight'), array('data' => t('Operations'), 'colspan' => '3'));
 
-  return theme('table', $header, $rows, array('id' => 'taxonomy'));
+  return theme('table', $header, $rows, array('id' => 'taxonomy')) . drupal_render($form);  
 }
 
 /**
@@ -114,7 +163,7 @@ function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
   // Set the hierarchy to "multiple parents" by default. This simplifies the
   // vocabulary form and standardizes the term form.
   $form['hierarchy'] = array('#type' => 'value',
-    '#value' => '2',
+    '#value' => '0',
   );
   // Enable "related terms" by default.
   $form['relations'] = array('#type' => 'value',
@@ -166,9 +215,6 @@ function taxonomy_admin_vocabulary_edit($vocabulary) {
  * Page to edit a vocabulary term.
  */
 function taxonomy_admin_term_edit($tid) {
-  if ((isset($_POST['op']) && $_POST['op'] == t('Delete')) || isset($_POST['confirm'])) {
-    return drupal_get_form('taxonomy_term_confirm_delete', $tid);
-  }
   if ($term = (array)taxonomy_get_term($tid)) {
     return drupal_get_form('taxonomy_form_term', taxonomy_vocabulary_load($term['vid']), $term);
   }
@@ -176,58 +222,388 @@ function taxonomy_admin_term_edit($tid) {
 }
 
 /**
+ * Form builder for the taxonomy terms overview.
+ * 
  * Display a tree of all the terms in a vocabulary, with options to edit
- * each one.
+ * each one. The form is made drag and drop by the theme function.
+ * 
+ * @ingroup forms
+ * @see taxonomy_overview_terms_submit().
+ * @see theme_taxonomy_overview_terms().
  */
-function taxonomy_overview_terms($vocabulary) {
-  $destination = drupal_get_destination();
+function taxonomy_overview_terms(&$form_state, $vocabulary) {
+  global $pager_page_array, $pager_total, $pager_total_items;
 
-  $header = array(t('Name'), t('Operations'));
+  // Check for confirmation forms.
+  if (isset($form_state['confirm_reset_alphabetical'])) {
+    return taxonomy_vocabulary_confirm_reset_alphabetical($form_state, $vocabulary->vid);
+  }
 
   drupal_set_title(t('Terms in %vocabulary', array('%vocabulary' => $vocabulary->name)));
-  $start_from      = isset($_GET['page']) ? $_GET['page'] : 0;
-  $total_entries   = 0;  // total count for pager
-  $page_increment  = 25; // number of tids per page
-  $displayed_count = 0;  // number of tids shown
+  $form = array(
+    '#vocabulary' => (array)$vocabulary,
+    '#tree' => TRUE,
+    '#parent_fields' => FALSE,
+  );
+
+  $page            = isset($_GET['page']) ? $_GET['page'] : 0;
+  $page_increment  = 10;  // Number of terms per page.
+  $page_entries    = 0;   // Elements shown on this page.
+  $before_entries  = 0;   // Elements at the root level before this page.
+  $after_entries   = 0;   // Elements at the root level after this page.
+  $root_entries    = 0;   // Elements at the root level on this page.
+
+  // Terms from previous and next pages are shown if the term tree would have
+  // been cut in the middle. Keep track of how many extra terms we show on each
+  // page of terms.
+  $back_peddle    = NULL;
+  $forward_peddle = 0;
 
+  // An array of the terms to be displayed on this page.
+  $current_page = array();
+
+  // Case for free tagging.
   if ($vocabulary->tags) {
     // We are not calling taxonomy_get_tree because that might fail with a big
     // number of tags in the freetagging vocabulary.
     $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);
+    $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'), $page_increment, 0, NULL, $vocabulary->vid);
     while ($term = db_fetch_object($results)) {
-      $rows[] = array(
-        l($term->name, "taxonomy/term/$term->tid"),
-        l(t('edit'), "admin/content/taxonomy/edit/term/$term->tid", array('query' => $destination)),
-      );
+      $key = 'tid:'. $term->tid .':0';
+      $current_page[$key] = $term;
+      $page_entries++;
     }
   }
+  // Case for restricted vocabulary.
   else {
+    $term_deltas = array();
     $tree = taxonomy_get_tree($vocabulary->vid);
-    foreach ($tree as $term) {
-      $total_entries++; // we're counting all-totals, not displayed
-      if (($start_from && ($start_from * $page_increment) >= $total_entries) || ($displayed_count == $page_increment)) {
+    $term = current($tree);
+    do {
+      // In case this tree is completely empty.
+      if (empty($term)) {
+        break;
+      }
+      // Count entries before the current page.
+      if ($page && ($page * $page_increment) > $before_entries && !isset($back_peddle)) {
+        $before_entries++;
+        continue;
+      }
+      // Count entries after the current page.
+      elseif ($page_entries > $page_increment && isset($complete_tree)) {
+        $after_entries++;
         continue;
       }
-      $rows[] = array(str_repeat('--', $term->depth) .' '. l($term->name, "taxonomy/term/$term->tid"), l(t('edit'), "admin/content/taxonomy/edit/term/$term->tid", array('query' => $destination)));
-      $displayed_count++; // we're counting tids displayed
+
+      // Do not let a term start the page that is not at the root.
+      if (isset($term->depth) && ($term->depth > 0) && !isset($back_peddle)) {
+        $back_peddle = 0;
+        while ($pterm = prev($tree)) {
+          $before_entries--;
+          $back_peddle++;
+          if ($pterm->depth == 0) {
+            prev($tree);
+            continue 2; // Jump back to the start of the root level parent.
+          }
+        }
+      }
+      $back_peddle = isset($back_peddle) ? $back_peddle : 0;
+
+      // Continue rendering the tree until we reach the a new root item.
+      if ($page_entries >= $page_increment + $back_peddle + 1 && $term->depth == 0 && $root_entries > 1) {
+        $complete_tree = TRUE;
+        // This new item at the root level is the first item on the next page.
+        $after_entries++;
+        continue;
+      }
+      if ($page_entries >= $page_increment + $back_peddle) {
+        $forward_peddle++;
+      }
+
+      // Finally, if we've gotten down this far, we're rendering a term on this page.
+      $page_entries++;
+      $term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0;
+      $key = 'tid:'. $term->tid .':'. $term_deltas[$term->tid];
+
+      // Keep track of the first term displayed on this page.
+      if ($page_entries == 1) {
+        $form['#first_tid'] = $term->tid;
+      }
+      // Keep a variable to make sure at least 2 root elements are displayed.
+      if ($term->parents[0] == 0) {
+        $root_entries++;
+      }
+      $current_page[$key] = $term;
+    } while ($term = next($tree));
+
+    // Because we didn't use a pager query, set the necessary pager variables.
+    $total_entries = $before_entries + $page_entries + $after_entries;
+    $pager_total_items[0] = $total_entries;
+    $pager_page_array[0] = $page;
+    $pager_total[0] = ceil($total_entries / $page_increment);
+  }
+
+  // If this form was already submitted once, it's probably hit a validation
+  // error. Ensure the form is rebuilt in the same order as the user submitted.
+  if (!empty($form_state['post'])) {
+    $order = array_flip(array_keys($form_state['post'])); // Get the $_POST order.
+    $current_page = array_merge($order, $current_page); // Update our form with the new order.
+    foreach ($current_page as $key => $term) {
+      // Verify this is a term for the current page and set at the current depth.
+      if (is_array($form_state['post'][$key]) && is_numeric($form_state['post'][$key]['tid'])) {
+        $current_page[$key]->depth = $form_state['post'][$key]['depth'];
+      }
+      else {
+        unset($current_page[$key]);
+      }
     }
+  }
 
-    if (empty($rows)) {
-      $rows[] = array(array('data' => t('No terms available.'), 'colspan' => '2'));
+  // Build the actual form.
+  foreach ($current_page as $key => $term) {
+    // Save the term for the current page so we don't have to load it a second time.
+    $form[$key]['#term'] = (array)$term;
+    if (isset($term->parents)) {
+      $form[$key]['#term']['parent'] = $term->parent = $term->parents[0];
+      unset($form[$key]['#term']['parents'], $term->parents);
     }
 
-    $GLOBALS['pager_page_array'][] = $start_from;  // FIXME
-    $GLOBALS['pager_total'][] = intval($total_entries / $page_increment) + 1; // FIXME
+    $form[$key]['view'] = array('#value' => l($term->name, "taxonomy/term/$term->tid"));
+    if (!$vocabulary->tags && $vocabulary->hierarchy < 2 && count($tree) > 1) {
+      $form['#parent_fields'] = TRUE;
+      $form[$key]['tid'] = array(
+        '#type' => 'hidden',
+        '#value' => $term->tid
+      );
+      $form[$key]['parent'] = array(
+        '#type' => 'hidden',
+        // Yes, default_value on a hidden. It needs to be changeable by the javascript.
+        '#default_value' => $term->parent,
+      );
+      $form[$key]['depth'] = array(
+        '#type' => 'hidden',
+        // Same as above, the depth is modified by javascript, so it's a default_value.
+        '#default_value' => $term->depth,
+      );
+    }
+    $form[$key]['edit'] = array('#value' => l(t('edit'), "admin/content/taxonomy/edit/term/$term->tid", array('query' => drupal_get_destination())));
   }
 
-  $output = theme('table', $header, $rows, array('id' => 'taxonomy'));
-  if ($vocabulary->tags || $total_entries >= $page_increment) {
-    $output .= theme('pager', NULL, $page_increment);
+  $form['#total_entries'] = $total_entries;
+  $form['#page_increment'] = $page_increment;
+  $form['#page_entries'] = $page_entries;
+  $form['#back_peddle'] = $back_peddle;
+  $form['#forward_peddle'] = $forward_peddle;
+  $form['#empty_text'] = t('No terms available.');
+
+  if (!$vocabulary->tags && $vocabulary->hierarchy < 2 && count($tree) > 1) {
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Submit')
+    );
+    $form['reset_alphabetical'] = array(
+      '#type' => 'submit',
+      '#value' => t('Reset to Alphabetical')
+    );
+    $form['destination'] = array(
+      '#type' => 'hidden',
+      '#value' => $_GET['q'] . (isset($_GET['page']) ? '?page='. $_GET['page'] : '')
+    );
   }
 
-  return $output;
+  return $form;
 }
 
+/**
+ * Submit handler for terms overview form.
+ * 
+ * Rather than using a textfield or weight field, this form depends entirely
+ * upon the order of form elements on the page to determine new weights.
+ * 
+ * Because there might be hundreds or thousands of taxonomy terms that need to
+ * be ordered, terms are weighted from 0 to the number of terms in the
+ * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
+ * lowest to highest, but are not necessarily sequential. Numbers may be skipped
+ * when a term has children so that reordering is minimal when a child is
+ * added or removed from a term.
+ * 
+ * @see taxonomy_overview_terms().
+ */
+function taxonomy_overview_terms_submit($form, &$form_state) {
+  if ($form_state['clicked_button']['#value'] == t('Reset to Alphabetical')) {
+    // Execute the term deletion.
+    if ($form_state['values']['reset_alphabetical'] === TRUE) {
+      return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
+    }
+    // Rebuild the form to confirm term deletion.
+    $form_state['rebuild'] = TRUE;
+    $form_state['confirm_reset_alphabetical'] = TRUE;
+    return;
+  }
+
+  $order = array_flip(array_keys($form['#post'])); // Get the $_POST order.
+  $form_state['values'] = array_merge($order, $form_state['values']); // Update our original form with the new order.
+
+  $vocabulary = $form['#vocabulary'];
+  $hierarchy = 0; // Update the current hierarchy type as we go.
+
+  $changed_terms = array();
+  $tree = taxonomy_get_tree($vocabulary['vid']);
+
+  if (empty($tree)) {
+    return;
+  }
+
+  // Build a list of all terms that need to be updated on previous pages.
+  $weight = 0;
+  $term = (array)$tree[0];
+  while ($term['tid'] != $form['#first_tid']) {
+    if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
+      $term['parent'] = $term['parents'][0];
+      $term['weight'] = $weight;
+      $changed_terms[$term['tid']] = $term;
+    }
+    $weight++;
+    $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
+    $term = (array)$tree[$weight];
+  }
+
+  // Renumber the current page weights and assign any new parents.
+  $level_weights = array();
+  foreach ($form_state['values'] as $tid => $values) {
+    if (isset($form[$tid]['#term'])) {
+      $term = $form[$tid]['#term'];
+      // Give terms at the root level a weight in sequence with terms on previous pages.
+      if ($values['parent'] == 0 && $term['weight'] != $weight) {
+        $term['weight'] = $weight;
+        $changed_terms[$term['tid']] = $term;
+      }
+      // Terms not at the root level can safely start from 0 because they're all on this page.
+      elseif ($values['parent'] > 0) {
+        $level_weights[$values['parent']] = isset($level_weights[$values['parent']]) ? $level_weights[$values['parent']] + 1 : 0;
+        if ($level_weights[$values['parent']] != $term['weight']) {
+          $term['weight'] = $level_weights[$values['parent']];
+          $changed_terms[$term['tid']] = $term;
+        }
+      }
+      // Update any changed parents.
+      if ($values['parent'] != $term['parent']) {
+        $term['parent'] = $values['parent'];
+        $changed_terms[$term['tid']] = $term;
+      }
+      $hierarchy = $term['parent'] != 0 ? 1 : $hierarchy;
+      $weight++;
+    }
+  }
+
+  // Build a list of all terms that need to be updated on following pages.
+  for ($weight; $weight < count($tree); $weight++) {
+    $term = (array)$tree[$weight];
+    if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
+      $term['parent'] = $term['parents'][0];
+      $term['weight'] = $weight;
+      $changed_terms[$term['tid']] = $term;
+    }
+    $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
+  }
+
+  // Save all updated terms.
+  foreach ($changed_terms as $term) {
+    taxonomy_save_term($term);
+  }
+
+  // Update the vocabulary hierarchy to flat or single hierarchy.
+  if ($vocabulary['hierarchy'] != $hierarchy) {
+    $vocabulary['hierarchy'] = $hierarchy;
+    taxonomy_save_vocabulary($vocabulary);
+  }
+}
+
+/**
+ * Theme the terms overview as a sortable list of terms.
+ * 
+ * @ingroup themeable
+ * @see taxonomy_overview_terms().
+ */
+function theme_taxonomy_overview_terms($form) {
+  $page_increment  = $form['#page_increment'];
+  $page_entries    = $form['#page_entries'];
+  $back_peddle     = $form['#back_peddle'];
+  $forward_peddle  = $form['#forward_peddle'];
+
+  // Add drag and drop if parent fields are present in the form.
+  if ($form['#parent_fields']) {
+    drupal_add_tabledrag('taxonomy', 'match', 'parent', 'term-parent', 'term-parent', 'term-id', FALSE);
+    drupal_add_tabledrag('taxonomy', 'depth', 'group', 'term-depth', NULL, NULL, FALSE);
+    drupal_add_js(drupal_get_path('module', 'taxonomy') .'/taxonomy.js');
+    drupal_add_js(array('taxonomy' => array('backPeddle' => $back_peddle, 'forwardPeddle' => $forward_peddle)), 'setting');
+    drupal_add_css(drupal_get_path('module', 'taxonomy') .'/taxonomy.css');
+  }
+
+  $errors = form_get_errors() != FALSE ? form_get_errors() : array();
+  $rows = array();
+  foreach (element_children($form) as $key) {
+    if (isset($form[$key]['#term'])) {
+      $term = &$form[$key];
+
+      $row = array();
+      $row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', $term['#term']['depth']) : '') . drupal_render($term['view']);
+      if ($form['#parent_fields']) {
+        $term['tid']['#attributes']['class'] = 'term-id';
+        $term['parent']['#attributes']['class'] = 'term-parent';
+        $term['depth']['#attributes']['class'] = 'term-depth';
+        $row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']);
+      }
+      $row[] = drupal_render($term['edit']);
+
+      $row = array('data' => $row);
+      $rows[$key] = $row;
+    }
+  }
+
+  // Add necessary classes to rows.
+  $row_position = 0;
+  foreach ($rows as $key => $row) {
+    $classes = array();
+    if (isset($form['#parent_fields'])) {
+      $classes[] = 'draggable';
+    }
+
+    // Add classes that mark which terms belong to previous and next pages.
+    if ($row_position < $back_peddle || $row_position >= $page_entries - $forward_peddle) {
+      $classes[] = 'taxonomy-term-preview';
+    }
+
+    if ($row_position !== 0 && $row_position !== count($rows) - 1) {
+      if ($row_position == $back_peddle - 1 || $row_position == $page_entries - $forward_peddle - 1) {
+        $classes[] = 'taxonomy-term-divider-top';
+      }
+      elseif ($row_position == $back_peddle || $row_position == $page_entries - $forward_peddle) {
+        $classes[] = 'taxonomy-term-divider-bottom';
+      }
+    }
+
+    // Add an error class if this row contains a form error.
+    foreach ($errors as $error_key => $error) {
+      if (strpos($error_key, $key) === 0) {
+        $classes[] = 'error';
+      }
+    }
+    $rows[$key]['class'] = implode(' ', $classes);
+    $row_position++;
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '2'));
+  }
+
+  $header = array(t('Name'), t('Operations'));
+  $output = theme('table', $header, $rows, array('id' => 'taxonomy'));
+  $output .= drupal_render($form);
+  $output .= theme('pager', NULL, $page_increment);
+
+  return $output;
+}
 
 /**
  * Menu callback; return the edit form for a new term after setting the title.
@@ -250,6 +626,21 @@ function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
     'tid' => NULL,
     'weight' => 0,
   );
+
+  $parent = array_keys(taxonomy_get_parents($edit['tid']));
+  $form['#term'] = $edit;
+  $form['#term']['parent'] = $parent;
+  $form['#vocabulary'] = (array)$vocabulary;
+  $form['#vocabulary']['nodes'] = drupal_map_assoc($vocabulary->nodes);;
+
+  // Check for confirmation forms.
+  if (isset($form_state['confirm_delete'])) {
+    return array_merge($form, taxonomy_term_confirm_delete($form_state, $edit['tid']));
+  }
+  elseif (isset($form_state['confirm_parents'])) {
+    return array_merge($form, taxonomy_term_confirm_parents($form_state, $vocabulary));
+  }
+
   $form['identification'] = array(
     '#type' => 'fieldset',
     '#title' => t('Identification'),
@@ -272,7 +663,7 @@ function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
     '#type' => 'fieldset',
     '#title' => 'Advanced options',
     '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
+    '#collapsed' => $vocabulary->hierarchy > 1 ? FALSE : TRUE,
   );
 
   // taxonomy_get_tree and taxonomy_get_parents may contain large numbers of
@@ -298,10 +689,12 @@ function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
     '#default_value' => implode("\n", taxonomy_get_synonyms($edit['tid'])),
     '#description' => t('Synonyms of this term, one synonym per line.'));
   $form['advanced']['weight'] = array(
-    '#type' => 'weight',
+    '#type' => 'textfield',
     '#title' => t('Weight'),
+    '#size' => 6,
     '#default_value' => $edit['weight'],
-    '#description' => t('Vocabularies are displayed in ascending order by weight.'));
+    '#description' => t('Terms are displayed in ascending order by weight.'),
+    '#required' => TRUE);
   $form['vid'] = array(
     '#type' => 'value',
     '#value' => $vocabulary->vid);
@@ -312,7 +705,8 @@ function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
   if ($edit['tid']) {
     $form['delete'] = array(
       '#type' => 'submit',
-      '#value' => t('Delete'));
+      '#value' => t('Delete'),
+      '#submit' => array('taxonomy_term_confirm_delete'));
     $form['tid'] = array(
       '#type' => 'value',
       '#value' => $edit['tid']);
@@ -325,9 +719,39 @@ function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
 }
 
 /**
- * Accept the form submission for a taxonomy term and save the result.
+ * Validation handler for the term edit form. Ensure numeric weight values.
+ * 
+ * @see taxonomy_form_term().
+ */
+function taxonomy_form_term_validate($form, &$form_state) {
+  if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
+    form_set_error('weight', t('Weight value must be numeric.'));
+  }
+}
+
+/**
+ * Submit handler to insert or update a term.
+ * 
+ * @see taxonomy_form_term().
  */
 function taxonomy_form_term_submit($form, &$form_state) {
+  if ($form_state['clicked_button']['#value'] == t('Delete')) {
+    // Execute the term deletion.
+    if ($form_state['values']['delete'] === TRUE) {
+      return taxonomy_term_confirm_delete_submit($form, $form_state);
+    }
+    // Rebuild the form to confirm term deletion.
+    $form_state['rebuild'] = TRUE;
+    $form_state['confirm_delete'] = TRUE;
+    return;
+  }
+  // Rebuild the form to confirm enabling multiple parents.
+  elseif ($form_state['clicked_button']['#value'] == t('Save') && !$form['#vocabulary']['tags'] && count($form_state['values']['parent']) > 1 && $form['#vocabulary']['hierarchy'] < 2) {
+    $form_state['rebuild'] = TRUE;
+    $form_state['confirm_parents'] = TRUE;
+    return;
+  }
+
   switch (taxonomy_save_term($form_state['values'])) {
     case SAVED_NEW:
       drupal_set_message(t('Created new term %term.', array('%term' => $form_state['values']['name'])));
@@ -339,11 +763,53 @@ function taxonomy_form_term_submit($form, &$form_state) {
       break;
   }
 
+  if (!$form['#vocabulary']['tags']) {
+    $current_parent_count = count($form_state['values']['parent']);
+    $previous_parent_count = count($form['#term']['parent']);
+    // Root doesn't count if it's the only parent.
+    if ($current_parent_count == 1 && isset($form_state['values']['parent'][''])) {
+      $current_parent_count = 0;
+      $form_state['values']['parent'] = array();
+    }
+  
+    // If the number of parents has been reduced to one or none, do a check on the
+    // parents of every term in the vocabulary value.
+    if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
+      taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
+    }
+    // If we've increased the number of parents and this is a single or flat
+    // hierarchy, update the vocabulary immediately.
+    elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']['hierarchy'] < 2) {
+      $form['#vocabulary']['hierarchy'] = $current_parent_count == 1 ? 1 : 2;
+      taxonomy_save_vocabulary($form['#vocabulary']);
+    }
+  }
+
   $form_state['tid'] = $form_state['values']['tid'];
   $form_state['redirect'] = 'admin/content/taxonomy';
   return;
 }
 
+/**
+ * Form builder for the confirmation of multiple term parents.
+ * 
+ * @ingroup forms
+ * @see taxonomy_form_term().
+ */
+function taxonomy_term_confirm_parents(&$form_state, $vocabulary) {
+  $form = array();
+  foreach (element_children($form_state['values']) as $key) {
+    $form[$key] = array(
+      '#type' => 'value',
+      '#value' => $form_state['values'][$key],
+    );
+  }
+  $question = t('Set multiple term parents?');
+  $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>';
+  $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>';
+  return confirm_form($form, $question, drupal_get_destination(), $description, t('Set multiple parents'));
+}
+
 /**
  * Form builder for the term delete form.
  *
@@ -356,6 +822,7 @@ function taxonomy_term_confirm_delete(&$form_state, $tid) {
   $form['type'] = array('#type' => 'value', '#value' => 'term');
   $form['name'] = array('#type' => 'value', '#value' => $term->name);
   $form['tid'] = array('#type' => 'value', '#value' => $tid);
+  $form['delete'] = array('#type' => 'value', '#value' => TRUE);
   return confirm_form($form,
                   t('Are you sure you want to delete the term %title?',
                   array('%title' => $term->name)),
@@ -365,8 +832,14 @@ function taxonomy_term_confirm_delete(&$form_state, $tid) {
                   t('Cancel'));
 }
 
+/**
+ * Submit handler to delete a term after confirmation.
+ * 
+ * @see taxonomy_term_confirm_delete().
+ */
 function taxonomy_term_confirm_delete_submit($form, &$form_state) {
   taxonomy_del_term($form_state['values']['tid']);
+  taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
   drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name'])));
   watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
   $form_state['redirect'] = 'admin/content/taxonomy';
@@ -394,6 +867,11 @@ function taxonomy_vocabulary_confirm_delete(&$form_state, $vid) {
                   t('Cancel'));
 }
 
+/**
+ * Submit handler to delete a vocabulary after confirmation.
+ * 
+ * @see taxonomy_vocabulary_confirm_delete().
+ */
 function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
   $status = taxonomy_del_vocabulary($form_state['values']['vid']);
   drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
@@ -401,3 +879,37 @@ function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
   $form_state['redirect'] = 'admin/content/taxonomy';
   return;
 }
+
+/**
+ * Form builder to confirm reseting a vocabulary to alphabetical order.
+ * 
+ * @ingroup forms
+ * @see taxonomy_vocabulary_confirm_reset_alphabetical_submit().
+ */
+function taxonomy_vocabulary_confirm_reset_alphabetical(&$form_state, $vid) {
+  $vocabulary = taxonomy_vocabulary_load($vid);
+
+  $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
+  $form['vid'] = array('#type' => 'value', '#value' => $vid);
+  $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
+  $form['reset_alphabetical'] = array('#type' => 'value', '#value' => TRUE);
+  return confirm_form($form,
+                  t('Are you sure you want to reset the vocabulary %title to alphabetical order?',
+                  array('%title' => $vocabulary->name)),
+                  'admin/content/taxonomy/'. $vid,
+                  t('Reseting a vocabulary will discard all custom ordering and sort items alphabetically.'),
+                  t('Reset to Alphabetical'),
+                  t('Cancel'));
+}
+
+/**
+ * Submit handler to reset a vocabulary to alphabetical order after confirmation.
+ * 
+ * @see taxonomy_vocabulary_confirm_reset_alphabetical().
+ */
+function taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, &$form_state) {
+  db_query('UPDATE {term_data} t SET weight = 0 WHERE vid = %d', $form_state['values']['vid']);
+  drupal_set_message(t('Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name'])));
+  watchdog('taxonomy', 'Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
+  $form_state['redirect'] = 'admin/content/taxonomy/'. $form_state['values']['vid'];
+}
diff --git a/modules/taxonomy/taxonomy.css b/modules/taxonomy/taxonomy.css
new file mode 100644
index 0000000000000000000000000000000000000000..483f1ca7b461cdc8e39aee947944a9d4162daf65
--- /dev/null
+++ b/modules/taxonomy/taxonomy.css
@@ -0,0 +1,9 @@
+tr.taxonomy-term-preview {
+  background-color: #EEE;
+}
+tr.taxonomy-term-divider-top {
+  border-bottom: none;
+}
+tr.taxonomy-term-divider-bottom {
+  border-top: 1px dotted #CCC;
+}
\ No newline at end of file
diff --git a/modules/taxonomy/taxonomy.js b/modules/taxonomy/taxonomy.js
new file mode 100644
index 0000000000000000000000000000000000000000..fab014ac7fa0b057c6ab75c4e0fd87bcd7975d6a
--- /dev/null
+++ b/modules/taxonomy/taxonomy.js
@@ -0,0 +1,36 @@
+// $Id $
+
+/**
+ * Move a block in the blocks table from one region to another via select list.
+ *
+ * This behavior is dependent on the tableDrag behavior, since it uses the
+ * objects initialized in that behavior to update the row.
+ */
+Drupal.behaviors.termDrag = function(context) {
+  var table = $('#taxonomy', context);
+  var tableDrag = Drupal.tableDrag.taxonomy; // Get the blocks tableDrag object.
+  var rows = $('tr', table).size();
+
+  // When a row is swapped, keep previous and next page classes set.
+  tableDrag.row.prototype.onSwap = function(swappedRow) {
+    $('tr.taxonomy-term-preview', table).removeClass('taxonomy-term-preview');
+    $('tr.taxonomy-term-divider-top', table).removeClass('taxonomy-term-divider-top');
+    $('tr.taxonomy-term-divider-bottom', table).removeClass('taxonomy-term-divider-bottom');
+
+    if (Drupal.settings.taxonomy.backPeddle) {
+      for (var n = 0; n < Drupal.settings.taxonomy.backPeddle; n++) {
+        $(table[0].tBodies[0].rows[n]).addClass('taxonomy-term-preview');
+      }
+      $(table[0].tBodies[0].rows[Drupal.settings.taxonomy.backPeddle - 1]).addClass('taxonomy-term-divider-top');
+      $(table[0].tBodies[0].rows[Drupal.settings.taxonomy.backPeddle]).addClass('taxonomy-term-divider-bottom');
+    }
+
+    if (Drupal.settings.taxonomy.forwardPeddle) {
+      for (var n = rows - Drupal.settings.taxonomy.forwardPeddle - 1; n < rows - 1; n++) {
+        $(table[0].tBodies[0].rows[n]).addClass('taxonomy-term-preview');
+      }
+      $(table[0].tBodies[0].rows[rows - Drupal.settings.taxonomy.forwardPeddle - 2]).addClass('taxonomy-term-divider-top');
+      $(table[0].tBodies[0].rows[rows - Drupal.settings.taxonomy.forwardPeddle - 1]).addClass('taxonomy-term-divider-bottom');
+    }
+  };
+};
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index 9c4ed8dec0d168a6bee658cf4329af809b18cf64..0857e169b9ec0367e00c8a6a6a40032e8d7904b3 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -24,6 +24,12 @@ function taxonomy_theme() {
     'taxonomy_term_page' => array(
       'arguments' => array('tids' => array(), 'result' => NULL),
     ),
+    'taxonomy_overview_vocabularies' => array(
+      'arguments' => array('form' => array()),
+    ),
+    'taxonomy_overview_terms' => array(
+      'arguments' => array('form' => array()),
+    ),
   );
 }
 
@@ -106,7 +112,8 @@ function taxonomy_menu() {
   $items['admin/content/taxonomy'] = array(
     'title' => 'Taxonomy',
     'description' => 'Manage tagging, categorization, and classification of your content.',
-    'page callback' => 'taxonomy_overview_vocabularies',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('taxonomy_overview_vocabularies'),
     'access arguments' => array('administer taxonomy'),
     'file' => 'taxonomy.admin.inc',
   );
@@ -158,8 +165,8 @@ function taxonomy_menu() {
   );
   $items['admin/content/taxonomy/%taxonomy_vocabulary'] = array(
     'title' => 'List terms',
-    'page callback' => 'taxonomy_overview_terms',
-    'page arguments' => array(3),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('taxonomy_overview_terms', 3),
     'access arguments' => array('administer taxonomy'),
     'type' => MENU_CALLBACK,
     'file' => 'taxonomy.admin.inc',
@@ -241,6 +248,47 @@ function taxonomy_del_vocabulary($vid) {
   return SAVED_DELETED;
 }
 
+/**
+ * Dynamicly check and update the hierarachy flag of a vocabulary.
+ * 
+ * Checks the current parents of all terms in a vocabulary and updates the
+ * vocabularies hierarchy setting to the lowest possible level. A hierarchy with
+ * no parents in any of its terms will be given a hierarchy of 0. If terms
+ * contain at most a single parent, the vocabulary will be given a hierarchy of
+ * 1. If any term contain multiple parents, the vocabulary will be given a
+ * hieararchy of 2.
+ * 
+ * @param $vocabulary
+ *   An array of the vocabulary structure.
+ * @param $changed_term
+ *   An array of the term structure that was updated.
+ */
+function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
+  $tree = taxonomy_get_tree($vocabulary['vid']);
+  $hierarchy = 0;
+  foreach ($tree as $term) {
+    // Update the changed term with the new parent value before comparision.
+    if ($term->tid == $changed_term['tid']) {
+      $term = (object)$changed_term;
+      $term->parents = $term->parent;
+    }
+    // Check this term's parent count.
+    if (count($term->parents) > 1) {
+      $hierarchy = 2;
+      break;
+    }
+    elseif (count($term->parents) == 1 && 0 !== array_shift($term->parents)) {
+      $hierarchy = 1;
+    }
+  }
+  if ($hierarchy != $vocabulary['hierarchy']) {
+    $vocabulary['hierarchy'] = $hierarchy;
+    taxonomy_save_vocabulary($vocabulary);
+  }
+
+  return $hierarchy;
+}
+
 /**
  * Helper function for taxonomy_form_term_submit().
  *
@@ -412,7 +460,7 @@ function taxonomy_get_vocabularies($type = NULL) {
     // If no node types are associated with a vocabulary, the LEFT JOIN will
     // return a NULL value for type.
     if (isset($voc->type)) {
-      $node_types[$voc->vid][] = $voc->type;
+      $node_types[$voc->vid][$voc->type] = $voc->type;
       unset($voc->type);
       $voc->nodes = $node_types[$voc->vid];
     }
@@ -928,7 +976,7 @@ function taxonomy_vocabulary_load($vid) {
     $node_types = array();
     while ($voc = db_fetch_object($result)) {
       if (!empty($voc->type)) {
-        $node_types[] = $voc->type;
+        $node_types[$voc->type] = $voc->type;
       }
       unset($voc->type);
       $voc->nodes = $node_types;
@@ -1182,6 +1230,19 @@ function taxonomy_help($path, $arg) {
       return $output;
     case 'admin/content/taxonomy':
       return '<p>'. t("The taxonomy module allows you to categorize your content using both tags and administrator defined terms. It is a flexible tool for classifying content with many advanced features. To begin, create a 'Vocabulary' to hold one set of terms or tags. You can create one free-tagging vocabulary for everything, or seperate controlled vocabularies to define the various properties of your content, for example 'Countries' or 'Colours'.") .'</p>';
+    case 'admin/content/taxonomy/%':
+      $vocabulary = taxonomy_vocabulary_load($arg[3]);
+      if ($vocabulary->tags) {
+        return '<p>'. t('%capital_name is a free-tagging vocabulary. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name))) .'</p>';
+      }
+      switch ($vocabulary->hierarchy) {
+        case 0:
+          return '<p>'. t('%capital_name is a flat vocabulary. You may organize the terms in the %name vocabulary by using the handles on the left side of the table. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) .'</p>';
+        case 1:
+          return '<p>'. t('%capital_name is a single hierarchy vocabulary. You may organize the terms in the %name vocabulary by using the handles on the left side of the table. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) .'</p>';
+        case 2:
+          return '<p>'. t('%capital_name is a multiple hierarchy vocabulary. To change the name or description of a term, click the <em>edit</em> link next to the term. Drag and drop of multiple hierarchies is not supported, but you can re-enable drag and drop support by editing each term to include only a single parent.', array('%capital_name' => drupal_ucfirst($vocabulary->name))) .'</p>';
+      }
     case 'admin/content/taxonomy/add/vocabulary':
       return '<p>'. t('Define how your vocabulary will be presented to administrators and users, and which content types to categorize with it. Tags allows users to create terms when submitting posts by typing a comma separated list. Otherwise terms are chosen from a select list and can only be created by users with the "administer taxonomy" permission.') .'</p>';
   }
diff --git a/themes/garland/fix-ie.css b/themes/garland/fix-ie.css
index 47812ee52e981fc8422c7f13081a1fb530011d9d..fa5ff7c5257a88a64199a8799406a32c5fb33baa 100644
--- a/themes/garland/fix-ie.css
+++ b/themes/garland/fix-ie.css
@@ -59,6 +59,10 @@ tr.menu-disabled {
   height: 1em;
 }
 
+tr.taxonomy-term-preview {
+  filter: alpha(opacity=50);
+}
+
 #attach-hide label, #uploadprogress div.message {
   /* Fading elements in IE causes the text to bleed unless they have a background. */
   background-color: #ffffff;
diff --git a/themes/garland/style.css b/themes/garland/style.css
index 01762cfac47f7e338f1cbabe39de518e4bd1a0bf..4210932f679da530c5ac41bf6e44659b5fc7237c 100644
--- a/themes/garland/style.css
+++ b/themes/garland/style.css
@@ -976,6 +976,18 @@ tr.selected td a:link, tr.selected td a:visited, tr.selected td a:active {
   color: #d3e7f4;
 }
 
+tr.taxonomy-term-preview {
+  opacity: 0.5;
+}
+
+tr.taxonomy-term-divider-top {
+  border-bottom: none;
+}
+
+tr.taxonomy-term-divider-bottom {
+  border-top: 1px dotted #CCC;
+}
+
 /**
  * CSS support
  */
@@ -1002,8 +1014,9 @@ div.status {
   border-color: #c7f2c8;
 }
 
-div.error {
+div.error, tr.error {
   color: #c52020;
+  background-color: #FFCCCC;
 }
 
 .form-item input.error, .form-item textarea.error {