Index: pathauto.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto.admin.inc,v
retrieving revision 1.20.2.6
diff -u -p -r1.20.2.6 pathauto.admin.inc
--- pathauto.admin.inc	3 Mar 2010 05:09:02 -0000	1.20.2.6
+++ pathauto.admin.inc	7 Mar 2010 03:27:03 -0000
@@ -79,15 +79,6 @@ function pathauto_admin_settings() {
     '#description' => t('Maximum text length of any component in the alias (e.g., [title]). 100 is the recommended length. @max is the maximum possible length. See <a href="@pathauto-help">Pathauto help</a> for details.', array('@pathauto-help' => url('admin/help/pathauto'), '@max' => _pathauto_get_schema_alias_maxlength())),
   );
 
-  $form['general']['pathauto_max_bulk_update'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Maximum number of objects to alias in a bulk update'),
-    '#size' => 4,
-    '#maxlength' => 4,
-    '#default_value' => variable_get('pathauto_max_bulk_update', 50),
-    '#description' => t('Maximum number of objects of a given type which should be aliased during a bulk update. The default is 50 and the recommended number depends on the speed of your server. If bulk updates "time out" or result in a "white screen" then reduce the number.'),
-  );
-
   $actions = array(
     t('Do nothing. Leave the old alias intact.'),
     t('Create a new alias. Leave the existing alias functioning.'),
@@ -172,10 +163,8 @@ function pathauto_admin_settings() {
 
   // Call the hook on all modules - an array of 'settings' objects is returned
   $all_settings = module_invoke_all('pathauto', 'settings');
-  $modulelist = '';
-  $indexcount = 0;
+  $modulelist = array();
   foreach ($all_settings as $settings) {
-    $items = '';
     $module = $settings->module;
     $modulelist[] = $module;
     $patterndescr = $settings->patterndescr;
@@ -254,22 +243,6 @@ function pathauto_admin_settings() {
       '#value' => $doc,
     );
 
-    // If the module supports bulk updates, offer the update action here
-    if ($settings->bulkname) {
-      $variable = 'pathauto_' . $module . '_bulkupdate';
-      if (variable_get($variable, FALSE)) {
-        variable_set($variable, FALSE);
-        $function = $module . '_pathauto_bulkupdate';
-        call_user_func($function);
-      }
-      $form[$module][$variable] = array(
-        '#type' => 'checkbox',
-        '#title' => $settings->bulkname,
-        '#default_value' => FALSE,
-        '#description' => $settings->bulkdescr,
-      );
-    }
-
     // If the module supports feeds, offer to generate aliases for them
     if ($supportsfeeds) {
       $variable = 'pathauto_' . $module . '_applytofeeds';
@@ -288,13 +261,6 @@ function pathauto_admin_settings() {
         '#description' => t('The text to use for aliases for RSS feeds. Examples are "0/feed" (used throughout Drupal core) and "feed" (used by some contributed Drupal modules, like Views).'),
       );
     }
-
-  }
-
-  if (isset($do_index_bulkupdate) && $do_index_bulkupdate) {
-    drupal_set_message(format_plural($indexcount,
-      'Bulk generation of index aliases completed, one alias generated.',
-      'Bulk generation of index aliases completed, @count aliases generated.'));
   }
 
   // Keep track of which modules currently support pathauto
@@ -388,6 +354,105 @@ function pathauto_admin_settings_validat
 }
 
 /**
+ * Form contructor for path alias bulk update form.
+ */
+function pathauto_admin_update() {
+  _pathauto_include();
+
+  $form['#tree'] = TRUE;
+
+  $modules = variable_get('pathauto_modulelist', array());
+  foreach ($modules as $module) {
+    $settings = module_invoke($module, 'pathauto', 'settings');
+    if (empty($settings) || empty($settings->bulkname)) {
+      continue;
+    }
+    $form['bulk_update'][$module] = array(
+      '#type' => 'checkbox',
+      '#title' => $settings->bulkname,
+      '#default_value' => FALSE,
+    );
+    if (!empty($settings->bulkdescr)) {
+      $form['bulk_update'][$module]['#description'] = $settings->bulkdescr;
+    }
+  }
+
+  $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Update'));
+
+  return $form;
+}
+
+/**
+ * Form submit handler for path alias bulk update form.
+ */
+function pathauto_admin_update_submit($form, &$form_state) {
+  // Disable pathauto verbose mode; backup current value to restore it later.
+  // In case a previous bulk update failed for any reason, the temporary
+  // variable will be set already.
+  $mode = variable_get('pathauto_batch_verbose', NULL);
+  if (!isset($mode)) {
+    variable_set('pathauto_batch_verbose', variable_get('pathauto_verbose', FALSE));
+    variable_set('pathauto_verbose', FALSE);
+  }
+
+  $path = drupal_get_path('module', 'pathauto');
+
+  $batch = array(
+    'title' => t('Bulk updating path aliases'),
+    'operations' => array(),
+    'finished' => 'pathauto_admin_update_batch_finished',
+    'file' => $path . '/pathauto.admin.inc',
+  );
+  foreach ($form_state['values']['bulk_update'] as $module => $value) {
+    if (!empty($value)) {
+      $batch['operations'][] = array('pathauto_admin_update_batch_process', array($module));
+    }
+  }
+  batch_set($batch);
+}
+
+/**
+ * Common batch processing callback for all operations.
+ *
+ * Required to load our include files.
+ *
+ * @todo Allow other modules to also load an include file?
+ */
+function pathauto_admin_update_batch_process($module, &$context) {
+  _pathauto_include();
+
+  if (!isset($context['results']['count'])) {
+    $context['results']['count'] = 0;
+  }
+
+  $function = $module . '_pathauto_batch_update';
+  $function($context);
+}
+
+/**
+ * Batch finished callback.
+ */
+function pathauto_admin_update_batch_finished($success, $results, $operations) {
+  // This finished callback also needs to be invoked in case nothing happened,
+  // so we can restore the verbose mode below.
+  if (isset($results['count'])) {
+    if ($success) {
+      $message = format_plural($results['count'], 'One alias generated.', '@count aliases generated.');
+      $type = 'success';
+    }
+    else {
+      $message = t('Finished with errors.');
+      $type = 'error';
+    }
+    drupal_set_message($message, $type);
+  }
+
+  // Restore verbose mode setting.
+  variable_set('pathauto_verbose', variable_get('pathauto_batch_verbose', FALSE));
+  variable_del('pathauto_batch_verbose');
+}
+
+/**
  * Menu callback; select certain alias types to delete.
  */
 function pathauto_admin_delete() {
@@ -472,8 +537,8 @@ function pathauto_admin_delete_submit($f
  */
 function _pathauto_warn_wysiwygs() {
   $wysiwygs = array(
-    'bueditor', 'fckeditor', 'htmlarea', 'htmlbox', 'tinymce', 'whizzywig',
-    'widgeditor', 'wymeditor', 'wysiwyg', 'xstandard', 'yui_editor',
+    'bueditor', 'ckeditor', 'fckeditor', 'htmlarea', 'htmlbox', 'tinymce', 'whizzywig',
+    'widgeditor', 'wymeditor', 'xstandard', 'yui_editor',
   );
 
   $wysiwyg_problem = FALSE;
Index: pathauto.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto.install,v
retrieving revision 1.14
diff -u -p -r1.14 pathauto.install
--- pathauto.install	17 Oct 2009 17:36:58 -0000	1.14
+++ pathauto.install	3 Mar 2010 00:05:15 -0000
@@ -108,3 +108,12 @@ function pathauto_update_7() {
   }
   return $ret;
 }
+
+/**
+ * Remove the maximum aliases for bulk updates variable (processing via batch now).
+ */
+function pathauto_update_6201() {
+  $ret = array();
+  variable_del('pathauto_max_bulk_update');
+  return $ret;
+}
Index: pathauto.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto.module,v
retrieving revision 1.126.2.19
diff -u -p -r1.126.2.19 pathauto.module
--- pathauto.module	2 Mar 2010 23:04:09 -0000	1.126.2.19
+++ pathauto.module	3 Mar 2010 00:07:50 -0000
@@ -69,7 +69,14 @@ function pathauto_menu() {
     'page arguments' => array('pathauto_admin_delete'),
     'access arguments' => array('administer url aliases'),
     'type' => MENU_LOCAL_TASK,
-    'weight' => 11,
+    'file' => 'pathauto.admin.inc',
+  );
+  $items['admin/build/path/update_bulk'] = array(
+    'title' => 'Bulk update',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('pathauto_admin_update'),
+    'access arguments' => array('administer url aliases'),
+    'type' => MENU_LOCAL_TASK,
     'file' => 'pathauto.admin.inc',
   );
 
@@ -112,14 +119,17 @@ function pathauto_path_alias_types() {
   if (module_exists('blog')) {
     $objects['blog/'] = t('User blogs');
   }
+  if (module_exists('contact')) {
+    $objects['user/%/contact'] = t('User contact forms');
+  }
   if (module_exists('taxonomy')) {
-    $objects['taxonomy/'] = t('Vocabularies and terms');
+    $objects['taxonomy/term/'] = t('Vocabularies and terms');
   }
   if (module_exists('tracker')) {
     $objects['user/%/track'] = t('User trackers');
   }
   if (module_exists('forum')) {
-    $objects['forum/%'] = t('Forums');
+    $objects['forum/'] = t('Forums');
   }
   return $objects;
 }
Index: pathauto.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto.test,v
retrieving revision 1.1.4.5
diff -u -p -r1.1.4.5 pathauto.test
--- pathauto.test	3 Mar 2010 00:38:56 -0000	1.1.4.5
+++ pathauto.test	7 Mar 2010 03:27:03 -0000
@@ -193,3 +193,48 @@ class PathautoBookTokenTestCase extends 
     $this->assertNodeAlias($sub_child_node1, 'root/sub-page1/sub-sub-page1');
   }
 }
+
+/**
+ * Bulk update functionality tests.
+ */
+class PathautoBulkUpdateTestCase extends PathautoTestHelper {
+  public static function getInfo() {
+    return array(
+      'name' => 'Bulk updates',
+      'description' => 'Tests bulk updating of URL aliases.',
+      'group' => 'Pathauto',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+  }
+
+  function testBulkCreate() {
+    // Remove node page pattern.
+    variable_del('pathauto_node_page_pattern');
+    variable_del('pathauto_node_pattern');
+
+    // Create some nodes.
+    $this->nodes = array();
+    foreach (range(0, 9) as $num) {
+      $this->nodes[$num] = $this->drupalCreateNode(array('type' => 'page'));
+      // Verify that there is no alias after creation.
+      $path = 'node/' . $this->nodes[$num]->nid;
+      $alias = drupal_lookup_path('alias', $path);
+      $this->assertFalse($alias);
+    }
+
+    // Setup node page pattern.
+    variable_set('pathauto_node_page_pattern', 'content/[title-raw]');
+
+    // Bulk create aliases.
+    $edit = array(
+      'bulk_update[node]' => TRUE,
+    );
+    $this->drupalPost('admin/build/path/update_bulk', $edit, t('Update'));
+    $message = format_plural(count($this->nodes), 'One alias generated.', '@count aliases generated.');
+    $this->assertText($message);
+  }
+}
+
Index: pathauto_node.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto_node.inc,v
retrieving revision 1.48.2.1
diff -u -p -r1.48.2.1 pathauto_node.inc
--- pathauto_node.inc	3 Mar 2010 05:09:02 -0000	1.48.2.1
+++ pathauto_node.inc	7 Mar 2010 03:27:59 -0000
@@ -20,8 +20,7 @@ function node_pathauto($op) {
       $settings['groupheader'] = t('Node paths');
       $settings['patterndescr'] = t('Default path pattern (applies to all node types with blank patterns below)');
       $settings['patterndefault'] = t('content/[title-raw]');
-      $settings['bulkname'] = t('Bulk generate aliases for nodes that are not aliased');
-      $settings['bulkdescr'] = t('Generate aliases for all existing nodes which do not already have aliases.');
+      $settings['bulkname'] = t('Content');
 
       $patterns = token_get_list('node');
       foreach ($patterns as $type => $pattern_set) {
@@ -62,63 +61,86 @@ function node_pathauto($op) {
 }
 
 /**
- * Generate aliases for all nodes without aliases.
+ * Batch processing callback; Generate aliases for nodes.
  */
-function node_pathauto_bulkupdate() {
-  // From all node types, only attempt to update those with patterns
-  $pattern_types = array();
-
-  // If there's a default pattern we assume all types might be updated.
-  if (trim(variable_get('pathauto_node_pattern', ''))) {
-    $pattern_types = array_keys(node_get_types('names'));
-  }
-  else {
-    // Check first for a node specific pattern...
-    $languages = array();
-    if (module_exists('locale')) {
-      $languages = array('' => t('Language neutral')) + locale_language_list('name');
-    }
-    foreach (array_keys(node_get_types('names')) as $type) {
-      if (trim(variable_get('pathauto_node_'. $type .'_pattern', ''))) {
-        $pattern_types[$type] = $type;
-        continue;
-      }
-      // ...then for a node-language pattern.
-      if (variable_get('language_content_type_'. $type, 0) && $languages) {
-        foreach ($languages as $lang_code => $lang_name) {
-          if (trim(variable_get('pathauto_node_'. $type .'_'. $lang_code .'_pattern', ''))) {
-            $pattern_types[$type] = $type;
-            continue 2;
+function node_pathauto_batch_update(&$context) {
+  // Prepare query.
+  if (!isset($context['sandbox']['query'])) {
+    // From all node types, only attempt to update those with patterns
+    $pattern_types = array();
+    // If there's a default pattern we assume all types might be updated.
+    if (trim(variable_get('pathauto_node_pattern', ''))) {
+      $pattern_types = array_keys(node_get_types('names'));
+    }
+    else {
+      // Check first for a node specific pattern...
+      $languages = array();
+      if (module_exists('locale')) {
+        $languages = array('' => t('Language neutral')) + locale_language_list('name');
+      }
+      foreach (array_keys(node_get_types('names')) as $type) {
+        if (trim(variable_get('pathauto_node_'. $type .'_pattern', ''))) {
+          $pattern_types[$type] = $type;
+          continue;
+        }
+        // ...then for a node-language pattern.
+        if (variable_get('language_content_type_'. $type, 0) && $languages) {
+          foreach ($languages as $lang_code => $lang_name) {
+            if (trim(variable_get('pathauto_node_'. $type .'_'. $lang_code .'_pattern', ''))) {
+              $pattern_types[$type] = $type;
+              continue 2;
+            }
           }
         }
       }
     }
+    if (!$pattern_types) {
+      $context['finished'] = 1;
+      return;
+    }
+
+    $context['sandbox']['query'] = "FROM {node} n LEFT JOIN {url_alias} alias ON CONCAT('node/', CAST(n.nid AS CHAR)) = alias.src WHERE alias.src IS NULL AND n.type IN (" . db_placeholders($pattern_types, 'varchar') . ')';
+    $context['sandbox']['query_args'] = $pattern_types;
   }
 
-  $count = 0;
-  if (count($pattern_types)) {
-    $query = "SELECT n.nid, n.vid, n.type, n.title, n.uid, n.created, n.language, alias.src, alias.dst FROM {node} n LEFT JOIN {url_alias} alias ON CONCAT('node/', CAST(n.nid AS CHAR)) = alias.src WHERE alias.src IS NULL AND n.type IN (". db_placeholders($pattern_types, 'varchar') .')';
-    $result = db_query_range($query, $pattern_types, 0, variable_get('pathauto_max_bulk_update', 50));
-
-    $placeholders = array();
-    while ($node_ref = db_fetch_object($result)) {
-      $node = node_load($node_ref->nid, NULL, TRUE);
-      $node->src = $node_ref->src;
-      $node->dst = $node_ref->dst;
-      if (module_exists('taxonomy')) {
-        // Must populate the terms for the node here for the category
-        // placeholders to work
-        $node->taxonomy = array_keys(taxonomy_node_get_terms($node));
-      }
-      $placeholders = pathauto_get_placeholders('node', $node);
-      $source = "node/$node->nid";
-      if (pathauto_create_alias('node', 'bulkupdate', $placeholders, $source, $node->nid, $node->type, $node->language)) {
-        $count++;
-      }
+  // Get the total amount of items to process.
+  if (!isset($context['sandbox']['total'])) {
+    $context['sandbox']['total'] = db_result(db_query("SELECT COUNT(nid) " . $context['sandbox']['query'], $context['sandbox']['query_args']));
+    $context['sandbox']['count'] = 0;
+    $context['sandbox']['items'] = array();
+  }
+
+  // Get the next stack of items to process.
+  if (empty($context['sandbox']['items'])) {
+    $query = "SELECT n.nid, alias.src, alias.dst " . $context['sandbox']['query'];
+    $result = db_query_range($query, $context['sandbox']['query_args'], $context['sandbox']['count'], 1000);
+    while ($row = db_fetch_object($result)) {
+      $context['sandbox']['items'][] = $row;
+    }
+  }
+
+  // Process the next item in the stack.
+  if ($row = array_shift($context['sandbox']['items'])) {
+    $context['sandbox']['count']++;
+
+    $node = node_load($row->nid, NULL, TRUE);
+    $node->src = $row->src;
+    $node->dst = $row->dst;
+    // Must populate the terms for the node here for category placeholders.
+    if (module_exists('taxonomy')) {
+      $node->taxonomy = array_keys(taxonomy_node_get_terms($node));
+    }
+    $placeholders = pathauto_get_placeholders('node', $node);
+    $src = "node/$node->nid";
+    if (pathauto_create_alias('node', 'bulkupdate', $placeholders, $src, $node->nid, $node->type, $node->language)) {
+      $type = node_get_types('name', $node);
+      $context['message'] = t('Updated alias for @type %title', array('@type' => $type, '%title' => $node->title));
+      // Update total counter.
+      $context['results']['count']++;
     }
   }
 
-  drupal_set_message(format_plural($count,
-    'Bulk generation of nodes completed, one alias generated.',
-    'Bulk generation of nodes completed, @count aliases generated.'));
+  if ($context['sandbox']['total'] > 0) {
+    $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+  }
 }
Index: pathauto_taxonomy.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto_taxonomy.inc,v
retrieving revision 1.41.2.2
diff -u -p -r1.41.2.2 pathauto_taxonomy.inc
--- pathauto_taxonomy.inc	3 Mar 2010 05:09:02 -0000	1.41.2.2
+++ pathauto_taxonomy.inc	7 Mar 2010 03:28:13 -0000
@@ -30,8 +30,7 @@ function taxonomy_pathauto($op) {
       }
 
       $settings['supportsfeeds'] = '0/feed';
-      $settings['bulkname'] = t('Bulk generate aliases for terms that are not aliased');
-      $settings['bulkdescr'] = t('Generate aliases for all existing terms which do not already have aliases.');
+      $settings['bulkname'] = t('Taxonomy terms');
 
       $vocabularies = taxonomy_get_vocabularies();
       if (sizeof($vocabularies) > 0) {
@@ -52,41 +51,73 @@ function taxonomy_pathauto($op) {
 }
 
 /**
- * Generate aliases for all categories without aliases.
+ * Batch processing callback; Generate aliases for taxonomy terms.
  */
-function taxonomy_pathauto_bulkupdate() {
-  // From all node types, only attempt to update those with patterns
-  $pattern_vids = array();
-  foreach (taxonomy_get_vocabularies() as $vid => $info) {
-    // TODO - If there's a default we shouldn't do this crazy where statement because all vocabularies get aliases.
-    // TODO - Special casing to exclude the forum vid (and the images vid and...?).
-    if (pathauto_pattern_load_by_entity('taxonomy', $vid)) {
-      $pattern_vids[] = $vid;
-      if (empty($vid_where)) {
-        $vid_where = " AND (vid = '%s' ";
+function taxonomy_pathauto_batch_update(&$context) {
+  // Prepare query.
+  if (!isset($context['sandbox']['query'])) {
+    // From all node types, only attempt to update those with patterns
+    $pattern_vids = array();
+    foreach (taxonomy_get_vocabularies() as $vid => $info) {
+      $pattern = trim(variable_get('pathauto_taxonomy_'. $vid .'_pattern', ''));
+
+      // If it's not set, check the default
+      // TODO - if there's a default we shouldn't do this crazy where statement because all vocabs get aliases
+      // TODO - special casing to exclude the forum vid (and the images vid and...?)
+      if (empty($pattern)) {
+        $pattern = trim(variable_get('pathauto_taxonomy_pattern', ''));
       }
-      else {
-        $vid_where .= " OR vid = '%s'";
+      if (!empty($pattern)) {
+        $pattern_vids[] = $vid;
+        if (empty($vid_where)) {
+          $vid_where = " AND (vid = '%s' ";
+        }
+        else {
+          $vid_where .= " OR vid = '%s'";
+        }
       }
     }
+    $vid_where .= ')';
+
+    // Exclude the forums and join all the args into one array so they can be passed to db_query
+    $forum_vid[] = variable_get('forum_nav_vocabulary', '');
+    $query_args = array_merge($forum_vid, $pattern_vids);
+
+    $context['sandbox']['query'] = "FROM {term_data} LEFT JOIN {url_alias} ON CONCAT('taxonomy/term/', CAST(tid AS CHAR)) = src WHERE src IS NULL AND vid <> %d " . $vid_where;
+    $context['sandbox']['query_args'] = $query_args;
   }
-  $vid_where .= ')';
 
-  // Exclude the forums and join all the args into one array so they can be passed to db_query
-  $forum_vid[] = variable_get('forum_nav_vocabulary', '');
-  $query_args = array_merge($forum_vid, $pattern_vids);
-  $query = "SELECT tid, vid, name, src, dst FROM {term_data} LEFT JOIN {url_alias} ON CONCAT('taxonomy/term/', CAST(tid AS CHAR)) = src WHERE src IS NULL AND vid <> %d ". $vid_where;
-  $result = db_query_range($query, $query_args, 0, variable_get('pathauto_max_bulk_update', 50));
+  // Get the total amount of items to process.
+  if (!isset($context['sandbox']['total'])) {
+    $context['sandbox']['total'] = db_result(db_query("SELECT COUNT(tid) " . $context['sandbox']['query'], $context['sandbox']['query_args']));
+    $context['sandbox']['count'] = 0;
+    $context['sandbox']['items'] = array();
+  }
 
-  $count = 0;
-  $placeholders = array();
-  while ($category = db_fetch_object($result)) {
-    $count += _taxonomy_pathauto_alias($category, 'bulkupdate');
+  // Get the next stack of items to process.
+  if (empty($context['sandbox']['items'])) {
+    $query = "SELECT tid " . $context['sandbox']['query'];
+    $result = db_query_range($query, $context['sandbox']['query_args'], $context['sandbox']['count'], 1000);
+    while ($uid = db_result($result)) {
+      $context['sandbox']['items'][] = $uid;
+    }
+  }
+
+  // Process the next item in the stack.
+  if ($tid = array_shift($context['sandbox']['items'])) {
+    $context['sandbox']['count']++;
+
+    $category = taxonomy_get_term($tid);
+    if (_taxonomy_pathauto_alias($category, 'bulkupdate')) {
+      $context['message'] = t('Updated alias for term %title', array('%title' => $category->name));
+      // Update total counter.
+      $context['results']['count']++;
+    }
   }
 
-  drupal_set_message(format_plural($count,
-    'Bulk generation of terms completed, one alias generated.',
-    'Bulk generation of terms completed, @count aliases generated.'));
+  if ($context['sandbox']['total'] > 0) {
+    $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+  }
 }
 
 /**
@@ -140,8 +171,8 @@ function forum_pathauto($op) {
       }
 
       $settings['supportsfeeds'] = '0/feed';
-      $settings['bulkname'] = t('Bulk generate aliases for forum paths that are not aliased');
-      $settings['bulkdescr'] = t('Generate aliases for all existing forums and forum containers which do not already have aliases.');
+      $settings['bulkname'] = t('Forums');
+      $settings['bulkdescr'] = t('Forum containers and forums are handled separately from taxonomy terms.');
       return (object) $settings;
     default:
       break;
@@ -149,20 +180,47 @@ function forum_pathauto($op) {
 }
 
 /**
- * Generate aliases for all forums and forum containers without aliases.
+ * Batch processing callback; Generate aliases for forums.
  */
-function forum_pathauto_bulkupdate() {
-  $forum_vid = variable_get('forum_nav_vocabulary', '');
-  $query = "SELECT tid, vid, name, src, dst FROM {term_data} LEFT JOIN {url_alias} ON CONCAT('forum/', CAST(tid AS CHAR)) = src WHERE vid = %d AND src IS NULL";
-  $result = db_query_range($query, $forum_vid, 0, variable_get('pathauto_max_bulk_update', 50));
+function forum_pathauto_batch_update(&$context) {
+  // Prepare query.
+  if (!isset($context['sandbox']['query'])) {
+    $forum_vid = variable_get('forum_nav_vocabulary', '');
+
+    $context['sandbox']['query'] = "FROM {term_data} LEFT JOIN {url_alias} ON CONCAT('forum/', CAST(tid AS CHAR)) = src WHERE vid = %d AND src IS NULL";
+    $context['sandbox']['query_args'] = array($forum_vid);
+  }
+
+  // Get the total amount of items to process.
+  if (!isset($context['sandbox']['total'])) {
+    $context['sandbox']['total'] = db_result(db_query("SELECT COUNT(tid) " . $context['sandbox']['query'], $context['sandbox']['query_args']));
+    $context['sandbox']['count'] = 0;
+    $context['sandbox']['items'] = array();
+  }
+
+  // Get the next stack of items to process.
+  if (empty($context['sandbox']['items'])) {
+    $query = "SELECT tid " . $context['sandbox']['query'];
+    $result = db_query_range($query, $context['sandbox']['query_args'], $context['sandbox']['count'], 1000);
+    while ($tid = db_result($result)) {
+      $context['sandbox']['items'][] = $tid;
+    }
+  }
 
-  $count = 0;
-  $placeholders = array();
-  while ($category = db_fetch_object($result)) {
-    $count = _taxonomy_pathauto_alias($category, 'bulkupdate') + $count;
+  // Process the next item in the stack.
+  if ($tid = array_shift($context['sandbox']['items'])) {
+    $context['sandbox']['count']++;
+
+    $category = taxonomy_get_term($tid);
+    if (_taxonomy_pathauto_alias($category, 'bulkupdate')) {
+      $context['message'] = t('Updated alias for forum %title', array('%title' => $category->name));
+      // Update total counter.
+      $context['results']['count']++;
+    }
   }
 
-  drupal_set_message(format_plural($count,
-    'Bulk update of forums and forum containers completed, one alias generated.',
-    'Bulk update of forums and forum containers completed, @count aliases generated.'));
+  if ($context['sandbox']['total'] > 0) {
+    $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+  }
 }
+
Index: pathauto_user.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/pathauto/pathauto_user.inc,v
retrieving revision 1.31.2.1
diff -u -p -r1.31.2.1 pathauto_user.inc
--- pathauto_user.inc	3 Mar 2010 05:09:02 -0000	1.31.2.1
+++ pathauto_user.inc	7 Mar 2010 03:30:18 -0000
@@ -29,8 +29,7 @@ function user_pathauto($op) {
         }
       }
 
-      $settings['bulkname'] = t('Bulk generate aliases for users that are not aliased');
-      $settings['bulkdescr'] = t('Generate aliases for all existing user account pages which do not already have aliases.');
+      $settings['bulkname'] = t('Users');
       return (object) $settings;
     default:
       break;
@@ -56,8 +55,7 @@ function blog_pathauto($op) {
       }
 
       $settings['supportsfeeds'] = 'feed';
-      $settings['bulkname'] = t('Bulk generate aliases for blogs that are not aliased');
-      $settings['bulkdescr'] = t('Generate aliases for all existing blog pages which do not already have aliases.');
+      $settings['bulkname'] = t('User blogs');
       return (object) $settings;
     default:
       break;
@@ -82,8 +80,7 @@ function tracker_pathauto($op) {
       }
 
       $settings['supportsfeeds'] = 'feed';
-      $settings['bulkname'] = t('Bulk generate aliases for user-tracker paths that are not aliased');
-      $settings['bulkdescr'] = t('Generate aliases for all existing user-tracker pages which do not already have aliases.');
+      $settings['bulkname'] = t('User trackers');
       return (object) $settings;
     default:
       break;
@@ -106,9 +103,7 @@ function contact_pathauto($op) {
       foreach ($patterns['user'] as $pattern => $description) {
         $settings['placeholders']['['. $pattern .']'] = $description;
       }
-
-      $settings['bulkname'] = t('Bulk generate aliases for user contact form paths that are not aliased');
-      $settings['bulkdescr'] = t('Generate aliases for all existing user contact form pages which do not already have aliases.');
+      $settings['bulkname'] = t('User contact forms');
       return (object) $settings;
     default:
       break;
@@ -116,91 +111,160 @@ function contact_pathauto($op) {
 }
 
 /**
- * Bulk generate aliases for all users without aliases.
+ * Batch processing callback; Generate aliases for users.
  */
-function user_pathauto_bulkupdate() {
-  $query = "SELECT uid, name, src, dst FROM {users} LEFT JOIN {url_alias} ON CONCAT('user/', CAST(uid AS CHAR)) = src WHERE uid > 0 AND src IS NULL";
-  $result = db_query_range($query, 0, variable_get('pathauto_max_bulk_update', 50));
+function user_pathauto_batch_update(&$context) {
+  // Get the total amount of items to process.
+  if (!isset($context['sandbox']['total'])) {
+    $context['sandbox']['total'] = db_result(db_query("SELECT COUNT(uid) FROM {users} LEFT JOIN {url_alias} ON CONCAT('user/', CAST(uid AS CHAR)) = src WHERE uid > 0 AND src IS NULL"));
+    $context['sandbox']['count'] = 0;
+    $context['sandbox']['items'] = array();
+  }
+
+  // Get the next stack of items to process.
+  if (empty($context['sandbox']['items'])) {
+    $query = "SELECT uid FROM {users} LEFT JOIN {url_alias} ON CONCAT('user/', CAST(uid AS CHAR)) = src WHERE uid > 0 AND src IS NULL";
+    $result = db_query_range($query, $context['sandbox']['count'], 1000);
+    while ($uid = db_result($result)) {
+      $context['sandbox']['items'][] = $uid;
+    }
+  }
 
-  $count = 0;
-  $placeholders = array();
-  while ($user = db_fetch_object($result)) {
-    $placeholders = pathauto_get_placeholders('user', $user);
-    $source = 'user/'. $user->uid;
-    if (pathauto_create_alias('user', 'bulkupdate', $placeholders, $source, $user->uid)) {
-      $count++;
+  // Process the next item in the stack.
+  if ($uid = array_shift($context['sandbox']['items'])) {
+    $context['sandbox']['count']++;
+
+    $account = user_load(array('uid' => $uid));
+    $placeholders = pathauto_get_placeholders('user', $account);
+    $src = 'user/' . $account->uid;
+    if (pathauto_create_alias('user', 'bulkupdate', $placeholders, $src, $account->uid)) {
+      $context['message'] = t('Updated alias for user %name', array('%name' => $account->name));
+      // Update total counter.
+      $context['results']['count']++;
     }
   }
 
-  drupal_set_message(format_plural($count,
-    'Bulk generation of users completed, one alias generated.',
-    'Bulk generation of users completed, @count aliases generated.'));
+  if ($context['sandbox']['total'] > 0) {
+    $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+  }
 }
 
 /**
- * Bulk generate aliases for all blogs without aliases.
+ * Batch processing callback; Generate aliases for user blogs.
  */
-function blog_pathauto_bulkupdate() {
-  $query = "SELECT uid, name, src, dst FROM {users} LEFT JOIN {url_alias} ON CONCAT('blog/', CAST(uid AS CHAR)) = src WHERE uid > 0 AND src IS NULL";
-  $result = db_query_range($query, 0, variable_get('pathauto_max_bulk_update', 50));
+function blog_pathauto_batch_update(&$context) {
+  // Get the total amount of items to process.
+  if (!isset($context['sandbox']['total'])) {
+    $context['sandbox']['total'] = db_result(db_query("SELECT COUNT(uid) FROM {users} LEFT JOIN {url_alias} ON CONCAT('blog/', CAST(uid AS CHAR)) = src WHERE uid > 0 AND src IS NULL"));
+    $context['sandbox']['count'] = 0;
+    $context['sandbox']['items'] = array();
+  }
 
-  $count = 0;
-  $placeholders = array();
-  while ($user = db_fetch_object($result)) {
-    $placeholders = pathauto_get_placeholders('user', $user);
-    $source = 'blog/'. $user->uid;
-    if (pathauto_create_alias('blog', 'bulkupdate', $placeholders, $source, $user->uid)) {
-      $count++;
+  // Get the next stack of items to process.
+  if (empty($context['sandbox']['items'])) {
+    $query = "SELECT uid FROM {users} LEFT JOIN {url_alias} ON CONCAT('blog/', CAST(uid AS CHAR)) = src WHERE uid > 0 AND src IS NULL";
+    $result = db_query_range($query, $context['sandbox']['count'], 1000);
+    while ($uid = db_result($result)) {
+      $context['sandbox']['items'][] = $uid;
     }
   }
 
-  drupal_set_message(format_plural($count,
-    'Bulk generation of user blogs completed, one alias generated.',
-    'Bulk generation of user blogs completed, @count aliases generated.'));
+  // Process the next item in the stack.
+  if ($uid = array_shift($context['sandbox']['items'])) {
+    $context['sandbox']['count']++;
+
+    $account = user_load(array('uid' => $uid));
+    $placeholders = pathauto_get_placeholders('user', $account);
+    $src = 'blog/'. $account->uid;
+    if (pathauto_create_alias('blog', 'bulkupdate', $placeholders, $src, $account->uid)) {
+      $context['message'] = t('Updated alias for blog of user %name', array('%name' => $account->name));
+      // Update total counter.
+      $context['results']['count']++;
+    }
+  }
+
+  if ($context['sandbox']['total'] > 0) {
+    $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+  }
 }
 
 /**
- * Bulk generate aliases for user trackers without aliases.
+ * Batch processing callback; Generate aliases for user tracker pages.
  */
-function tracker_pathauto_bulkupdate() {
-  // We do the double CONCAT because Pgsql8.1 doesn't support more than three arguments to CONCAT
-  // Hopefully some day we can remove that.
-  $query = "SELECT uid, name, src, dst FROM {users} LEFT JOIN {url_alias} ON CONCAT(CONCAT('user/', CAST(uid AS CHAR)), '/track') = src WHERE uid > 0 AND src IS NULL";
-  $result = db_query_range($query, 0, variable_get('pathauto_max_bulk_update', 50));
+function tracker_pathauto_batch_update(&$context) {
+  // Get the total amount of items to process.
+  if (!isset($context['sandbox']['total'])) {
+    $context['sandbox']['total'] = db_result(db_query("SELECT COUNT(uid) FROM {users} LEFT JOIN {url_alias} ON CONCAT(CONCAT('user/', CAST(uid AS CHAR)), '/track') = src WHERE uid > 0 AND src IS NULL"));
+    $context['sandbox']['count'] = 0;
+    $context['sandbox']['items'] = array();
+  }
 
-  $count = 0;
-  $placeholders = array();
-  while ($user = db_fetch_object($result)) {
-    $placeholders = pathauto_get_placeholders('user', $user);
-    $source = 'user/'. $user->uid .'/track';
-    if (pathauto_create_alias('tracker', 'bulkupdate', $placeholders, $source, $user->uid)) {
-      $count++;
+  // Get the next stack of items to process.
+  if (empty($context['sandbox']['items'])) {
+    // We do the double CONCAT because Pgsql8.1 doesn't support more than three
+    // arguments to CONCAT.
+    $query = "SELECT uid FROM {users} LEFT JOIN {url_alias} ON CONCAT(CONCAT('user/', CAST(uid AS CHAR)), '/track') = src WHERE uid > 0 AND src IS NULL";
+    $result = db_query_range($query, $context['sandbox']['count'], 1000);
+    while ($uid = db_result($result)) {
+      $context['sandbox']['items'][] = $uid;
     }
   }
 
-  drupal_set_message(format_plural($count,
-    'Bulk generation of user tracker pages completed, one alias generated.',
-    'Bulk generation of user tracker pages completed, @count aliases generated.'));
+  // Process the next item in the stack.
+  if ($uid = array_shift($context['sandbox']['items'])) {
+    $context['sandbox']['count']++;
+
+    $account = user_load(array('uid' => $uid));
+    $placeholders = pathauto_get_placeholders('user', $account);
+    $src = 'user/' . $account->uid . '/track';
+    if (pathauto_create_alias('tracker', 'bulkupdate', $placeholders, $src, $account->uid)) {
+      $context['message'] = t('Updated alias for tracker of user %name', array('%name' => $account->name));
+      // Update total counter.
+      $context['results']['count']++;
+    }
+  }
+
+  if ($context['sandbox']['total'] > 0) {
+    $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+  }
 }
 
 /**
- * Bulk generate aliases for all users without aliases
+ * Batch processing callback; Generate aliases for user contact forms.
  */
-function contact_pathauto_bulkupdate() {
-  $query = "SELECT uid, name, src, dst FROM {users} LEFT JOIN {url_alias} ON CONCAT(CONCAT('user/', CAST(uid AS CHAR)), '/contact') = src WHERE uid > 0 AND src IS NULL";
-  $result = db_query_range($query, 0, variable_get('pathauto_max_bulk_update', 50));
+function contact_pathauto_batch_update(&$context) {
+  // Get the total amount of items to process.
+  if (!isset($context['sandbox']['total'])) {
+    $context['sandbox']['total'] = db_result(db_query("SELECT COUNT(uid) FROM {users} LEFT JOIN {url_alias} ON CONCAT(CONCAT('user/', CAST(uid AS CHAR)), '/contact') = src WHERE uid > 0 AND src IS NULL"));
+    $context['sandbox']['count'] = 0;
+    $context['sandbox']['items'] = array();
+  }
 
-  $count = 0;
-  $placeholders = array();
-  while ($user = db_fetch_object($result)) {
-    $placeholders = pathauto_get_placeholders('user', $user);
-    $source = 'user/'. $user->uid .'/contact';
-    if (pathauto_create_alias('contact', 'bulkupdate', $placeholders, $source, $user->uid)) {
-      $count++;
+  // Get the next stack of items to process.
+  if (empty($context['sandbox']['items'])) {
+    $query = "SELECT uid FROM {users} LEFT JOIN {url_alias} ON CONCAT(CONCAT('user/', CAST(uid AS CHAR)), '/contact') = src WHERE uid > 0 AND src IS NULL";
+    $result = db_query_range($query, $context['sandbox']['count'], 1000);
+    while ($uid = db_result($result)) {
+      $context['sandbox']['items'][] = $uid;
     }
   }
 
-  drupal_set_message(format_plural($count,
-    'Bulk generation of contact pages completed, one alias generated.',
-    'Bulk generation of contact pages completed, @count aliases generated.'));
+  // Process the next item in the stack.
+  if ($uid = array_shift($context['sandbox']['items'])) {
+    $context['sandbox']['count']++;
+
+    $account = user_load(array('uid' => $uid));
+    $placeholders = pathauto_get_placeholders('user', $account);
+    $src = 'user/' . $account->uid . '/contact';
+    if (pathauto_create_alias('contact', 'bulkupdate', $placeholders, $src, $account->uid)) {
+      $context['message'] = t('Updated alias for contact form of user %name', array('%name' => $account->name));
+      // Update total counter.
+      $context['results']['count']++;
+    }
+  }
+
+  if ($context['sandbox']['total'] > 0) {
+    $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+  }
 }
+
