'Prev/Next', 'description' => 'Prev/Next API for nodes', 'page callback' => 'drupal_get_form', 'page arguments' => array('prev_next_admin'), 'access arguments' => array('administer prev_next'), 'file' => 'prev_next.admin.inc', 'file path' => drupal_get_path('module', 'prev_next'), ); $items['admin/config/system/prev_next/re-index'] = array( //'type' => MENU_CALLBACK, 'title' => 'Prev/Next reset', 'page callback' => 'drupal_get_form', 'page arguments' => array('prev_next_reindex_confirm'), 'access arguments' => array('administer prev_next'), 'file' => 'prev_next.admin.inc', 'file path' => drupal_get_path('module', 'prev_next'), ); return $items; } /** * Module function to initiate a reindexing of nodes. */ function prev_next_reindex() { // Wipe the table clean db_query('TRUNCATE {prev_next_node}'); // Get the highest nid $max_nid = db_query('SELECT MAX(nid) FROM {node}')->fetchField(); // Set the variable to that variable_set('prev_next_index_nid', $max_nid); if ($max_nid) { drupal_set_message(t('Prev/Next will index from node %nid downward.', array('%nid' => $max_nid))); } } /** * Implements hook_cron(). */ function prev_next_cron() { $max_nid = variable_get('prev_next_index_nid', 0); 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(); timer_start('prev_next_cron'); $result = db_query_range("SELECT nid FROM {node} WHERE nid <= :nid AND status = 1 $cond ORDER BY nid DESC", 0, $batch_size, array(':nid' => $max_nid)); $count = 0; foreach ($result as $row) { // Remove existing data for this node. db_delete('prev_next_node') ->condition('nid', $row->nid) ->execute(); //_prev_next_modify_pointing_nodes($row->nid); _prev_next_add($row->nid); // Update nodes that might point to this one. // Note that we have indexed at least one node. $last_nid = $row->nid; $count++; } $time = timer_read('prev_next_cron'); if ($last_nid !== FALSE) { // Prepare a starting point for the next run. variable_set('prev_next_index_nid', $last_nid - 1); } else { // If all nodes have been indexed, set to zero to skip future cron runs. variable_set('prev_next_index_nid', 0); } if ($count) watchdog('prev_next', 'Indexed %count nodes in %time milliseconds.', array('%count' => $count, '%time' => $time)); $total = db_query("SELECT COUNT(nid) FROM {node} WHERE status = 1 $cond")->fetchField(); $completed = db_query("SELECT COUNT(nid) FROM {prev_next_node}")->fetchField(); $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, ))); } } /** * Create or update the prev_next records. * @param string $nid * The current node id. */ function _prev_next_add($nid) { $node_type = db_query_range("SELECT type FROM {node} WHERE nid = :nid", 0, 1, array(':nid' => $nid))->fetchField(); $search_criteria = variable_get(PREV_NEXT_NODE_TYPE . $node_type . '_indexing_criteria', PREV_NEXT_INDEXING_CRITERIA_DEFAULT); $join = _prev_next_category_sql($node_type); if (strlen($join) > 0) { // search category for current node $category = variable_get(PREV_NEXT_NODE_TYPE . $node_type . '_category_criteria', 'none'); $category_id = db_query("SELECT " . $category . "_tid FROM {field_data_" . $category . "} WHERE entity_id = :nid", array( ':nid' => $nid ))->fetchField(); if (empty($category_id)) { $cond = ''; } else { $cond = " AND " . $category . "_tid = " . $category_id . " "; $cond .= _prev_next_node_types_sql($node_type); } } else { $cond = _prev_next_node_types_sql($node_type); } if ($search_criteria != 'nid') { $criteria_value = db_query_range("SELECT $search_criteria FROM {node} WHERE nid = :nid", 0, 1, array(':nid' => $nid))->fetchField(); $next_nid = db_query_range("SELECT nid FROM {node} $join WHERE (($search_criteria = :value AND nid > :nid) OR $search_criteria > :value) AND status = 1 $cond ORDER BY $search_criteria ASC,nid ASC", 0, 1, array(':value' => $criteria_value, ':nid' => $nid))->fetchField(); $prev_nid = db_query_range("SELECT nid FROM {node} $join WHERE (($search_criteria = :value AND nid < :nid) OR $search_criteria < :value) AND status = 1 $cond ORDER BY $search_criteria DESC,nid DESC", 0, 1, array(':value' => $criteria_value, ':nid' => $nid))->fetchField(); } else { $next_nid = db_query_range("SELECT nid FROM {node} $join WHERE nid > :nid AND status = 1 $cond ORDER BY nid ASC", 0, 1, array(':nid' => $nid))->fetchField(); $prev_nid = db_query_range("SELECT nid FROM {node} $join WHERE nid < :nid AND status = 1 $cond ORDER BY nid DESC", 0, 1, array(':nid' => $nid))->fetchField(); } // Update the node-level data $exists = (bool) db_query_range('SELECT 1 FROM {prev_next_node} WHERE nid = :nid', 0, 1, array(':nid' => $nid))->fetchField(); if (!empty($exists)) { db_update('prev_next_node') ->fields(array( 'prev_nid' => ($prev_nid) ? $prev_nid : 0, 'next_nid' => ($next_nid) ? $next_nid : 0, 'changed' => REQUEST_TIME, )) ->condition('nid', $nid) ->execute(); } else { $id = db_insert('prev_next_node') ->fields(array( 'prev_nid' => ($prev_nid) ? $prev_nid : 0, 'next_nid' => ($next_nid) ? $next_nid : 0, 'changed' => REQUEST_TIME, 'nid' => $nid, )) ->execute(); } // Update the other nodes pointing to this node foreach (node_type_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); $join = _prev_next_category_sql($node_type); if (strlen($join) > 0) { // search category for current node $category = variable_get(PREV_NEXT_NODE_TYPE . $node_type . '_category_criteria', 'none'); $category_id = db_query("SELECT " . $category . "_tid FROM {field_data_" . $category . "} WHERE entity_id = :nid", array( ':nid' => $nid ))->fetchField(); if (empty($category_id)) { $cond = ''; watchdog('prev_next', 'Node ' . $nid . ' missing taxonomy id for field_data_' . $category, WATCHDOG_ERROR); } else { $cond = " AND " . $category . "_tid = " . $category_id . " "; $cond .= _prev_next_node_types_sql($node_type); } } else { $cond = _prev_next_node_types_sql($node_type); } if ($search_criteria != 'nid') { $criteria_value = db_query_range("SELECT $search_criteria FROM {node} WHERE nid = :nid", 0, 1, array(':nid' => $nid))->fetchField(); $prev_nid = db_query_range("SELECT nid FROM {node} $join WHERE (($search_criteria = :value AND nid < :nid) OR $search_criteria < :value) AND status = 1 $cond ORDER BY $search_criteria DESC,nid DESC", 0, 1, array(':value' => $criteria_value, ':nid' => $nid))->fetchField(); $next_nid = db_query_range("SELECT nid FROM {node} $join WHERE (($search_criteria = :value AND nid > :nid) OR $search_criteria > :value) AND status = 1 $cond ORDER BY $search_criteria ASC,nid ASC", 0, 1, array(':value' => $criteria_value, ':nid' => $nid))->fetchField(); } else { $prev_nid = db_query_range("SELECT nid FROM {node} $join WHERE nid < :nid AND status = 1 $cond ORDER BY nid DESC", 0, 1, array(':nid' => $nid))->fetchField(); $next_nid = db_query_range("SELECT nid FROM {node} $join WHERE nid > :nid AND status = 1 $cond ORDER BY nid ASC", 0, 1, array(':nid' => $nid))->fetchField(); } } if ($next_nid) { db_update('prev_next_node') ->fields(array( 'next_nid' => ($next_nid) ? $next_nid : 0, )) ->condition('nid', $nid) ->execute(); } if ($prev_nid) { db_update('prev_next_node') ->fields(array( 'prev_nid' => ($prev_nid) ? $prev_nid : 0, )) ->condition('nid', $nid) ->execute(); } } } /** * Module function to update the prev_next records. * * @param string $nid * The node id of the current node. */ 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); } /** * Module function to delete from the prev_next records. * @param string $nid * The node id of the current node. */ function _prev_next_remove($nid) { // Delete the data for this node db_delete('prev_next_node') ->condition('nid', $nid) ->execute(); // Find out if any other nodes point to this node and update them _prev_next_modify_pointing_nodes($nid); } /** * Updates other nodes pointing to a particular node. * * @param string $nid * The node id of the current node. */ function _prev_next_modify_pointing_nodes($nid) { // First for previous $prev = db_query("SELECT nid FROM {prev_next_node} WHERE prev_nid = :prev_nid", array(':prev_nid' => $nid))->fetchField(); if ($prev) _prev_next_add($prev); // Then for next $next = db_query("SELECT nid FROM {prev_next_node} WHERE next_nid = :next_nid", array(':next_nid' => $nid))->fetchField(); // if ($next) _prev_next_add($nid); if ($next) _prev_next_add($next); } /** * Implements hook_node_insert(). */ function prev_next_node_insert($node) { $types = _prev_next_node_types(); if (in_array($node->type, $types)) _prev_next_add($node->nid); } /** * Implements hook_node_update(). */ function prev_next_node_update($node) { $types = _prev_next_node_types(); if (in_array($node->type, $types)) _prev_next_modify($node->nid); } /** * Implements hook_node_delete(). */ function prev_next_node_delete($node) { //_prev_next_remove($node->nid); $_SESSION['prev_next_remove_nids'][] = $node->nid; } /** * Callable API function to get the next/prev nid of a given nid * @param string $nid * The node id of the current node. * @param string $op * The operation to perform. prev gets the previous node, next gets the next * node. * @return string $nid * The node id of either the previous or next node of the current node. */ function prev_next_nid($nid, $op = 'next') { foreach (module_implements('prev_next_nid') as $module) { $function = $module . '_prev_next_nid'; $ret = $function($nid, $op); if ($ret !== FALSE) { // If the function returns FALSE, keep trying other methods return $ret; } } switch ($op) { case 'prev': return prev_next_nid_prev($nid); break; case 'next': return prev_next_nid_next($nid); break; default: return 0; } } /** * Module function to get the next node id of the current node. * @param string $nid * The current node id. * @return string */ function prev_next_nid_next($nid) { return db_query("SELECT next_nid FROM {prev_next_node} WHERE nid = :nid", array(':nid' => $nid))->fetchField(); } /** * Module function to get the previous node id of the current node. * @param string $nid * The current node id. * @return string */ function prev_next_nid_prev($nid) { return db_query("SELECT prev_nid FROM {prev_next_node} WHERE nid = :nid", array(':nid' => $nid))->fetchField(); } /** * Helper function to return an array of node types to index. * @return array() */ function _prev_next_node_types() { $types = array(); foreach (node_type_get_types() as $type => $name) { if (variable_get(PREV_NEXT_NODE_TYPE . $type, 0)) { $types[] = $type; } } return $types; } /** * Helper function to return a SQL clause for types to be indexed * @param string $node_type * The indexing criteria for the type of node to query for. * @return string */ 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) . ')'; } } else { $cond = " AND type = '" . $node_type . "'"; } return $cond; } /** * Helper function to return a SQL clause for categories to be indexed * @param string $node_type * @return string */ function _prev_next_category_sql($node_type = '') { $cond = ''; $same_type = variable_get(PREV_NEXT_NODE_TYPE . $node_type . '_same_type', 0); $category = variable_get(PREV_NEXT_NODE_TYPE . $node_type . '_category_criteria', 'none'); if (!$same_type || $category == 'none') { return $cond; } $cond = " INNER JOIN {field_data_$category} ON nid = entity_id "; return $cond; } /** * Implements hook_init(). */ function prev_next_init() { if (!empty($_SESSION['prev_next_remove_nids'])) { foreach ($_SESSION['prev_next_remove_nids'] as $nid) { _prev_next_remove($nid); } unset($_SESSION['prev_next_remove_nids']); } } function prev_next_permission() { return array( 'administer prev_next' => array( 'title' => t('Administer the prev/next module for nodes, including re-indexing. This includes /admin/config/system/prev_next and admin/config/system/prev_next/re-index'), ), ); }