### Eclipse Workspace Patch 1.0
#P prev_next
Index: prev_next.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/prev_next/prev_next.module,v
retrieving revision 1.1
diff -u -r1.1 prev_next.module
--- prev_next.module	7 May 2009 22:12:04 -0000	1.1
+++ prev_next.module	6 Nov 2009 09:52:10 -0000
@@ -2,7 +2,12 @@
 // $Id: prev_next.module,v 1.1 2009/05/07 22:12:04 kbahey Exp $
 
 define('PREV_NEXT_BATCH_SIZE_DEFAULT', 200);
-define('PREV_NEXT_NODE_TYPE',          'prev_next_node_type_');
+define('PREV_NEXT_INDEXING_CRITERIA_DEFAULT', 'nid');
+define('PREV_NEXT_NODE_TYPE', 'prev_next_node_type_');
+define('PREV_NEXT_NUM_BLOCKS_DEFAULT', 1);
+define('PREV_NEXT_DISPLAY_DEFAULT', 1);
+define('PREV_NEXT_DISPLAY_TEXT_PREV_DEFAULT', '[title] »');
+define('PREV_NEXT_DISPLAY_TEXT_NEXT_DEFAULT', '« [title]');
 
 /**
  * Implementation of hook_menu().
@@ -32,8 +37,8 @@
  */
 function prev_next_admin() {
   $form['status'] = array(
-    '#type' => 'fieldset', 
-    '#title' => t('Indexing status'),
+    '#type'   => 'fieldset',
+    '#title'  => t('Indexing status'),
   );
 
   $max_nid = variable_get('prev_next_index_nid', 0);
@@ -50,14 +55,14 @@
       '%remaining'  => $remaining,
       '%total'      => $total,
     ));
-  $status .= ($max_nid) ? t('<p>Max node ID for indexing on the next cron run: @max</p>', array('@max' => $max_nid)) : t('<p>Existing nodes have finished prev/next indexing.</p>');
+  $status .= ($max_nid) ? t('<p>Max node ID for indexing on the next cron run: @max.</p>', array('@max' => $max_nid)) : t('<p>Existing nodes have finished prev/next indexing.</p>');
 
   $form['status']['status'] = array(
     '#value' => $status,
   );
 
   $form['status']['reindex'] = array(
-    '#type'  => 'submit', 
+    '#type'  => 'submit',
     '#value' => t('Re-index'),
   );
 
@@ -71,20 +76,72 @@
     '#required'      => TRUE,
   );
 
+  $form['prev_next_num_blocks'] = array(
+    '#title'         => t('Blocks'),
+    '#description'   => t('Number of blocks available.'),
+    '#type'          => 'textfield',
+    '#size'          => 2,
+    '#maxlength'     => 3,
+    '#default_value' => variable_get('prev_next_num_blocks', PREV_NEXT_NUM_BLOCKS_DEFAULT),
+    '#required'      => TRUE,
+  );
+
   $form['node_types'] = array(
     '#type'        => 'fieldset',
     '#title'       => t('Content types'),
-    '#description' => t('Content types to include in prev/next. If you do not specify content types, then all of them will be included.'),
+    '#description' => t('Define settings for each content type. If none of them is included, then all of them will be.'),
   );
 
   foreach (node_get_types() as $type => $name) {
-    $form['node_types'][PREV_NEXT_NODE_TYPE . $type] = array(
+
+    $form['node_types'][$type] = array(
+      '#type'         => 'fieldset',
+      '#description'  => t('Note: changing one of these values will reset the entire Prev/Next index.'),
+      '#title'        => node_get_types('name', $type),
+      '#collapsible'  => TRUE,
+      '#collapsed'    => !variable_get(PREV_NEXT_NODE_TYPE . $type, 0)
+    );
+
+    $form['node_types'][$type][PREV_NEXT_NODE_TYPE . $type] = array(
       '#type'          => 'checkbox',
-      '#title'         => node_get_types('name', $type),
+      '#title'         => t('Include'),
       '#default_value' => variable_get(PREV_NEXT_NODE_TYPE . $type, 0),
     );
+    $form['node_types'][$type][PREV_NEXT_NODE_TYPE . $type . '_current'] = array(
+      '#type'          => 'hidden',
+      '#default_value' => variable_get(PREV_NEXT_NODE_TYPE . $type, 0)
+    );
+
+    $form['node_types'][$type][PREV_NEXT_NODE_TYPE . $type . '_indexing_criteria'] = array(
+      '#title'          => t('Indexing criteria'),
+      '#type'           => 'select',
+      '#options'        => array(
+        'nid'     => t('Node ID'),
+        'created' => t('Post date'),
+        'changed' => t('Updated date'),
+        'title'   => t('Title')
+      ),
+      '#default_value'  => variable_get(PREV_NEXT_NODE_TYPE . $type . '_indexing_criteria', PREV_NEXT_INDEXING_CRITERIA_DEFAULT)
+    );
+    $form['node_types'][$type][PREV_NEXT_NODE_TYPE . $type . '_indexing_criteria_current'] = array(
+      '#type'   => 'hidden',
+      '#value'  => variable_get(PREV_NEXT_NODE_TYPE . $type . '_indexing_criteria', PREV_NEXT_INDEXING_CRITERIA_DEFAULT)
+    );
+
+    $form['node_types'][$type][PREV_NEXT_NODE_TYPE . $type . '_same_type'] = array(
+      '#type'          => 'checkbox',
+      '#title'         => t('Only nodes with same content type'),
+      '#default_value' => variable_get(PREV_NEXT_NODE_TYPE . $type . '_same_type', 0),
+    );
+    $form['node_types'][$type][PREV_NEXT_NODE_TYPE . $type . '_same_type_current'] = array(
+      '#type'          => 'hidden',
+      '#default_value' => variable_get(PREV_NEXT_NODE_TYPE . $type . '_same_type', 0)
+    );
+
   }
 
+  $form['#submit'][] = 'prev_next_admin_submit';
+
   return system_settings_form($form);
 }
 
@@ -105,9 +162,32 @@
   }
 }
 
+/**
+ * Submit callback.
+ */
+function prev_next_admin_submit($form, &$form_state) {
+  $rebuild = FALSE;
+
+  // Test sensitive values.
+  foreach (node_get_types() as $type => $name) {
+    if ($form_state['values'][PREV_NEXT_NODE_TYPE . $type . '_current'] != $form_state['values'][PREV_NEXT_NODE_TYPE . $type]
+      || $form_state['values'][PREV_NEXT_NODE_TYPE . $type . '_indexing_criteria_current'] != $form_state['values'][PREV_NEXT_NODE_TYPE . $type . '_indexing_criteria']
+      || $form_state['values'][PREV_NEXT_NODE_TYPE . $type . '_same_type_current'] != $form_state['values'][PREV_NEXT_NODE_TYPE . $type . '_same_type']
+    )
+      $rebuild = TRUE;
+  }
+
+  // If the search criterias has been changed, re-index.
+  if ($rebuild) {
+    prev_next_reindex();
+    drupal_set_message(t('The Prev/Next index will be rebuilt.'));
+  }
+  $form_state['redirect'] = 'admin/settings/prev_next';
+}
+
 function prev_next_reindex_confirm() {
   return confirm_form(array(), t('Are you sure you want to re-index Prev/Next?'),
-    'admin/settings/prev_next', t(' The entire Prev/Next index will be reset and rebuilt incrementally as cron runs. action cannot be undone.'), t('Re-index'), t('Cancel'));
+    'admin/settings/prev_next', t('The entire Prev/Next index will be reset and rebuilt incrementally as cron runs. action cannot be undone.'), t('Re-index'), t('Cancel'));
 }
 
 function prev_next_reindex_confirm_submit(&$form, &$form_state) {
@@ -138,11 +218,10 @@
   if ($max_nid) {
     $batch_size = variable_get('prev_next_batch_size', PREV_NEXT_BATCH_SIZE_DEFAULT);
     $last_nid = FALSE;
-    $cond =  _prev_next_node_types_sql();
+    $cond = _prev_next_node_types_sql();
 
     timer_start('prev_next_cron');
-    $result = db_query("SELECT nid FROM {node} WHERE nid <= %d AND status = 1 $cond ORDER BY nid DESC LIMIT %d",
-      $max_nid, $batch_size);
+    $result = db_query("SELECT nid FROM {node} WHERE nid <= %d AND status = 1 $cond ORDER BY nid DESC LIMIT %d", $max_nid, $batch_size);
     $count = 0;
 
     while ($row = db_fetch_object($result)) {
@@ -161,24 +240,179 @@
     if ($last_nid !== FALSE) {
       // Prepare a starting point for the next run.
       variable_set('prev_next_index_nid', $last_nid - 1);
-      watchdog('prev_next', 'Indexed %count nodes in %time milliseconds.', array('%count' => $count, '%time' => $time));
     }
     else {
       // If all nodes have been indexed, set to zero to skip future cron runs.
       variable_set('prev_next_index_nid', 0);
     }
+    watchdog('prev_next', 'Indexed %count nodes in %time milliseconds.', array('%count' => $count, '%time' => $time));
+
+    $total = db_result(db_query("SELECT COUNT(nid) FROM {node} WHERE status = 1 $cond"));
+    $completed = db_result(db_query("SELECT COUNT(nid) FROM {prev_next_node}"));
+    $remaining = max(0, $total - $completed);
+    drupal_set_message(t('Indexed %count nodes for the Prev/Next index. There are %remaining items left to index.', array(
+      '%count' => $count,
+      '%remaining'  => $remaining
+    )));
+
   }
 }
 
 /**
+ * Implementation of hook_block().
+ */
+function prev_next_block($op = 'list', $delta = 0, $edit = array()) {
+  switch ($op) {
+
+    case 'list':
+      $num_blocks = variable_get('prev_next_num_blocks', PREV_NEXT_NUM_BLOCKS_DEFAULT);
+      for ($b = 0; $b < $num_blocks ; $b++) {
+        $blocks[$b] = array(
+          'info'    => t('Prev/Next links !blocknum', array('!blocknum' => 1+$b)),
+          'status'  => 0,
+          'cache'   => BLOCK_CACHE_PER_PAGE
+        );
+      }
+      return $blocks;
+      break;
+
+    case 'configure':
+      $description = (module_exists('token')) ? 'Use the available tokens (see below) to customize the link text.' : 'The [title] token will be replaced by the actual node title.';
+
+      $form['previous'] = array(
+        '#type'         => 'fieldset',
+        '#title'        => t('Previous Node'),
+        '#collapsible'  => TRUE,
+      );
+      $form['previous']['prev_next_display_prev' . $delta] = array(
+        '#type'           => 'checkbox',
+        '#title'          => t('Display'),
+        '#default_value'  => variable_get('prev_next_display_prev' . $delta, PREV_NEXT_DISPLAY_DEFAULT),
+      );
+      $form['previous']['prev_next_display_text_prev' . $delta] = array(
+        '#type'           => 'textfield',
+        '#title'          => t('Link text'),
+        '#description'    => $description,
+        '#default_value'  => variable_get('prev_next_display_text_prev' . $delta, PREV_NEXT_DISPLAY_TEXT_PREV_DEFAULT),
+      );
+      if (module_exists('token')) {
+        $form['previous']['token_help'] = array(
+          '#title' => t('Replacement patterns'),
+          '#type' => 'fieldset',
+          '#collapsible' => TRUE,
+          '#collapsed' => TRUE,
+        );
+        $form['previous']['token_help']['help'] = array(
+          '#value' => theme('token_help', 'node'),
+        );
+      }
+
+      $form['next'] = array(
+        '#type'         => 'fieldset',
+        '#title'        => t('Next Node'),
+        '#collapsible'  => TRUE,
+      );
+      $form['next']['prev_next_display_next' . $delta] = array(
+        '#type'           => 'checkbox',
+        '#title'          => t('Display'),
+        '#default_value'  => variable_get('prev_next_display_next' . $delta, PREV_NEXT_DISPLAY_DEFAULT),
+      );
+      $form['next']['prev_next_display_text_next' . $delta] = array(
+        '#type'           => 'textfield',
+        '#title'          => t('Link text'),
+        '#description'    => $description,
+        '#default_value'  => variable_get('prev_next_display_text_next' . $delta, PREV_NEXT_DISPLAY_TEXT_NEXT_DEFAULT),
+      );
+      if (module_exists('token')) {
+        $form['next']['token_help'] = array(
+          '#title' => t('Replacement patterns'),
+          '#type' => 'fieldset',
+          '#collapsible' => TRUE,
+          '#collapsed' => TRUE,
+        );
+        $form['next']['token_help']['help'] = array(
+          '#value' => theme('token_help', 'node'),
+        );
+      }
+    return $form;
+    break;
+
+    case 'save':
+      variable_set('prev_next_display_prev' . $delta, $edit['prev_next_display_prev' . $delta]);
+      variable_set('prev_next_display_next' . $delta, $edit['prev_next_display_next' . $delta]);
+      variable_set('prev_next_display_text_prev' . $delta, $edit['prev_next_display_text_prev' . $delta]);
+      variable_set('prev_next_display_text_next' . $delta, $edit['prev_next_display_text_next' . $delta]);
+      break;
+
+    case 'view':
+      $content = '';
+      $next_display = variable_get('prev_next_display_next' . $delta, PREV_NEXT_DISPLAY_DEFAULT);
+      $next_text = variable_get('prev_next_display_text_next' . $delta, PREV_NEXT_DISPLAY_TEXT_NEXT_DEFAULT);
+      $prev_display = variable_get('prev_next_display_prev' . $delta, PREV_NEXT_DISPLAY_DEFAULT);
+      $prev_text = variable_get('prev_next_display_text_prev' . $delta, PREV_NEXT_DISPLAY_TEXT_PREV_DEFAULT);
+
+      if (arg(0) == 'node' && is_numeric(arg(1)) && !arg(2) ) {
+        $node = node_load(arg(1));
+        $n_nid = prev_next_nid($node->nid, 'next');
+        $p_nid = prev_next_nid($node->nid, 'prev');
+
+        if ($n_nid || $p_nid) {
+
+          if ($p_nid && $prev_display && $prev_text != '') {
+            $p_node = node_load($p_nid);
+            if (module_exists('token')) {
+              $link = token_replace($prev_text, 'node', $p_node);
+            }
+            else {
+              $link = str_replace('[title]', $p_node->title, $prev_text);
+            }
+            $options = array('html' => TRUE);
+            $content .= '<li class="prev-next-link-prev">' . l($link, "node/$p_nid", $options) . '</li>';
+          }
+
+          if ($n_nid && $next_display && $next_text != '') {
+            $n_node = node_load($n_nid);
+            if (module_exists('token')) {
+              $link = token_replace($next_text, 'node', $n_node);
+            }
+            else {
+              $link = str_replace('[title]', $n_node->title, $next_text);
+            }
+            $options = array('html' => TRUE);
+            $content .= '<li class="prev-next-link-next">' . l($link, "node/$n_nid", $options) . '</li>';
+          }
+
+          $block = array(
+            'subject' => t('Prev/Next links'),
+            'content' => '<ul class="prev-next-links">' . $content . '</ul>'
+          );
+
+        }
+
+      }
+
+    return $block;
+
+  }
+
+}
+
+/**
  * Create or update the prev_next records.
  */
 function _prev_next_add($nid) {
-  $cond =  _prev_next_node_types_sql();
+  $node_type = db_result(db_query("SELECT type FROM {node} WHERE nid = %d LIMIT 1", $nid));
+  $search_criteria = variable_get(PREV_NEXT_NODE_TYPE . $node_type . '_indexing_criteria', PREV_NEXT_INDEXING_CRITERIA_DEFAULT);
+
+  $criteria_value = db_result(db_query("SELECT %s FROM {node} WHERE nid = %d LIMIT 1", $search_criteria, $nid));
 
-  $next_nid = db_result(db_query("SELECT nid FROM {node} WHERE nid > %d AND status = 1 $cond ORDER BY nid ASC  LIMIT 1", $nid));
+  $cond = _prev_next_node_types_sql($node_type);
 
-  $prev_nid = db_result(db_query("SELECT nid FROM {node} WHERE nid < %d AND status = 1 $cond ORDER BY nid DESC LIMIT 1", $nid));
+  $next_nid = db_result(db_query("SELECT nid FROM {node} WHERE %s > '%s' AND status = 1 $cond ORDER BY %s ASC LIMIT 1",
+    $search_criteria, $criteria_value, $search_criteria));
+
+  $prev_nid = db_result(db_query("SELECT nid FROM {node} WHERE %s < '%s' AND status = 1 $cond ORDER BY %s DESC LIMIT 1",
+    $search_criteria, $criteria_value, $search_criteria));
 
   // Update the node-level data
   $exists = db_result(db_query('SELECT COUNT(*) FROM {prev_next_node} WHERE nid = %d', $nid));
@@ -192,13 +426,34 @@
   }
 
   // Update the other nodes pointing to this node
-  if ($prev_nid) {
-    // Update the previous node
-    db_query("UPDATE {prev_next_node} SET next_nid = %d WHERE nid = %d", $nid, $prev_nid);
-  }
-  if ($next_nid) {
-    db_query("UPDATE {prev_next_node} SET prev_nid = %d WHERE nid = %d", $nid, $next_nid);
+  foreach (node_get_types() as $type => $name) {
+    if (variable_get(PREV_NEXT_NODE_TYPE . $type, 0)) {
+      $search_criteria = variable_get(PREV_NEXT_NODE_TYPE . $type . '_indexing_criteria', PREV_NEXT_INDEXING_CRITERIA_DEFAULT);
+      $criteria_value = db_result(db_query("SELECT %s FROM {node} WHERE nid = %d LIMIT 1", $search_criteria, $nid));
+      $cond = _prev_next_node_types_sql($node_type);
+      $next_nid = db_result(db_query("SELECT nid FROM {node} WHERE %s < '%s' AND status = 1 $cond ORDER BY %s DESC LIMIT 1",
+        $search_criteria, $criteria_value, $search_criteria));
+      if ($next_nid) {
+        db_query("UPDATE {prev_next_node} SET next_nid = %d WHERE nid = %d", $nid, $next_nid);
+      }
+      $prev_nid = db_result(db_query("SELECT nid FROM {node} WHERE %s > '%s' AND status = 1 $cond ORDER BY %s ASC LIMIT 1",
+        $search_criteria, $criteria_value, $search_criteria));
+      if ($prev_nid) {
+        db_query("UPDATE {prev_next_node} SET prev_nid = %d WHERE nid = %d", $nid, $prev_nid);
+      }
+    }
   }
+
+}
+
+/**
+ * Update the prev_next records.
+ */
+function _prev_next_modify($nid) {
+  // Find out if any other nodes point to this node and update them
+  _prev_next_modify_pointing_nodes($nid);
+  // Then update this one
+  _prev_next_add($nid);
 }
 
 /**
@@ -212,13 +467,20 @@
     db_query("DELETE FROM {prev_next_node} WHERE nid = %d", $nid);
   }
   // Find out if any other nodes point to this node and update them
+  _prev_next_modify_pointing_nodes($nid);
+}
+
+/**
+ * Update other nodes pointing to a particular node
+ */
+function _prev_next_modify_pointing_nodes($nid) {
   // First for previous
-  $prev_nid = db_result(db_query("SELECT nid FROM {prev_next_node} WHERE prev_nid = %d", $nid));
-  _prev_next_add($prev_nid);
+  $result = db_query("SELECT nid FROM {prev_next_node} WHERE prev_nid = %d", $nid);
+  while ($prev_row = db_fetch_object($result)) _prev_next_add($prev_row->nid);
 
   // Then for next
-  $next_nid = db_result(db_query("SELECT nid FROM {prev_next_node} WHERE next_nid = %d", $nid));
-  _prev_next_add($next_nid);
+  $result = db_query("SELECT nid FROM {prev_next_node} WHERE next_nid = %d", $nid);
+  while ($next_row = db_fetch_object($result)) _prev_next_add($next_row->nid);
 }
 
 /**
@@ -226,7 +488,7 @@
  */
 function prev_next_nodeapi(&$node, $op) {
   $found = FALSE;
-  foreach(_prev_next_node_types() as $type) {
+  foreach (_prev_next_node_types() as $type) {
     if ($node->type == $type) {
       $found = TRUE;
       break;
@@ -236,19 +498,22 @@
     return;
   }
 
-  switch($op) {
+  switch ($op) {
     case 'insert':
-    case 'update':
       _prev_next_add($node->nid);
       break;
 
+    case 'update':
+      _prev_next_modify($node->nid);
+      break;
+
     case 'delete':
       _prev_next_remove($node->nid);
       break;
   }
 }
 
-/* 
+/*
  * Callable API function to get the next/prev nid of a given nid
  */
 function prev_next_nid($nid, $op = 'next') {
@@ -288,18 +553,25 @@
 /*
  * Helper function to return a SQL clause for types to be indexed
  */
-function _prev_next_node_types_sql() {
-  $types = _prev_next_node_types();
+function _prev_next_node_types_sql($node_type = '') {
+
+  $same_type = variable_get(PREV_NEXT_NODE_TYPE . $node_type . '_same_type', 0);
+
+  if (!$same_type) {
+
+    $types = _prev_next_node_types();
+
+    $quoted_types = array();
+    foreach (_prev_next_node_types() as $type) {
+      $quoted_types[] = "'" . $type . "'";
+    }
+    $cond = '';
+    if (count($types)) {
+      $cond  = ' AND type IN (' . implode(',', $quoted_types) . ')';
+    }
 
-  $quoted_types = array();
-  foreach(_prev_next_node_types() as $type) {
-    $quoted_types[] = "'" . $type . "'";
-  }
-  $cond = '';
-  if (count($types)) {
-    $cond  = ' AND type IN (' . implode(',', $quoted_types) . ')';
   }
+  else $cond = " AND type = '" . $node_type . "'";
 
   return $cond;
 }
-
