| 1 |
<?php |
<?php |
| 2 |
// $Id: node_recommendation.module,v 1.1.4.8 2007/07/23 14:11:41 hickory Exp $ |
// $Id: $ |
| 3 |
|
|
| 4 |
|
|
| 5 |
|
/** |
| 6 |
|
* Implementation of hook_init |
| 7 |
|
*/ |
| 8 |
|
function node_recommendation_init() { |
| 9 |
|
// ensure we are not serving a cached page |
| 10 |
|
if (function_exists('drupal_set_content')) { |
| 11 |
|
// we don't do this in hook_menu to ensure the files are already included when |
| 12 |
|
// views_menu is executed |
| 13 |
|
if (module_exists('views')) { |
| 14 |
|
include_once('./'. drupal_get_path('module', 'node_recommendation') .'/node_recommendation_views.inc'); |
| 15 |
|
} |
| 16 |
|
} |
| 17 |
|
} |
| 18 |
|
|
| 19 |
/* |
/* |
| 20 |
* hook_help |
* hook_help |
| 29 |
} |
} |
| 30 |
|
|
| 31 |
/** |
/** |
| 32 |
* hook_perm(). |
* hook_cron |
| 33 |
*/ |
* update the node_recommendations table |
| 34 |
function node_recommendation_perm() { |
*/ |
| 35 |
return array('access recommendations'); |
function node_recommendation_cron() { |
| 36 |
|
// big query to calc recommendations for all users |
| 37 |
|
// get the user limit |
| 38 |
|
$limit = 25; |
| 39 |
|
$query_type = variable_get('cre_query_type', 'accurate'); |
| 40 |
|
|
| 41 |
|
// set a variable to use to make sure we don't time out |
| 42 |
|
variable_set('node_recommendation_calc', time()); |
| 43 |
|
|
| 44 |
|
// register a function to check that variable |
| 45 |
|
register_shutdown_function('node_recommendation_shutdown'); |
| 46 |
|
|
| 47 |
|
$tag = variable_get('node_recommendation_tag', 'vote'); |
| 48 |
|
|
| 49 |
|
// grab the most recently voted users |
| 50 |
|
$users = db_query_range("SELECT DISTINCT(uid) FROM {votingapi_vote} WHERE uid <> 0 ORDER BY timestamp DESC", 0, $limit); |
| 51 |
|
while ($account = db_fetch_object($users)) { |
| 52 |
|
if ($query_type == 'points') { |
| 53 |
|
$field = 'ABS(node_similarity.sum/node_similarity.count)'; |
| 54 |
|
$voting_style = 'points'; |
| 55 |
|
$sql = "SELECT n.nid, $field as score FROM {cre_similarity_matrix} node_similarity JOIN {node} n ON n.nid = content_id1 JOIN {votingapi_vote} r ON r.content_id = node_similarity.content_id2 WHERE content_type1 = 'node' AND content_type2 = 'node' AND r.tag = '$tag' AND r.value_type = '$voting_style' AND r.uid = $account->uid"; |
| 56 |
|
} |
| 57 |
|
else { |
| 58 |
|
$voting_style = 'percent'; |
| 59 |
|
if ($query_type == 'accurate') { |
| 60 |
|
$field = "SUM(node_similarity.sum + node_similarity.count * r.value) / SUM(node_similarity.count)"; |
| 61 |
|
$sql = "SELECT n.nid, $field as score FROM {cre_similarity_matrix} node_similarity JOIN {node} n ON n.nid = content_id1 JOIN {votingapi_vote} r ON r.content_id = node_similarity.content_id2 WHERE r.content_type = 'node' AND r.tag = '$tag' AND r.uid = $account->uid AND r.value_type = '$voting_style'"; |
| 62 |
|
} |
| 63 |
|
else { |
| 64 |
|
$average = db_result(db_query("SELECT sum/count from {cre_average_vote} WHERE uid = %d", $account->uid)); |
| 65 |
|
$field = sprintf("SUM(node_similarity.sum + node_similarity.count) / SUM(node_similarity.count) + %d", $average); |
| 66 |
|
$sql = "SELECT n.nid, $field as score FROM {cre_similarity_matrix} node_similarity JOIN {node} n ON n.nid = content_id1"; |
| 67 |
|
} |
| 68 |
|
} |
| 69 |
|
$sql .= " AND n.nid NOT IN (SELECT content_id FROM {votingapi_vote} WHERE uid = $account->uid AND content_type ='node') GROUP BY n.nid"; |
| 70 |
|
$recommendations = db_query($sql); |
| 71 |
|
while($record = db_fetch_object($recommendations)) { |
| 72 |
|
|
| 73 |
|
// we update because that will be the most frequent db change |
| 74 |
|
db_query("UPDATE {node_recommendations} SET score = %f WHERE nid = %d AND uid = %d", $record->score, $record->nid, $account->uid); |
| 75 |
|
if (!db_affected_rows()) { |
| 76 |
|
// if we didn't update anything we have a new record |
| 77 |
|
db_query("INSERT INTO {node_recommendations} VALUES (%d, %d, %f)", $record->nid, $account->uid, $record->score); |
| 78 |
|
} |
| 79 |
|
} |
| 80 |
|
} |
| 81 |
|
|
| 82 |
|
variable_del('node_recommendation_calc'); |
| 83 |
|
|
| 84 |
|
} |
| 85 |
|
|
| 86 |
|
function node_recommendation_shutdown() { |
| 87 |
|
if (variable_get('node_recommendation_calc', FALSE)) { |
| 88 |
|
watchdog('node_recommendation', t('Calculation timed out'), WATCHDOG_ERROR); |
| 89 |
|
variable_del('node_recommendation_calc'); |
| 90 |
|
} |
| 91 |
} |
} |
| 92 |
|
|
| 93 |
/** |
/** |
| 98 |
|
|
| 99 |
if ($may_cache) { |
if ($may_cache) { |
| 100 |
$items[] = array( |
$items[] = array( |
|
'path' => 'recommendations', |
|
|
'title' => t('Your recommendations'), |
|
|
'callback' => 'node_recommendation_recommendations_page', |
|
|
'access' => user_access('access recommendations'), |
|
|
'type' => MENU_SUGGESTED_ITEM |
|
|
); |
|
|
|
|
|
$items[] = array( |
|
| 101 |
'path' => 'admin/settings/cre/node', |
'path' => 'admin/settings/cre/node', |
| 102 |
'title' => t('Node recommendation'), |
'title' => t('Node recommendation'), |
| 103 |
'description' => t('Administer node recommendation settings.'), |
'description' => t('Administer node recommendation settings.'), |
| 118 |
* structured array that represents the setting form |
* structured array that represents the setting form |
| 119 |
*/ |
*/ |
| 120 |
function node_recommendation_settings() { |
function node_recommendation_settings() { |
|
$form['node_recommendation_page_n'] = array( |
|
|
'#type' => 'textfield', |
|
|
'#title' => t('Number of items to display'), |
|
|
'#description' => t('The maximum number of items to display on a node recommendation page.'), |
|
|
'#size' => 3, |
|
|
'#default_value' => variable_get('node_recommendation_page_n', 20), |
|
|
); |
|
|
|
|
|
foreach (node_get_types() as $type) { |
|
|
$form['node_recommendation_days_' . $type->type] = array( |
|
|
'#type' => 'textfield', |
|
|
'#title' => t('Maximum age (in days) of any recommended %type node', array('%type' => $type->name)), |
|
|
'#description' => t('Only nodes newer than this will be recommended; leave blank to recommend nodes of any age.'), |
|
|
'#size' => 3, |
|
|
'#default_value' => variable_get('node_recommendation_days_' . $type->type, NULL), |
|
|
); |
|
|
} |
|
|
|
|
| 121 |
$tags = _node_recommendation_get_tags(); |
$tags = _node_recommendation_get_tags(); |
| 122 |
if (!empty($tags)) { |
if (!empty($tags)) { |
| 123 |
$form['node_recommendation_tag'] = array( |
$form['node_recommendation_tag'] = array( |
| 127 |
'#options' => $tags |
'#options' => $tags |
| 128 |
); |
); |
| 129 |
} |
} |
|
|
|
|
$options = array('1' => t('All types')); |
|
|
foreach (node_get_types() as $type) { |
|
|
$options[$type->type] = $type->name; |
|
|
} |
|
|
|
|
|
$form['node_recommendation_global_type'] = array( |
|
|
'#type' => 'select', |
|
|
'#title' => t('Type of node to recommend'), |
|
|
'#default_value' => variable_get('node_recommendation_global_type', '1'), |
|
|
'#description' => t('Limit all recommendations to nodes of a particular type?'), |
|
|
'#options' => $options |
|
|
); |
|
|
|
|
| 130 |
return system_settings_form($form); |
return system_settings_form($form); |
| 131 |
} |
} |
| 132 |
|
|
| 141 |
} |
} |
| 142 |
|
|
| 143 |
/* |
/* |
|
* hook_block() |
|
|
*/ |
|
|
function node_recommendation_block($op = 'list', $delta = 0, $edit = array()) { |
|
|
global $user; |
|
|
|
|
|
// The $op parameter determines what piece of information is being requested. |
|
|
switch ($op) { |
|
|
case 'list': |
|
|
return array( |
|
|
'0' => array('info' => t('CRE: Recommended nodes (all types)')), |
|
|
'1' => array('info' => t('CRE: Similarly rated nodes')), |
|
|
'2' => array('info' => t('CRE: Recommended nodes (' . variable_get('node_recommendation_type_type_2', 1) . ')')), |
|
|
'3' => array('info' => t('CRE: Recommended nodes (' . variable_get('node_recommendation_type_type_3', 2) . ')')), |
|
|
'4' => array('info' => t('CRE: Recommended nodes (' . variable_get('node_recommendation_type_type_4', 3) . ')')), |
|
|
); |
|
|
break; |
|
|
|
|
|
case 'configure': |
|
|
$form = array(); |
|
|
switch ($delta){ |
|
|
|
|
|
// Personalised recommendations. |
|
|
case 0: |
|
|
$form['node_recommendation_n'] = array( |
|
|
'#type' => 'textfield', |
|
|
'#title' => t('Maximum number of nodes to recommend'), |
|
|
'#size' => 2, |
|
|
'#description' => t('The maximum number of nodes shown in the "Recommended nodes" block'), |
|
|
'#default_value' => variable_get('node_recommendation_n_value', 2) |
|
|
); |
|
|
break; |
|
|
|
|
|
// General similar block. |
|
|
case 1: |
|
|
$form['node_recommendation_n'] = array( |
|
|
'#type' => 'textfield', |
|
|
'#title' => t('Maximum number of nodes to recommend'), |
|
|
'#size' => 2, |
|
|
'#description' => t('The maximum number of nodes shown in the "Similarly rated nodes" block'), |
|
|
'#default_value' => variable_get('node_recommendation_similar_n_value', 4), |
|
|
); |
|
|
break; |
|
|
|
|
|
// Recommendations of a certain type. |
|
|
case 2: |
|
|
case 3: |
|
|
case 4: |
|
|
$options = array(); |
|
|
foreach (node_get_types() as $type) { |
|
|
$options[$type->type] = $type->name; |
|
|
} |
|
|
|
|
|
$form['node_recommendation_type'] = array( |
|
|
'#type' => 'select', |
|
|
'#title' => t('Type of node to recommend'), |
|
|
'#default_value' => variable_get('node_recommendation_type_type_' . $delta, 'story'), |
|
|
'#options' => $options |
|
|
); |
|
|
|
|
|
$form['node_recommendation_n'] = array( |
|
|
'#type' => 'textfield', |
|
|
'#title' => t('Maximum number of nodes to recommend'), |
|
|
'#size' => 2, |
|
|
'#description' => t('The maximum number of nodes shown in the "Recommended nodes of a particular type" block'), |
|
|
'#default_value' => variable_get('node_recommendation_type_n_value_' . $delta, 5), |
|
|
); |
|
|
break; |
|
|
|
|
|
} |
|
|
return $form; |
|
|
|
|
|
case 'save': |
|
|
switch ($delta){ |
|
|
case 0: |
|
|
variable_set('node_recommendation_n_value', $edit['node_recommendation_n']); |
|
|
break; |
|
|
|
|
|
case 1: |
|
|
variable_set('node_recommendation_similar_n_value', $edit['node_recommendation_n']); |
|
|
break; |
|
|
|
|
|
case 2: |
|
|
case 3: |
|
|
case 4: |
|
|
variable_set('node_recommendation_type_type_' . $delta, $edit['node_recommendation_type']); |
|
|
variable_set('node_recommendation_type_n_value_' . $delta, $edit['node_recommendation_n']); |
|
|
break; |
|
|
} |
|
|
return; |
|
|
|
|
|
case 'view': |
|
|
default: |
|
|
switch ($delta) { |
|
|
|
|
|
// Personalised recommendations. |
|
|
case 0: |
|
|
return array( |
|
|
'subject' => l(t('Recommended'), variable_get('node_recommendation_path_to_page', 'recommendations')), |
|
|
'content' => theme('cre_title_list', node_recommendation_get_top_n($user->uid, variable_get('node_recommendation_n_value', 5))), |
|
|
); |
|
|
break; |
|
|
|
|
|
// General similar block. |
|
|
case 1: |
|
|
if (arg(0) == 'node') { |
|
|
return array( |
|
|
'subject' => t('Those that like this also like'), |
|
|
'content' => theme('cre_title_list', node_recommendation_get_similar(variable_get('node_recommendation_similar_n_value', 5))), |
|
|
); |
|
|
} |
|
|
break; |
|
|
|
|
|
// Recommendations of a certain type. |
|
|
case 2: |
|
|
case 3: |
|
|
case 4: |
|
|
$type = variable_get('node_recommendation_type_type_' . $delta, 'story'); |
|
|
return array( |
|
|
'subject' => l(t('Recommended'), variable_get('node_recommendation_path_to_page', 'recommendations') . "/$type"), |
|
|
'content' => theme('cre_title_list', node_recommendation_get_top_n($user->uid, variable_get('node_recommendation_type_n_value_' . $delta, 5), $type)), |
|
|
); |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
/* |
|
|
* Returns the top n nodes of a particular type |
|
|
* |
|
|
* @param $uid |
|
|
* uid for the personalized recommendations |
|
|
* |
|
|
* @param $n |
|
|
* specifies the number of recommendations to return |
|
|
*/ |
|
|
|
|
|
function node_recommendation_get_top_n($uid, $n, $type = 'node') { |
|
|
return cre_top($uid, 'node_recommendation_cre_query', $n, 'node', variable_get('node_recommendation_tag', 'vote'), 'node', array($type)); |
|
|
} |
|
|
|
|
|
/* |
|
|
* Returns nodes that were rated similarly to the currently viewed node |
|
|
* |
|
|
* @param $n |
|
|
* Specifies the number of recommendations to return |
|
|
*/ |
|
|
function node_recommendation_get_similar($n) { |
|
|
$nid = arg(1); |
|
|
if (is_numeric($nid)){ |
|
|
return cre_similar($n, $nid, 'node', variable_get('node_recommendation_tag', 'vote')); |
|
|
} |
|
|
} |
|
|
|
|
|
/** |
|
|
* Callback to display a page of recommended items |
|
|
*/ |
|
|
function node_recommendation_recommendations_page($type = 'node', $uid = NULL) { |
|
|
if (!is_numeric($uid)){ |
|
|
global $user; |
|
|
$uid = $user->uid; |
|
|
} |
|
|
|
|
|
$n = variable_get('node_recommendation_page_n', 20); |
|
|
|
|
|
$recommendations = $uid ? cre_top($uid, 'node_recommendation_cre_query', $n, 'node', variable_get('node_recommendation_tag', 'vote'), 'node', array($type)) : _node_recommendation_get_top_rated_nodes($n); // return recommendations for logged-in users, top-rated nodes otherwise. |
|
|
|
|
|
if($uid != 0 ) { |
|
|
$recommendations = cre_top($uid, 'node_recommendation_cre_query', $n, 'node', variable_get('node_recommendation_tag', 'vote'), 'node', array($type)); |
|
|
if($recommendations == NULL) { |
|
|
// user doesn't have any recommendations therefore! return top nodes |
|
|
$recommendations = _node_recommendation_get_top_rated_nodes($n); |
|
|
} |
|
|
} |
|
|
else { // anon user therefore just get the top rated nodes |
|
|
$recommendations = _node_recommendation_get_top_rated_nodes($n); |
|
|
} |
|
|
$content = array(); |
|
|
foreach ($recommendations as $node) { |
|
|
$content[] = theme('cre_node_view', $node->content_id); |
|
|
} |
|
|
return implode("\n", $content); |
|
|
} |
|
|
|
|
|
/* |
|
|
* gets the highest rated items |
|
|
*/ |
|
|
function _node_recommendation_get_top_rated_nodes($n) { |
|
|
$result = db_query("SELECT c.content_id as content_id, c.value as avg_vote, n.title |
|
|
FROM {votingapi_cache} c, {node} n |
|
|
WHERE c.content_id = n.nid AND c.function = 'average' AND c.tag = '%s' AND c.content_type = 'node' |
|
|
ORDER BY avg_vote DESC LIMIT %d", |
|
|
variable_get('node_recommendation_tag', 'vote'), $n); |
|
|
$voting_results = array(); |
|
|
while ($tmp = db_fetch_object($result)) { |
|
|
$voting_results[] = $tmp; |
|
|
} |
|
|
return $voting_results; |
|
|
} |
|
|
|
|
|
function node_recommendation_cre_query(&$query, $uid, $args) { |
|
|
$query->add_table('{node} n'); |
|
|
$query->add_column('n.title'); |
|
|
$query->add_column('n.type'); |
|
|
$query->add_where('n.nid = d.content_id1'); |
|
|
//$query->add_where('n.uid <> %d', $uid); |
|
|
$query->add_where("r.tag = '%s'", variable_get(node_recommendation_tag, 'vote')); |
|
|
|
|
|
if ($type = $args[0]){ |
|
|
$query->add_where("n.type = '%s'", $type); |
|
|
} |
|
|
else { |
|
|
$global_type = variable_get('node_recommendation_global_type', '1'); |
|
|
if ($global_type != '1'){ |
|
|
$query->add_where("n.type = '%s'", $global_type); |
|
|
} |
|
|
} |
|
|
|
|
|
if ($numdays = variable_get('node_recommendation_days_' . $type, FALSE)) { |
|
|
$query->add_where('n.created >= %d', time() - (60 * 60 * 24 * $numdays)); |
|
|
} |
|
|
} |
|
|
|
|
|
/* |
|
| 144 |
* private function to return all voting tags present in the system |
* private function to return all voting tags present in the system |
| 145 |
*/ |
*/ |
| 146 |
function _node_recommendation_get_tags() { |
function _node_recommendation_get_tags() { |