| 6 |
* Module that shows a block listing similar entries. |
* Module that shows a block listing similar entries. |
| 7 |
* NOTE: Uses MySQL's FULLTEXT indexing for MyISAM tables. |
* NOTE: Uses MySQL's FULLTEXT indexing for MyISAM tables. |
| 8 |
* |
* |
|
* Caching feature sponsored by http://xomba.com/ |
|
|
* |
|
| 9 |
* @author David Kent Norman http://deekayen.net/ |
* @author David Kent Norman http://deekayen.net/ |
| 10 |
* @author Arnab Nandi http://arnab.org/ |
* @author Arnab Nandi http://arnab.org/ |
| 11 |
*/ |
*/ |
| 24 |
} |
} |
| 25 |
|
|
| 26 |
/** |
/** |
| 27 |
* Implementation of hook_block(). |
* Implementation of hook_block_list(). |
| 28 |
* |
*/ |
| 29 |
* This hook both declares to Drupal what blocks are provided by the module, and |
function similar_block_list() { |
| 30 |
* generates the contents of the blocks themselves. |
$blocks[0]['info'] = t('Similar entries'); |
| 31 |
* |
$blocks[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE; |
| 32 |
* @param string $op |
return $blocks; |
| 33 |
* @param integer $delta |
} |
| 34 |
* @param array $edit |
|
| 35 |
*/ |
/** |
| 36 |
function similar_block($op = 'list', $delta = 0, $edit = array()) { |
* Implementation of hook_block_configure(). |
| 37 |
switch ($op) { |
*/ |
| 38 |
case 'list': |
function similar_block_configure($delta = 0) { |
| 39 |
$blocks[0]['info'] = t('Similar entries'); |
$form = array(); |
| 40 |
$blocks[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE; |
if ($delta == 0) { |
| 41 |
return $blocks; |
$form['similar_teaser_enabled'] = array( |
| 42 |
|
'#type' => 'radios', |
| 43 |
case 'configure': |
'#title' => t('Include teaser text'), |
| 44 |
$form = array(); |
'#default_value' => variable_get('similar_teaser_enabled', 0), |
| 45 |
if ($delta == 0) { |
'#options' => array(t('No'), t('Yes')) |
| 46 |
$form['similar_teaser_enabled'] = array( |
); |
| 47 |
'#type' => 'radios', |
$form['similar_rel_nofollow'] = array( |
| 48 |
'#title' => t('Include teaser text'), |
'#type' => 'radios', |
| 49 |
'#default_value' => variable_get('similar_teaser_enabled', 0), |
'#title' => t('Block search engines'), |
| 50 |
'#options' => array(t('No'), t('Yes')) |
'#description' => t('Adds rel="nofollow" to the HTML source of similar links so search engines won\'t count similar links in their ranking calculations.'), |
| 51 |
); |
'#default_value' => variable_get('similar_rel_nofollow', 0), |
| 52 |
$form['similar_rel_nofollow'] = array( |
'#options' => array(t('No'), t('Yes')) |
| 53 |
'#type' => 'radios', |
); |
| 54 |
'#title' => t('Block search engines'), |
for ($i=1, $options=array(); $i < 101; $options[$i] = $i, $i+=1); |
| 55 |
'#description' => t('Adds rel="nofollow" to the HTML source of similar links so search engines won\'t count similar links in their ranking calculations.'), |
$form['similar_num_display'] = array( |
| 56 |
'#default_value' => variable_get('similar_rel_nofollow', 0), |
'#type' => 'select', |
| 57 |
'#options' => array(t('No'), t('Yes')) |
'#title' => t('Number of similar entries to find'), |
| 58 |
); |
'#default_value' => variable_get('similar_num_display', 5), |
| 59 |
for ($i=1, $options=array(); $i < 101; $options[$i] = $i, $i+=1); |
'#options' => $options |
| 60 |
$form['similar_num_display'] = array( |
); |
| 61 |
'#type' => 'select', |
$types = _similar_published_node_types(); |
| 62 |
'#title' => t('Number of similar entries to find'), |
$form['similar_node_types'] = array( |
| 63 |
'#default_value' => variable_get('similar_num_display', 5), |
'#type' => 'checkboxes', |
| 64 |
'#options' => $options |
'#multiple' => TRUE, |
| 65 |
); |
'#title' => t('Node types to display'), |
| 66 |
$types = _similar_published_node_types(); |
'#default_value' => variable_get('similar_node_types', $types), |
| 67 |
$form['similar_node_types'] = array( |
'#options' => $types |
| 68 |
'#type' => 'checkboxes', |
); |
| 69 |
'#multiple' => TRUE, |
|
| 70 |
'#title' => t('Node types to display'), |
if (module_exists('taxonomy')) { |
| 71 |
'#default_value' => variable_get('similar_node_types', $types), |
$names = _similar_taxonomy_names(); |
| 72 |
'#options' => $types |
$form['similar_taxonomy'] = array( |
| 73 |
); |
'#type' => 'fieldset', |
| 74 |
|
'#title' => t('Taxonomy category filter'), |
| 75 |
if (module_exists('taxonomy')) { |
'#collapsible' => TRUE, |
| 76 |
$names = _similar_taxonomy_names(); |
'#collapsed' => TRUE |
| 77 |
$form['similar_taxonomy'] = array( |
); |
| 78 |
'#type' => 'fieldset', |
$form['similar_taxonomy']['similar_taxonomy_filter'] = array( |
| 79 |
'#title' => t('Taxonomy category filter'), |
'#type' => 'radios', |
| 80 |
'#collapsible' => TRUE, |
'#title' => t('Filter by taxonomy categories'), |
| 81 |
'#collapsed' => TRUE |
'#default_value' => variable_get('similar_taxonomy_filter', 0), |
| 82 |
); |
'#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')), |
| 83 |
$form['similar_taxonomy']['similar_taxonomy_filter'] = array( |
'#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.') |
| 84 |
'#type' => 'radios', |
); |
| 85 |
'#title' => t('Filter by taxonomy categories'), |
$form['similar_taxonomy']['similar_taxonomy_select'] = array( |
| 86 |
'#default_value' => variable_get('similar_taxonomy_filter', 0), |
'#type' => 'fieldset', |
| 87 |
'#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')), |
'#title' => t('Taxonomy categories to display'), |
| 88 |
'#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.') |
'#collapsible' => TRUE, |
| 89 |
); |
'#collapsed' => TRUE |
| 90 |
$form['similar_taxonomy']['similar_taxonomy_select'] = array( |
); |
| 91 |
'#type' => 'fieldset', |
$form['similar_taxonomy']['similar_taxonomy_select']['similar_taxonomy_tids'] = array( |
| 92 |
'#title' => t('Taxonomy categories to display'), |
'#type' => 'select', |
| 93 |
'#collapsible' => TRUE, |
'#default_value' => variable_get('similar_taxonomy_tids', array_keys($names)), |
| 94 |
'#collapsed' => TRUE |
'#description' => t('Hold the CTRL key to (de)select multiple options.'), |
| 95 |
); |
'#options' => $names, |
| 96 |
$form['similar_taxonomy']['similar_taxonomy_select']['similar_taxonomy_tids'] = array( |
'#multiple' => TRUE |
| 97 |
'#type' => 'select', |
); |
| 98 |
'#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; |
|
| 99 |
} |
} |
| 100 |
|
return $form; |
| 101 |
|
} |
| 102 |
|
|
| 103 |
|
/** |
| 104 |
|
* Implementation of hook_block_save(). |
| 105 |
|
*/ |
| 106 |
|
function similar_block_save($delta = 0, $edit = array()) { |
| 107 |
|
if ($delta == 0) { |
| 108 |
|
variable_set('similar_teaser_enabled', $edit['similar_teaser_enabled']); |
| 109 |
|
variable_set('similar_rel_nofollow', $edit['similar_rel_nofollow']); |
| 110 |
|
variable_set('similar_num_display', $edit['similar_num_display']); |
| 111 |
|
variable_set('similar_node_types', $edit['similar_node_types']); |
| 112 |
|
if (module_exists('taxonomy')) { |
| 113 |
|
variable_set('similar_taxonomy_filter', $edit['similar_taxonomy_filter']); |
| 114 |
|
variable_set('similar_taxonomy_tids', $edit['similar_taxonomy_tids']); |
| 115 |
|
} |
| 116 |
|
} |
| 117 |
|
} |
| 118 |
|
|
| 119 |
|
/** |
| 120 |
|
* Implementation of hook_block_view(). |
| 121 |
|
*/ |
| 122 |
|
function similar_block_view($delta = 0) { |
| 123 |
|
if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) != 'edit') { |
| 124 |
|
$node = node_load(arg(1)); |
| 125 |
|
} |
| 126 |
|
else { |
| 127 |
|
return; |
| 128 |
|
} |
| 129 |
|
|
| 130 |
|
$similar_node_types = variable_get('similar_node_types', _similar_published_node_types()); |
| 131 |
|
|
| 132 |
|
if ($node->nid > 0 && !empty($similar_node_types[$node->type])) { |
| 133 |
|
unset($similar_node_types); |
| 134 |
|
|
| 135 |
|
switch ($delta) { |
| 136 |
|
case 0: |
| 137 |
|
// The subject is displayed at the top of the block. Note that it should |
| 138 |
|
// be passed through t() for translation. |
| 139 |
|
$block['subject'] = t('Similar entries'); |
| 140 |
|
$block['content'] = theme('similar_content', $node); |
| 141 |
|
} |
| 142 |
|
} |
| 143 |
|
return empty($block['content']) ? '' : $block; |
| 144 |
} |
} |
| 145 |
|
|
| 146 |
/** |
/** |
| 152 |
function _similar_published_node_types() { |
function _similar_published_node_types() { |
| 153 |
$types = array(); |
$types = array(); |
| 154 |
$result = db_query('SELECT DISTINCT(n.type) FROM {node} n WHERE n.status <> 0 ORDER BY n.type ASC'); |
$result = db_query('SELECT DISTINCT(n.type) FROM {node} n WHERE n.status <> 0 ORDER BY n.type ASC'); |
| 155 |
while ($type = db_fetch_object($result)) { |
while ($type = $result->fetchObject()) { |
| 156 |
$types[$type->type] = $type->type; |
$types[$type->type] = $type->type; |
| 157 |
} |
} |
| 158 |
return $types; |
return $types; |
| 166 |
*/ |
*/ |
| 167 |
function _similar_taxonomy_names() { |
function _similar_taxonomy_names() { |
| 168 |
$names = array(); |
$names = array(); |
| 169 |
$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'); |
$result = db_query('SELECT d.tid, v.vid, v.name AS vocab_name, d.name AS data_name FROM {taxonomy_term_data} d, {taxonomy_vocabulary} v WHERE v.vid = d.vid ORDER BY v.name, d.name ASC'); |
| 170 |
while ($data = db_fetch_object($result)) { |
while ($data = $result->fetchObject()) { |
| 171 |
$names[$data->tid] = $data->vocab_name .': '. $data->data_name; |
$names[$data->tid] = $data->vocab_name . ': ' . $data->data_name; |
| 172 |
} |
} |
| 173 |
return $names; |
return $names; |
| 174 |
} |
} |
| 181 |
*/ |
*/ |
| 182 |
function _similar_taxonomy_membership($nid) { |
function _similar_taxonomy_membership($nid) { |
| 183 |
$tids = array(); |
$tids = array(); |
| 184 |
$result = db_query('SELECT t.tid FROM {term_node} t WHERE t.nid = %d', $nid); |
$result = db_query('SELECT t.tid FROM {taxonomy_term_node} t WHERE t.nid = :nid', array(':nid' => $nid)); |
| 185 |
while ($data = db_fetch_object($result)) { |
while ($data = $result->fetchObject()) { |
| 186 |
$tids[$data->tid] = $data->tid; |
$tids[$data->tid] = $data->tid; |
| 187 |
} |
} |
| 188 |
return $tids; |
return $tids; |
| 196 |
} |
} |
| 197 |
|
|
| 198 |
/** |
/** |
| 199 |
* SQL injection prevention |
* SQL injection prevention. Probably overkill. |
| 200 |
*/ |
*/ |
| 201 |
function _similar_force_int(&$item) { |
function _similar_force_int(&$item) { |
| 202 |
$item = (int)$item; |
$item = (int)$item; |
| 231 |
$types = variable_get('similar_node_types', $types); |
$types = variable_get('similar_node_types', $types); |
| 232 |
array_walk($types, '_similar_content_type_escape'); |
array_walk($types, '_similar_content_type_escape'); |
| 233 |
|
|
| 234 |
if (sizeof($types) > 1) { |
$query = db_select('node_revision', 'r'); |
| 235 |
$types = implode("','", $types); |
$query->addField('r', 'nid', 'nid'); |
| 236 |
} |
$query->addExpression('MATCH(r.body, r.title) AGAINST (:matchtext)', 'score', array(':matchtext' => $text)); |
| 237 |
else { |
$query->innerJoin('node', 'n', 'r.nid = n.nid AND r.vid = n.vid'); |
| 238 |
list(, $types) = each($types); |
|
|
} |
|
|
$types = "'$types'"; |
|
| 239 |
if (module_exists('taxonomy') && (variable_get('similar_taxonomy_filter', 0) == 2 && $taxonomy_tids = variable_get('similar_taxonomy_tids', array())) |
if (module_exists('taxonomy') && (variable_get('similar_taxonomy_filter', 0) == 2 && $taxonomy_tids = variable_get('similar_taxonomy_tids', array())) |
| 240 |
|| (variable_get('similar_taxonomy_filter', 0) == 1 && $taxonomy_tids = _similar_taxonomy_membership($node->nid))) { |
|| (variable_get('similar_taxonomy_filter', 0) == 1 && $taxonomy_tids = _similar_taxonomy_membership($node->nid))) { |
| 241 |
|
|
| 242 |
array_walk($taxonomy_tids, '_similar_force_int'); |
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; |
|
|
} |
|
| 243 |
|
|
| 244 |
$result = db_query_range("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 MATCH(r.body, r.title) AGAINST ('%s') AND n.status <> 0 AND r.nid <> %d AND n.type IN ($types) GROUP BY n.nid ORDER BY score DESC, r.vid DESC", $text, $taxonomy_tids, $text, $node->nid, 0, variable_get('similar_num_display', 5)); |
$query->innerJoin('taxonomy_term_node', 't', 'n.nid = t.nid AND t.tid IN (:tids)', array(':tids' => $taxonomy_tids)); |
|
} |
|
|
else { |
|
|
$result = db_query_range("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 MATCH(r.body, r.title) AGAINST ('%s') AND n.status <> 0 AND r.nid <> %d AND n.type IN ($types) GROUP BY n.nid ORDER BY score DESC, r.vid DESC", $text, $text, $node->nid, 0, variable_get('similar_num_display', 5)); |
|
| 245 |
} |
} |
| 246 |
while ($node = db_fetch_object($result)) { |
|
| 247 |
|
$query->where("MATCH(r.body, r.title) AGAINST (:wheretext)", array(':wheretext' => $text)); |
| 248 |
|
$query->condition('n.status', 0, '<>'); |
| 249 |
|
$query->condition('r.nid', $node->nid, '<>'); |
| 250 |
|
$query->condition('n.type', $types, 'IN'); |
| 251 |
|
$query->groupBy('nid'); |
| 252 |
|
$query->orderBy('score', 'DESC'); |
| 253 |
|
$query->orderBy('r.vid', 'DESC'); |
| 254 |
|
$query->range(0, variable_get('similar_num_display', 5)); |
| 255 |
|
$result = $query->execute(); |
| 256 |
|
|
| 257 |
|
while ($node = $result->fetchObject()) { |
| 258 |
$content = node_load($node->nid); |
$content = node_load($node->nid); |
| 259 |
if ($teaser) { |
if ($teaser) { |
| 260 |
$items[] = '<div class="similar-title">'. |
$items[] = '<div class="similar-title">' . |
| 261 |
l($content->title, |
l($content->title, |
| 262 |
'node/'. $node->nid, |
'node/' . $node->nid, |
| 263 |
array( |
array( |
| 264 |
'attributes' => variable_get('similar_rel_nofollow', 0) ? array('rel' => 'nofollow') : NULL, |
'attributes' => variable_get('similar_rel_nofollow', 0) ? array('rel' => 'nofollow') : NULL, |
| 265 |
'absolute' => TRUE |
'absolute' => TRUE |
| 266 |
) |
) |
| 267 |
) . |
) . |
| 268 |
'</div><div class="similar-teaser">'. check_markup($content->teaser, $content->format, FALSE) .'</div>'; |
'</div><div class="similar-teaser">' . check_markup($content->teaser, $content->format, FALSE) . '</div>'; |
| 269 |
} |
} |
| 270 |
else { |
else { |
| 271 |
$items[] = l( |
$items[] = l( |
| 272 |
$content->title, |
$content->title, |
| 273 |
'node/'. $node->nid, |
'node/' . $node->nid, |
| 274 |
array('attributes' => variable_get('similar_rel_nofollow', 0) ? array('rel' => 'nofollow') : NULL) |
array('attributes' => variable_get('similar_rel_nofollow', 0) ? array('rel' => 'nofollow') : NULL) |
| 275 |
); |
); |
| 276 |
} |
} |