You are here

similar.module in Similar Entries 6

Same filename and directory in other branches
  1. 5 similar.module
  2. 6.2 similar.module
  3. 7.2 similar.module
  4. 7 similar.module

Module that shows a block listing similar entries. NOTE: Uses MySQL's FULLTEXT indexing for MyISAM tables.

Caching feature sponsored by http://xomba.com/

@author David Kent Norman http://deekayen.net/ @author Arnab Nandi http://arnab.org/

File

similar.module
View source
<?php

/**
 * @file
 * Module that shows a block listing similar entries.
 * NOTE: Uses MySQL's FULLTEXT indexing for MyISAM tables.
 *
 * Caching feature sponsored by http://xomba.com/
 *
 * @author David Kent Norman http://deekayen.net/
 * @author Arnab Nandi http://arnab.org/
 */

/**
 * Implements hook_help().
 */
function similar_help($path, $arg) {
  switch ($path) {
    case 'admin/help#similar':
      return t('<p>Lists the most similar nodes to the current node.</p>');
      break;
  }
}

/**
 * Implements hook_block().
 */
function similar_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks[0]['info'] = t('Similar Entries');
      $blocks[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE;
      return $blocks;
    case 'configure':
      $form = array();
      if ($delta == 0) {
        $form['similar_teaser_enabled'] = array(
          '#type' => 'radios',
          '#title' => t('Include teaser text'),
          '#default_value' => variable_get('similar_teaser_enabled', 0),
          '#options' => array(
            t('No'),
            t('Yes'),
          ),
        );
        $form['similar_rel_nofollow'] = array(
          '#type' => 'radios',
          '#title' => t('Block search engines'),
          '#description' => t('Adds rel="nofollow" to the HTML source of similar links so search engines won\'t count similar links in their ranking calculations.'),
          '#default_value' => variable_get('similar_rel_nofollow', 0),
          '#options' => array(
            t('No'),
            t('Yes'),
          ),
        );
        for ($i = 1, $options = array(); $i < 101; $options[$i] = $i, $i += 1) {
        }
        $form['similar_num_display'] = array(
          '#type' => 'select',
          '#title' => t('Number of similar entries to find'),
          '#default_value' => variable_get('similar_num_display', 5),
          '#options' => $options,
        );
        $types = _similar_published_node_types();
        $form['similar_node_types'] = array(
          '#type' => 'checkboxes',
          '#multiple' => TRUE,
          '#title' => t('Node types to display'),
          '#default_value' => variable_get('similar_node_types', $types),
          '#options' => $types,
        );
        if (module_exists('taxonomy')) {
          $names = _similar_taxonomy_names();
          $form['similar_taxonomy'] = array(
            '#type' => 'fieldset',
            '#title' => t('Taxonomy category filter'),
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
          );
          $form['similar_taxonomy']['similar_taxonomy_filter'] = array(
            '#type' => 'radios',
            '#title' => t('Filter by taxonomy categories'),
            '#default_value' => variable_get('similar_taxonomy_filter', 0),
            '#options' => array(
              t('No category filtering'),
              t('Only show the similar nodes in the same category as the original node'),
              t('Use global category filtering'),
            ),
            '#description' => t('By selecting global filtering, only nodes assigned to the following selected categories will display as similar nodes, regardless of the categories the original node is or is not assigned to.'),
          );
          $form['similar_taxonomy']['similar_taxonomy_select'] = array(
            '#type' => 'fieldset',
            '#title' => t('Taxonomy categories to display'),
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
          );
          $form['similar_taxonomy']['similar_taxonomy_select']['similar_taxonomy_tids'] = array(
            '#type' => 'select',
            '#default_value' => variable_get('similar_taxonomy_tids', array_keys($names)),
            '#description' => t('Hold the CTRL key to (de)select multiple options.'),
            '#options' => $names,
            '#multiple' => TRUE,
          );
        }
      }
      return $form;
    case 'save':
      if ($delta == 0) {
        variable_set('similar_teaser_enabled', $edit['similar_teaser_enabled']);
        variable_set('similar_rel_nofollow', $edit['similar_rel_nofollow']);
        variable_set('similar_num_display', $edit['similar_num_display']);
        variable_set('similar_node_types', $edit['similar_node_types']);
        if (module_exists('taxonomy')) {
          variable_set('similar_taxonomy_filter', $edit['similar_taxonomy_filter']);
          variable_set('similar_taxonomy_tids', $edit['similar_taxonomy_tids']);
        }
      }
      return;
    case 'view':
    default:
      if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) != 'edit') {
        $node = node_load(array(
          'nid' => arg(1),
        ));
      }
      else {
        return;
      }
      $similar_node_types = variable_get('similar_node_types', _similar_published_node_types());
      if ($node->nid > 0 && !empty($similar_node_types[$node->type])) {
        unset($similar_node_types);
        switch ($delta) {
          case 0:

            // The subject is displayed at the top of the block. Note that it should
            // be passed through t() for translation.
            $block['subject'] = t('Similar Entries');
            $block['content'] = theme('similar_content', $node);
        }
      }
      return empty($block['content']) ? '' : $block;
      break;
  }
}

/**
 * Queries for published node types.
 * @link http://drupal.org/node/33444 @endlink
 *
 * @return
 *   An array of node types that are available.
 */
function _similar_published_node_types() {
  $types = array();
  $result = db_query('SELECT DISTINCT(n.type) FROM {node} n WHERE n.status <> 0 ORDER BY n.type ASC');
  while ($type = db_fetch_object($result)) {
    $types[$type->type] = $type->type;
  }
  return $types;
}

/**
 * Queries for taxonomy names.
 * @link http://drupal.org/node/51041 @endlink
 *
 *@return
 *   An array of taxonomy term names to be used.
 */
function _similar_taxonomy_names() {
  $names = array();
  $result = db_query('SELECT d.tid, v.vid, v.name AS vocab_name, d.name AS data_name FROM {term_data} d, {vocabulary} v WHERE v.vid = d.vid ORDER BY v.name, d.name ASC');
  while ($data = db_fetch_object($result)) {
    $names[$data->tid] = $data->vocab_name . ': ' . $data->data_name;
  }
  return $names;
}

/**
 * Queries for taxonomies to which a specific node belongs.
 * @link http://drupal.org/node/51041 @endlink
 *
 * @param $nid
 *   A node ID.
 * @return
 *   An array of terms related to the given node.
 */
function _similar_taxonomy_membership($nid) {
  $tids = array();
  $result = db_query('SELECT t.tid FROM {term_node} t WHERE t.nid = %d', $nid);
  while ($data = db_fetch_object($result)) {
    $tids[$data->tid] = $data->tid;
  }
  return $tids;
}

/**
 * Strips characters from node type strings.
 */
function _similar_content_type_escape(&$item) {
  $item = str_replace(array(
    "\0",
    "\n",
    "\r",
    "\\",
    "'",
    "\"",
    "\32",
  ), '', $item);
}

/**
 * Prevents SQL injection be forcing an integer value.
 */
function _similar_force_int(&$item) {
  $item = (int) $item;
}

/**
 * Implements hook_theme().
 */
function similar_theme() {
  return array(
    'similar_content' => array(
      'arguments' => array(
        'node' => NULL,
      ),
    ),
  );
}

/**
 * Queries the database for similar entries and puts them in a HTML list.
 *
 * @param $node
 *   The current node being viewed.
 * @return
 *   A themed item list of related links.
 */
function theme_similar_content($node) {
  $items = array();
  $text = "{$node->title} {$node->body}";
  $teaser = variable_get('similar_teaser_enabled', 0);
  $types = _similar_published_node_types();
  $types = variable_get('similar_node_types', $types);
  array_walk($types, '_similar_content_type_escape');
  if (sizeof($types) > 1) {
    $types = implode("','", $types);
  }
  else {
    list(, $types) = each($types);
  }
  $types = "'{$types}'";
  if (module_exists('taxonomy') && (variable_get('similar_taxonomy_filter', 0) == 2 && ($taxonomy_tids = variable_get('similar_taxonomy_tids', array()))) || variable_get('similar_taxonomy_filter', 0) == 1 && ($taxonomy_tids = _similar_taxonomy_membership($node->nid))) {
    array_walk($taxonomy_tids, '_similar_force_int');
    if (sizeof($taxonomy_tids) > 1) {
      $taxonomy_tids = implode(',', $taxonomy_tids);
    }
    else {
      list(, $taxonomy_tids) = each($taxonomy_tids);
      $taxonomy_tids = (int) $taxonomy_tids;
    }
    $query = "SELECT r.nid, MATCH(r.body, r.title) AGAINST ('%s') AS score FROM {node_revisions} r INNER JOIN {node} n ON r.nid = n.nid AND r.vid = n.vid INNER JOIN {term_node} t ON n.nid = t.nid AND t.tid IN (%s) WHERE n.status <> 0 AND r.nid <> %d AND n.type IN ({$types}) GROUP BY n.nid HAVING score > 0 ORDER BY score DESC, r.vid DESC";
  }
  else {
    $query = "SELECT r.nid, MATCH(r.body, r.title) AGAINST ('%s') AS score FROM {node_revisions} r INNER JOIN {node} n ON r.nid = n.nid AND r.vid = n.vid WHERE n.status <> 0 AND r.nid <> %d AND n.type IN ({$types}) GROUP BY n.nid HAVING score > 0 ORDER BY score DESC, r.vid DESC";
  }
  $query = db_rewrite_sql($query, 'n', 'nid');
  $result = db_query_range($query, strip_tags($text), $node->nid, 0, variable_get('similar_num_display', 5));
  while ($node = db_fetch_object($result)) {
    $content = node_load($node->nid);
    $no_follow = variable_get('similar_rel_nofollow', 0) ? array(
      'rel' => 'nofollow',
    ) : array();
    if ($teaser) {
      $items[] = '<div class="similar-title">' . l($content->title, 'node/' . $node->nid, array(
        'attributes' => array(
          'title' => $content->title,
        ) + $no_follow,
        'absolute' => TRUE,
      )) . '</div><div class="similar-teaser">' . check_markup($content->teaser, $content->format, FALSE) . '</div>';
    }
    else {
      $items[] = l($content->title, 'node/' . $node->nid, array(
        'attributes' => array(
          'title' => $content->title,
        ) + $no_follow,
      ));
    }
  }
  return sizeof($items) > 0 ? theme('item_list', $items) : '';
}

Functions

Namesort descending Description
similar_block Implements hook_block().
similar_help Implements hook_help().
similar_theme Implements hook_theme().
theme_similar_content Queries the database for similar entries and puts them in a HTML list.
_similar_content_type_escape Strips characters from node type strings.
_similar_force_int Prevents SQL injection be forcing an integer value.
_similar_published_node_types Queries for published node types. http://drupal.org/node/33444
_similar_taxonomy_membership Queries for taxonomies to which a specific node belongs. http://drupal.org/node/51041
_similar_taxonomy_names Queries for taxonomy names. http://drupal.org/node/51041