'admin/settings/user_karma', 'title' => t('User Karma'), 'description' => t('Settings for the user karma module'), 'callback' => 'drupal_get_form', 'callback arguments' => array('user_karma_admin_settings'), 'access' => user_access('administer site configuration'), 'type' => MENU_NORMAL_ITEM ); // NON-CACHABLE ENTRIES } else { $items[] = array('path' => 'user_karma/roles_mass_recalculation', 'title' => t('User karma - roles mass-recalculation'), 'callback' => 'drupal_get_form', 'callback arguments' => array('user_karma_roles_mass_recalculation_confirm'), 'access' => user_access('administer site configuration'), 'type' => MENU_CALLBACK ); $items[] = array('path' => 'user_karma/karma_mass_recalculation', 'title' => t('User karma - karma mass-recalculation'), 'callback' => 'drupal_get_form', 'callback arguments' => array('user_karma_karma_mass_recalculation_confirm'), 'access' => user_access('administer site configuration'), 'type' => MENU_CALLBACK ); } // That's all! return $items; } ################################################################## # START OF SETTINGS FORM ################################################################## /** * Return the configuration form. It's quite a form... * * @return * The config form */ function user_karma_admin_settings() { // Form for the recalculation $form['roles_mass_recalculation'] = array( '#weight' => -5, '#type' => 'submit', '#value' => t('Run the mass-recalculation for user roles') ); // Form for the recalculation $form['karma_mass_recalculation'] = array( '#weight' => -5, '#type' => 'submit', '#value' => t('Run the mass-recalculation for user karma') ); $result = db_query('SELECT rid, name FROM {role} ORDER BY name'); $role_options = array(); $role_options[0] = t("None"); while ($role = db_fetch_object($result)) { if ($role->rid != 1 && $role->rid != 2) { $role_options[$role->rid] = $role->name; } } $form['role_assigning'] = array( '#type' => 'fieldset', '#title' => t('Assigning roles to karma amounts'), '#collapsible' => FALSE, '#collapsed' => FALSE, ); $karma_role_options[0] = t("Consider these absolute values"); $karma_role_options[1] = t("Consider these as the user's ranking, out of 10000"); for ($i = 0;$i < MAX_ROLES;$i++) { $form['role_assigning']['user_karma_role_'. $i] = array( '#type' => 'select', '#title' => t('Role to be assigned...'), '#default_value' => variable_get('user_karma_role_'. $i, ''), '#options' => $role_options, '#description' => t('This role will be assigned if the user hits the karma in the range below'), ); $form['role_assigning']['user_karma_role_'. $i .'_from'] = array( '#type' => 'textfield', '#title' => t('From'), '#description' => t('Leaving an empty value means "from 0"'), '#size' => 7, '#maxlength' => 7, '#default_value' => variable_get('user_karma_role_'. $i .'_from', ''), ); $form['role_assigning']['user_karma_role_'. $i .'_to'] = array( '#type' => 'textfield', '#title' => t('To'), '#description' => t('Leaving an empty value means "to infinite"'), '#size' => 7, '#maxlength' => 7, '#default_value' => variable_get('user_karma_role_'. $i .'_to', ''), ); $form['role_assigning']['user_karma_role_'. $i .'_type'] = array( '#title' => t('How to interpret the numbers in the range'), '#type' => 'radios', '#options' => $karma_role_options, '#default_value' => variable_get('user_karma_role_'. $i .'_type', 0), ); } $form['user_karma_point_duration']=array( '#type' => 'textfield', '#title' => t('How many days karma points are valid for.'), '#size' => 4, '#maxlength' => 4, '#default_value' => variable_get('user_karma_point_duration', '90'), ); $form['user_karma_show_karma_in_user_page']=array( '#type' => 'checkbox', '#title' => t('Show karma in the user profile page'), '#default_value' => variable_get('user_karma_show_karma_in_user_page', FALSE), ); $form['user_karma_show_ranking_in_user_page']=array( '#type' => 'checkbox', '#title' => t('Show ranking in the user profile page'), '#default_value' => variable_get('user_karma_show_ranking_in_user_page', FALSE), ); return system_settings_form($form); } /** * Implementation of hook_validate(). * Used to validate user_karma_admin_settings() * The only interesting part is that it will "derail" the flow in case * the user clicked on the "Mass recalculation" button */ function user_karma_admin_settings_validate($form_id, $form_values) { // This hook will sneakly intercept the roles recalculation request // and will redirect accordingly to the roles mass-recalculation // function if ($form_values['op'] == t('Run the mass-recalculation for user roles')) { drupal_goto('user_karma/roles_mass_recalculation'); } // This hook will sneakly intercept the karma recalculation request // and will redirect accordingly to the karma mass-recalculation // function if ($form_values['op'] == t('Run the mass-recalculation for user karma')) { drupal_goto('user_karma/karma_mass_recalculation'); } } /** * This is a simple confirmation step to make sure that the * user really wants to go ahead * * @return * The confirmation form */ function user_karma_roles_mass_recalculation_confirm() { return confirm_form( array(), t('Are you sure you want to recalculate the role of every single user?'), 'admin/settings/user_karma', t('The role of every single user will be recalculated according to the current range values'), t('Run mass-recalcuation of roles'), t('Cancel') ); } /** * At this point, the answer was confirmed. So, the recalculation * actually takes place * * @param $form_id * The form id * @param $form * The form * @return * A redirection to the module's configuration screen */ function user_karma_roles_mass_recalculation_confirm_submit($form_id, &$form) { if ($form['confirm']) { user_karma_roles_mass_recalculation(); drupal_set_message(t('The recalculation was done!')); return 'admin/settings/user_karma'; } } /** * This is a simple confirmation step to make sure that the * user really wants to go ahead * * @return * The confirmation form */ function user_karma_karma_mass_recalculation_confirm() { return confirm_form( array(), t('Are you sure you want to recalculate the karma of every single user?'), 'admin/settings/user_karma', t('The karma of every single user will be recalculated'), t('Run mass-recalcuation of karma'), t('Cancel') ); } /** * At this point, the answer was confirmed. So, the recalculation * actually takes place * * @param $form_id * The form id * @param $form * The form * @return * A redirection to the module's configuration screen */ function user_karma_karma_mass_recalculation_confirm_submit($form_id, &$form) { if ($form['confirm']) { user_karma_karma_mass_recalculation(); drupal_set_message(t('The recalculation was done!')); return 'admin/settings/user_karma'; } } ################################################################## # END OF SETTINGS FORM ################################################################## ################################################################## # START OF INTERFACING WITH THE VOTING API ################################################################## /** * This is the hook that intercept a vote - insert */ function user_karma_votingapi_insert($v) { $value = $v->value; user_karma_new_vote('insert', $v, $value); } /** * This is the hook that intercept a vote - update */ function user_karma_votingapi_update($v, $value) { user_karma_new_vote('update', $v, $value); } /** * This function is the super-hook for hook_votingapi_update and _insert, * since it's identical as far as karma is concerned. If a node or a comment * are voted, this function will create a fake vote from admin to the user, * with the user's current karma (calculated through * user_karma_calculate_karma() ). * * @param $op * Not really used (yet). It tells the function if it was an * insert or an update * @param $v * The vote object * @value * The vote value. This holds the "new" value of the vote if it was an update, or the inserted value if it was an isert * @return * The karma amount * */ function user_karma_new_vote($op, $v, $value) { // If the node type is 'node' or 'article', then get // started - load the object that got the karma if ($v->content_type == 'node' || $v->content_type == 'comment') { // Load the node if ($v->content_type == 'node') { $o = node_load($v->content_id); //$o->sk_type = 'node'; //$o->sk_id = $o->nid; $recipient_uid = $o->uid; } if ($v->content_type == 'comment') { $o = _comment_load($v->content_id); //$o->sk_type = 'comment'; //$o->sk_id = $o->cid; $recipient_uid = $o->uid; } // Assign the karma, IF the recipient's UID was found if ($recipient_uid) { user_karma_assign_user_karma($recipient_uid); } } return TRUE; } function user_karma_assign_user_karma($recipient_uid) { // Calculate the new karma $total_karma_amount=user_karma_calculate_karma($recipient_uid); // Create the voting object $new_vote= new stdClass(); $new_vote->value = $total_karma_amount; $new_vote->value_type = 'karma_points'; $new_vote->tag = 'karma'; // Actually do the voting - it's basically a vote to the author // of the object (comment or node), that is, the ->uid votingapi_set_vote('user', $recipient_uid, $new_vote, 1); user_karma_calculate_role($recipient_uid); } /** * Calculates a user's karma, and returns it * * @param $recipient_uid * The user receiving the karma * @return * The karma amount * */ function user_karma_calculate_karma($recipient_uid) { $query = "SELECT SUM(vv.value) FROM {votingapi_vote} vv LEFT JOIN {node} n ON vv.content_type='node' AND vv.content_id=n.nid WHERE vv.content_type='node' AND vv.value_type='points' AND n.uid = $recipient_uid AND ". user_karma_sql_duration("n.created"); $karma_from_nodes = db_result(db_query($query)); //drupal_set_message("Query: $query ; Karma from nodes: $karma_from_nodes"); $query = "SELECT SUM(vv.value) FROM {votingapi_vote} vv LEFT JOIN {comments} c ON vv.content_type='comment' AND vv.content_id=c.cid WHERE vv.content_type='comment' AND vv.value_type='points' AND c.uid = $recipient_uid AND ". user_karma_sql_duration("c.timestamp"); $karma_from_comments = db_result(db_query($query)); //drupal_set_message("Query: $query ; Karma from comments: $karma_from_comments"); $total_karma=$karma_from_nodes + $karma_from_comments; return (int) $total_karma; } /** * Implementation of hook_votingapi_calculate * It's important to have this hook so that a cached entry is created for * the user which has just received karma. * This will probably get called also when another module ever votes on * a user. But that's OK. * */ function user_karma_votingapi_calculate(&$cache, $votes, $content_type, $content_id) { if ($content_type == 'user') { // Get the caches vote by admin. Basically, the cache here is just // a copy of admin's vote. $existing_vote = user_karma_user_karma($content_id); //drupal_set_message("Existing vote: $existing_vote"); // Set the $cache variable, so that a new record will be added to // votingapi_cache $cache['karma']['karma_points']['sum'] = $existing_vote; } return TRUE; } ################################################################## # END OF INTERFACING WITH THE VOTING API ################################################################## /** * The module's cron hook. It's used to: * * - Recalculate the karma for those users who got karma from a comment * or a node, but that karma has now expired * * @return * Nothing */ function user_karma_cron() { //drupal_set_message("user_karma's cron started!"); // This will only happen once every 12 hours maximum $cron_timestamp = variable_get('user_karma_cron_timestamp', ''); if ((time() - $cron_timestamp) >= 60*60*12 || TRUE) { //if ((time() - $cron_timestamp) >= 60*60*12) { //drupal_set_message("In the 'every 12 hours' section!"); // ************************************************************ // Recalculate the karma for users with comments which have // expired // ************************************************************ // Only do the recalculation if an expiration is set for the // users' karma points $days = variable_get('user_karma_point_duration', ''); if ($days != 0) { // Scan through all of the users, load each one and // run the cache recalculation function // This query will only include people who have not received a vote // over the time when a karma vote is valid for. So, if a person has // received a vote over the last 2 weeks and a vote lasts 2 months, the // recalculation won't be done. This will sieve out a few users. $query="SELECT u.uid FROM {users} u LEFT JOIN {votingapi_cache} vc ON u.uid = vc.content_id WHERE vc.content_type = 'user' AND ". user_karma_sql_duration('vc.timestamp', '<'); //drupal_set_message("QUERY: $query"); $result = db_query($query); while ($u = db_fetch_object($result)) { user_karma_assign_user_karma($u->uid); } } variable_set('user_karma_cron_timestamp', time()); } } ################################################################## # START OF FUNCTIONS FOR THE MANAGEMENT OF ROLES ################################################################## /** * This is where the actual mass-recalculation takes place. * First of all, ALL of the roles managed by the module are wiped out. * This is why it's CRUCIAL that only specific karma-roles are managed here. * Then, the function user_karma_calculate_role($uid,FALSE) is run for * each user. Yes, even users with no karmic points, which will have 0 karma * whick might, in turn, be associated with some roles. * That 'FALSE' is to tell user_karma_calculate_role() that the role * deletion has already taken place * * @return * Nothing */ function user_karma_roles_mass_recalculation() { // Just in case. This could take a little while... set_time_limit(300); // Deletes right away any role that this module might possibly manage for ($i = 0;$i < MAX_ROLES;$i++) { $r = variable_get('user_karma_role_'. $i, ''); if ($r != 0) { //drupal_set_message("Deleting ALL roles $r"); db_query('DELETE FROM {users_roles} WHERE rid = %d', $r ); } } // Get the list of users. You need the COMPLETE list, since you // might want to assign roles to users with no karma points assigned $result = db_query('SELECT uid FROM {users} WHERE status=1'); // Recalculate for each user while ($data = db_fetch_object($result)) { $uid = $data->uid; user_karma_calculate_role($uid, FALSE); } } /** * This is where the actual karma mass-recalculation takes place. * * @return * Nothing */ function user_karma_karma_mass_recalculation() { // Just in case. This could take a little while... set_time_limit(9000); // Make up the list of users which will need karma recalculation // based on who added a node $query = "SELECT uid FROM {node} GROUP BY uid"; $result = db_query($query); while ($u = db_fetch_object($result)) { $users[$u->uid] = TRUE; } // Make up the list of users whic will need karma recalculation // based on who added a comment $query = "SELECT uid FROM {comments} GROUP BY uid"; $result = db_query($query); while ($u = db_fetch_object($result)) { $users[$u->uid] = TRUE; } // At this point, the keys of the $users variable will have a list of // unique UIDs: recalculate foreach ($users as $uid => $k) { user_karma_assign_user_karma($uid); } } /** * Recalculates the user's roles. NOTE: this function will work on * absolute karma values (which sucks rocks) AND on tue user's rank * (which is cool) * * @param $uid * The user id of the user that will have new karmic-roles assigned * @param $delete * Default is TRUE. If set to FALSE, this function will not delete roles * before re-assigning them. Mass-recalculation is one of the possible * reasons * @return * Nothing */ function user_karma_calculate_role($uid, $delete = TRUE) { static $users_t; //drupal_set_message("."); //drupal_set_message("Calculating role for $uid"); // Calculates the total number of users, and puts it in a static // variable. This way, if a mass recalculation is needed, // this query is not run thousands of times if (!$users_t) { $users_t = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE status=1")); //drupal_set_message("Users_t is now $users_t"); } // Get the karma $total_karma = user_karma_user_karma($uid); //drupal_set_message("Total karma is $total_karma"); // Calculate the rank. Note that it's on a 1 to 10000 scale, and that // if the result is "1" then the rank is left as "1"! $rank = user_karma_user_rank($uid); //drupal_set_message("RB: $rank"); if ($rank != 1) { $rank= (int) ($rank / $users_t * 10000); } //drupal_set_message("RA: $rank"); for ($i = 0;$i < MAX_ROLES;$i++) { // Create the vars with short names for the three roles/ranges. $r = variable_get('user_karma_role_'. $i, ''); $f = variable_get('user_karma_role_'. $i .'_from', ''); $t = variable_get('user_karma_role_'. $i .'_to', ''); $type = variable_get('user_karma_role_'. $i .'_type', ''); //drupal_set_message("Here: $r,$f,$t,$type"); if ($r != 0) { if ($delete) { db_query('DELETE FROM {users_roles} WHERE uid = %d AND rid = %d', $uid, $r ); //drupal_set_message("Delete is on"); } // Set which variable will be checked. if ($type == 0) { $variable = $total_karma; } else { $variable=$rank; } //drupal_set_message("Variable is $variable"); //drupal_set_message("Type is $type. Comparing $variable with $f and $t"); // If the variable is within the range, then assign the role! if ( ($variable >= $f || $f == '') && ($variable <= $t || $t =='') ) { db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $uid, $r ); //drupal_set_message("ROLE '$r' ADDED"); } } } } /** * Returns a list of roles which are handled by the karma module * * @return * An array with a list of rids */ function user_karma_roles() { $result = array(); for ($i = 0;$i < MAX_ROLES;$i++) { $r = variable_get('user_karma_role_'. $i, '0'); if ( $r != 0) { $result[] = $r; } } return $result; } /** * Returns the list of KARMA roles attached to the user. THis function * will only return roles if they are managed by the karma module! * Plus, it caches the result to minimise queries. * * @param $uid * The user id of a user * @return * An array with a list of rids ATTACHED to that user * */ function user_karma_roles_for_user($uid) { static $user_roles; // If the user isn't cached, then calculate its roles and put them // in the static $user_roles variable if (!isset($user_roles[$uid])) { //drupal_set_message("Calculating it!"); // After this, the var will be defined $user_roles[$uid] = array(); // Get the list of roles which are connected to the karma module $karma_roles = user_karma_roles(); // Make up the query $query = " 1 = 0 "; for ($i = 0; $i < sizeof($karma_roles);$i++) { $query .= " OR rid = $karma_roles[$i] "; } // Make up the $return variable with the query's result $result = db_query("SELECT rid FROM {users_roles} WHERE uid = $uid AND ($query)"); while ($data = db_fetch_object($result)) { $user_roles[$uid][] = $data->rid; } } // End of calculation if the user wasn't in the cache already //drupal_set_message("HERE: ". $user_roles[$uid][0]); //drupal_set_message("HERE: ". $user_roles[$uid][1]); //drupal_set_message("HERE: ". $user_roles[$uid][2]); return $user_roles[$uid]; } /** * Implementation of hook_user(). * This will make sure that the user is assigned the right role * as soon as s/he signs up. */ function user_karma_user($op, &$edit, &$account, $category = NULL) { switch ($op) { // Recalculate the role after an update case 'after_update': user_karma_calculate_role($account->uid); break; case 'view': return user_karma_view_profile($account); break; } } ################################################################## # END OF FUNCTIONS FOR THE MANAGEMENT OF ROLES ROLES ################################################################## ################################################################## # START OF FUNCTIONS TO DISPLAY KARMA ################################################################## function user_karma_view_profile($account) { $uid = $account->uid; if (variable_get('user_karma_show_ranking_in_user_page', FALSE)) { $fields['User karma']['rank'] = array( 'value' => '
Rank
'. user_karma_user_rank($uid) .'
' ); } if (variable_get('user_karma_show_karma_in_user_page', FALSE)) { $fields['User karma']['karma'] = array( 'value' => '
Karma
'. user_karma_user_karma($uid) .'
' ); } return $fields; } ################################################################## # END OF FUNCTIONS TO DISPLAY KARMA ################################################################## ################################################################## # START OF HELPER FUNCTIONS ################################################################## /** * Helper function to create a nice, "fitting" SQL to check that * a karma point is not expired. This function is really only used when * considering * * @param $field_name * The name of the "created" field (it might be aliased) * @return * A nice SQL fragment that will fit after an AND and after a WHERE */ function user_karma_sql_duration($field_name, $operator = '>') { $days = variable_get('user_karma_point_duration', ''); // This is important so that it still works if it's just after an "AND" $return_str = " 1=1"; // If the day is set, ADD the bit that checks the point's expiry date if ( $days != '') { $seconds = $days * 86400; $return_str .= " AND $field_name $operator UNIX_TIMESTAMP(CURRENT_TIMESTAMP()) - $seconds "; } // That's it! return $return_str; } function user_karma_user_rank($uid) { // If there is no karma stored, then the comparison is made with "0". // Otherwise, the comparison is made with a sub-query. // THIS IS BECAUSE ANY INTEGER COMPARED TO NULL IS 0, WHICH WON'T WORK // IN TERMS OF RANKING $r = db_result(db_query("SELECT COUNT(*) FROM {votingapi_cache} vc2 WHERE vc2.content_type = 'user' AND vc2.value_type='karma_points' AND vc2.tag='karma' AND vc2.function='sum' AND vc2.content_id=%d", $uid)); if ($r == 0) { $extra_bit = "0"; } else { $extra_bit = "(SELECT value FROM {votingapi_cache} vc2 WHERE vc2.content_type = 'user' AND vc2.value_type='karma_points' AND vc2.tag='karma' AND vc2.function='sum' AND vc2.content_id=%d)"; } // Query curtesy of http://arjen-lentz.livejournal.com/55083.html and // http://arjen-lentz.livejournal.com/56292.html - EVERYBODY should // read those pages! $r = db_result(db_query("SELECT COUNT(*)+1 AS ranking FROM {votingapi_cache} vc LEFT JOIN {users} u ON u.uid = vc.content_id WHERE vc.content_type = 'user' AND vc.value_type='karma_points' AND vc.tag='karma' AND vc.function='sum' AND vc.value > $extra_bit", $uid)); return $r; } function user_karma_user_karma($uid) { $r = votingapi_get_vote('user', $uid, 'karma_points', 'karma', 1); return (int)$r->value; } ################################################################## # START OF HELPER FUNCTIONS ################################################################## # CHUNK #1 //return (db_result(db_query("SELECT COUNT(*)+1 as ranking FROM {user_karma_cache} skc LEFT JOIN {users} u ON u.uid = skc.oid WHERE u.status=1 AND skc.otype = 'u' AND karma > (SELECT karma FROM {user_karma_cache} sk WHERE sk.otype='u' AND sk.oid=%d)", $uid))); # Curtesy of http://arjen-lentz.livejournal.com/55083.html and http://arjen-lentz.livejournal.com/56292.html - EVERYBODY should read it!