You are here

prev_next.module in Previous/Next API 7.2

Same filename and directory in other branches
  1. 8.2 prev_next.module
  2. 6 prev_next.module
  3. 7 prev_next.module

The previous next module indexes the previous and next nodes based upon user-selectable criteria and stores this index in the database for faster retrieval later.

File

prev_next.module
View source
<?php

/**
 * @file
 * The previous next module indexes the previous and next nodes based upon
 * user-selectable criteria and stores this index in the database for faster
 * retrieval later.
 */
define('PREV_NEXT_BATCH_SIZE_DEFAULT', 200);
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', '«[node:title]');
define('PREV_NEXT_DISPLAY_TEXT_NEXT_DEFAULT', '[node:title]»');
module_load_include('inc', 'prev_next', 'prev_next.block');

/**
 * Implements hook_menu().
 */
function prev_next_menu() {
  $items['admin/config/system/prev_next'] = array(
    'title' => '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\n                               FROM {node}\n                               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}\n                                      FROM {node}\n                                      WHERE nid = :nid", 0, 1, array(
      ':nid' => $nid,
    ))
      ->fetchField();
    $next_nid = db_query_range("SELECT nid FROM {node}\n                                {$join}\n                                WHERE (({$search_criteria} = :value AND nid > :nid)\n                                OR {$search_criteria} > :value)\n                                AND status = 1\n                                {$cond} ORDER BY\n                                {$search_criteria} ASC,nid ASC", 0, 1, array(
      ':value' => $criteria_value,
      ':nid' => $nid,
    ))
      ->fetchField();
    $prev_nid = db_query_range("SELECT nid FROM {node}\n                                {$join}\n                                WHERE (({$search_criteria} = :value\n                                AND nid < :nid)\n                                OR {$search_criteria} < :value)\n                                AND status = 1\n                                {$cond}\n                                ORDER BY {$search_criteria} DESC,nid DESC", 0, 1, array(
      ':value' => $criteria_value,
      ':nid' => $nid,
    ))
      ->fetchField();
  }
  else {
    $next_nid = db_query_range("SELECT nid\n                                FROM {node}\n                                {$join}\n                                WHERE nid > :nid\n                                AND status = 1 {$cond} ORDER BY nid ASC", 0, 1, array(
      ':nid' => $nid,
    ))
      ->fetchField();
    $prev_nid = db_query_range("SELECT nid\n                                FROM {node}\n                                {$join}\n                                WHERE nid < :nid\n                                AND status = 1\n                                {$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}\n                                          FROM {node}\n                                          WHERE nid = :nid", 0, 1, array(
          ':nid' => $nid,
        ))
          ->fetchField();
        $prev_nid = db_query_range("SELECT nid\n                                    FROM {node}\n                                    {$join}\n                                    WHERE (({$search_criteria} = :value\n                                    AND nid < :nid)\n                                    OR {$search_criteria} < :value)\n                                    AND status = 1\n                                    {$cond} ORDER BY {$search_criteria} DESC,nid DESC", 0, 1, array(
          ':value' => $criteria_value,
          ':nid' => $nid,
        ))
          ->fetchField();
        $next_nid = db_query_range("SELECT nid\n                                    FROM {node}\n                                    {$join}\n                                    WHERE (({$search_criteria} = :value\n                                    AND nid > :nid)\n                                    OR {$search_criteria} > :value)\n                                    AND status = 1\n                                    {$cond} ORDER BY\n                                    {$search_criteria} ASC,nid ASC", 0, 1, array(
          ':value' => $criteria_value,
          ':nid' => $nid,
        ))
          ->fetchField();
      }
      else {
        $prev_nid = db_query_range("SELECT nid\n                                    FROM {node}\n                                    {$join}\n                                    WHERE nid < :nid\n                                    AND status = 1\n                                    {$cond}\n                                    ORDER BY nid DESC", 0, 1, array(
          ':nid' => $nid,
        ))
          ->fetchField();
        $next_nid = db_query_range("SELECT nid\n                                    FROM {node}\n                                    {$join}\n                                    WHERE nid > :nid\n                                    AND status = 1\n                                    {$cond}\n                                    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\n                    FROM {prev_next_node}\n                    WHERE prev_nid = :prev_nid", array(
    ':prev_nid' => $nid,
  ))
    ->fetchField();
  if ($prev) {
    _prev_next_add($prev);
  }

  // Then for next
  $next = db_query("SELECT nid\n                    FROM {prev_next_node}\n                    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\n                   FROM {prev_next_node}\n                   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\n                   FROM {prev_next_node}\n                   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'),
    ),
  );
}

Functions

Namesort descending Description
prev_next_cron Implements hook_cron().
prev_next_init Implements hook_init().
prev_next_menu Implements hook_menu().
prev_next_nid Callable API function to get the next/prev nid of a given nid
prev_next_nid_next Module function to get the next node id of the current node.
prev_next_nid_prev Module function to get the previous node id of the current node.
prev_next_node_delete Implements hook_node_delete().
prev_next_node_insert Implements hook_node_insert().
prev_next_node_update Implements hook_node_update().
prev_next_permission
prev_next_reindex Module function to initiate a reindexing of nodes.
_prev_next_add Create or update the prev_next records.
_prev_next_category_sql Helper function to return a SQL clause for categories to be indexed
_prev_next_modify Module function to update the prev_next records.
_prev_next_modify_pointing_nodes Updates other nodes pointing to a particular node.
_prev_next_node_types Helper function to return an array of node types to index.
_prev_next_node_types_sql Helper function to return a SQL clause for types to be indexed
_prev_next_remove Module function to delete from the prev_next records.

Constants

Namesort descending Description
PREV_NEXT_BATCH_SIZE_DEFAULT @file The previous next module indexes the previous and next nodes based upon user-selectable criteria and stores this index in the database for faster retrieval later.
PREV_NEXT_DISPLAY_DEFAULT
PREV_NEXT_DISPLAY_TEXT_NEXT_DEFAULT
PREV_NEXT_DISPLAY_TEXT_PREV_DEFAULT
PREV_NEXT_INDEXING_CRITERIA_DEFAULT
PREV_NEXT_NODE_TYPE
PREV_NEXT_NUM_BLOCKS_DEFAULT