votingapi.module in Voting API 6
Same filename and directory in other branches
File
votingapi.moduleView source
<?php
/**
* VotingAPI 2.0
*
* A generalized voting API for Drupal. Modules can cast votes with
* arbitrary properties and VotingAPI will total them automatically.
* Support for basic anonymous voting by IP address, multi-criteria
* voting, arbitrary aggregation functions, etc.
*/
/**
* Implementation of hook_menu. Adds the url path for the votingapi
* settings page.
*/
function votingapi_menu() {
$items = array();
$items['admin/settings/votingapi'] = array(
'title' => 'Voting API',
'description' => 'Global settings for the Voting API.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'votingapi_settings_form',
),
'access callback' => 'user_access',
'access arguments' => array(
'administer voting api',
),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Implementation of hook_init. Loads the Views integration code.
*/
function votingapi_init() {
if (module_exists('views')) {
require_once drupal_get_path('module', 'votingapi') . '/votingapi_views.inc';
}
}
/**
* Implementation of hook_perm
*/
function votingapi_perm() {
return array(
'administer voting api',
);
}
/**
* Implementation of hook_cron. Allows db-intensive recalculations
* to put off until cron-time.
*/
function votingapi_cron() {
if (variable_get('votingapi_calculation_schedule', 'immediate') == 'cron') {
$time = time();
$last_cron = variable_get('votingapi_last_cron', 0);
$result = db_query('SELECT DISTINCT content_type, content_id FROM {votingapi_vote} WHERE timestamp > %d', $last_cron);
while ($content = db_fetch_object($result)) {
votingapi_recalculate_results($content->content_type, $content->content_id, TRUE);
}
variable_set('votingapi_last_cron', $time);
}
}
function votingapi_settings_form($form_state) {
$period = array(
0 => t('Never'),
);
$period += drupal_map_assoc(array(
600,
3600,
10800,
21600,
32400,
43200,
86400,
), 'format_interval');
$form['votingapi_anonymous_window'] = array(
'#type' => 'select',
'#title' => t('Anonymous vote rollover'),
'#description' => t('The amount of time that must pass before two anonymous votes from the same computer are considered unique. Setting this to \'never\' will eliminate most double-voting, but will make it impossible for multiple anonymous on the same computer (like internet cafe customers) from casting votes.'),
'#default_value' => variable_get('votingapi_anonymous_window', 3600),
'#options' => $period,
);
$form['votingapi_calculation_schedule'] = array(
'#type' => 'radios',
'#title' => t('Vote tallying'),
'#description' => t('On high-traffic sites, administrators can use this setting to postpone the calculation of vote results.'),
'#default_value' => variable_get('votingapi_calculation_schedule', 'immediate'),
'#options' => array(
'immediate' => t('Tally results whenever a vote is cast'),
'cron' => t('Tally results at cron-time'),
'manual' => t('Do not tally results automatically: I am using a module that manages its own vote results.'),
),
);
return system_settings_form($form);
}
/**
* Cast a vote on a particular piece of content. If a vote already
* exists, its value is changed. In most cases, this is the function
* that should be used by external modules.
*
* @param $votes
* A single vote object, or an array of vote objects, with the following
* properties:
* $vote->content_type (Optional, defaults to 'node')
* $vote->content_id (Required)
* $vote->value_type (Optional, defaults to 'percent')
* $vote->value (Required)
* $vote->tag (Optional, defaults to 'vote')
* $vote->uid (Optional, defaults to current user)
* $vote->vote_source (Optional, defaults to current IP)
* $vote->timestamp (Optional, defaults to time())
* @param $criteria
* A keyed array used to determine what votes will be deleted
* when the current vote is cast. If no value is specified, all
* votes for the current content by the current user will be reset.
* If an empty array is passed in, no votes will be reset and all
* incoming votes will be saved IN ADDITION to existing ones.
* $criteria['vote_id'] (If this is set, all other keyes are skipped)
* $criteria['content_type']
* $criteria['content_type']
* $criteria['value_type']
* $criteria['tag']
* $criteria['uid']
* $criteria['vote_source']
* $criteria['timestamp'] (If this is set, records with timestamps
* GREATER THAN the set value will be selected.)
* @return
* An array of vote result records affected by the vote. The values
* are contained in a nested array keyed thusly:
* $value = $results[$content_type][$content_id][$tag][$value_type][$function]
* If $silent is TRUE, this array will be empty.
*/
function votingapi_set_vote($votes = array(), $criteria = NULL) {
if (is_object($votes)) {
$votes = array(
$votes,
);
}
$touched = array();
foreach ($votes as $key => $vote) {
// First nuke existing votes by this user.
$vote = _votingapi_prep_vote($vote);
if (!isset($criteria)) {
$criteria = votingapi_current_user_identifier();
$criteria += (array) $vote;
}
if (!empty($criteria)) {
votingapi_delete_votes(votingapi_select_votes($criteria));
}
$votes[$key] = votingapi_add_votes($vote);
$touched[$vote->content_type][$vote->content_id] = TRUE;
}
if (variable_get('votingapi_calculation_schedule', 'immediate') != 'cron') {
foreach ($touched as $type => $ids) {
foreach ($ids as $id => $bool) {
$touched[$type][$id] = votingapi_recalculate_results($type, $id);
}
}
}
return $touched;
}
/**
* Generate a proper identifier for the current user: if they have
* an account, return their UID. Otherwise, return their IP address.
*/
function votingapi_current_user_identifier() {
global $user;
$criteria = array();
if ($user->uid) {
$criteria['uid'] = $user->uid;
}
else {
$criteria['vote_source'] = ip_address();
}
return $criteria;
}
/**
* Save a single vote to the database.
*
* @param $vobj
* An array of vote objects with the following properties:
* $vote->content_type (Optional, defaults to 'node')
* $vote->content_id (Required)
* $vote->value_type (Optional, defaults to 'percent')
* $vote->value (Required)
* $vote->tag (Optional, defaults to 'vote')
* $vote->uid (Optional, defaults to current user)
* $vote->vote_source (Optional, defaults to current IP)
* $vote->timestamp (Optional, defaults to time())
* @return
* The same vote objects, with vote_ids property populated.
*/
function votingapi_add_votes($votes = array()) {
if (is_object($votes)) {
$votes = array(
$votes,
);
}
foreach ($votes as $vobj) {
$vobj = _votingapi_prep_vote($vobj);
db_query("INSERT INTO {votingapi_vote} (content_type, content_id, value, value_type, tag, uid, timestamp, vote_source) VALUES ('%s', %d, %f, '%s', '%s', %d, %d, '%s')", $vobj->content_type, $vobj->content_id, $vobj->value, $vobj->value_type, $vobj->tag, $vobj->uid, $vobj->timestamp, $vobj->vote_source);
$vobj->vote_id = db_last_insert_id('votingapi_vote', 'vote_id');
}
module_invoke_all('votingapi_insert', $votes);
return $votes;
}
/**
* Save a single vote result to the database.
*
* @param robj
* An array of vote result objects with the following properties:
* $robj->content_type
* $robj->content_id
* $robj->value_type
* $robj->value
* $robj->tag
* $robj->function
* $robj->timestamp (Optional, defaults to time())
*/
function votingapi_add_results($vote_results = array()) {
if (is_object($vote_results)) {
$vote_results = array(
$vote_results,
);
}
foreach ($vote_results as $robj) {
$robj->timestamp = time();
db_query("INSERT INTO {votingapi_cache} (content_type, content_id, value, value_type, tag, function, timestamp) VALUES ('%s', %d, %f, '%s', '%s', '%s', %d)", $robj->content_type, $robj->content_id, $robj->value, $robj->value_type, $robj->tag, $robj->function, $robj->timestamp);
}
}
/**
* Delete votes from the database.
*
* @param $votes
* An array of vote objects to delete. Minimally, each object must
* have the vote_id property set.
*/
function votingapi_delete_votes($votes = array()) {
if (!empty($votes)) {
module_invoke_all('votingapi_delete', $votes);
$vids = array();
foreach ($votes as $vote) {
$vids[] = $vote->vote_id;
}
db_query("DELETE FROM {votingapi_vote} WHERE vote_id IN (" . implode(',', array_fill(0, count($vids), '%d')) . ")", $vids);
}
}
/**
* Delete cached vote results from the database.
*
* @param $vote_results
* An array of vote result objects to delete. Minimally, each object
* must have the vote_cache_id property set.
*/
function votingapi_delete_results($vote_results = array()) {
if (!empty($vote_results)) {
$vids = array();
foreach ($vote_results as $vote) {
$vids[] = $vote->vote_cache_id;
}
db_query("DELETE FROM {votingapi_cache} WHERE vote_cache_id IN (" . implode(',', array_fill(0, count($vids), '%d')) . ")", $vids);
}
}
/**
* Select individual votes from the database.
*
* @param $criteria
* A keyed array used to build the select query. Keys can contain
* a single value or an array of values to be matched.
* $criteria['vote_id'] (If this is set, all other keyes are skipped)
* $criteria['content_id']
* $criteria['content_type']
* $criteria['value_type']
* $criteria['tag']
* $criteria['uid']
* $criteria['vote_source']
* $criteria['timestamp'] (If this is set, records with timestamps
* GREATER THAN the set value will be selected.)
* @return
* An array of vote objects matching the criteria.
*/
function votingapi_select_votes($criteria = array()) {
if (!empty($criteria['vote_source'])) {
$criteria['timestamp'] = time() - variable_get('votingapi_anonymous_window', 3600);
}
$votes = array();
$result = _votingapi_query('vote', $criteria);
while ($vote = db_fetch_object($result)) {
$votes[] = $vote;
}
return $votes;
}
/**
* Select cached vote results from the database.
*
* @param $criteria
* A keyed array used to build the select query. Keys can contain
* a single value or an array of values to be matched.
* $criteria['vote_cache_id'] (If this is set, all other keyes are skipped)
* $criteria['content_type']
* $criteria['content_type']
* $criteria['value_type']
* $criteria['tag']
* $criteria['uid']
* $criteria['vote_source']
* $criteria['timestamp'] (If this is set, records with timestamps
* GREATER THAN the set value will be selected.)
* @return
* An array of vote result objects matching the criteria.
*/
function votingapi_select_results($criteria = array()) {
$cached = array();
$result = _votingapi_query('cache', $criteria);
while ($cache = db_fetch_object($result)) {
$cached[] = $cache;
}
return $cached;
}
/**
* Loads all votes for a given piece of content, then calculates and
* caches the aggregate vote results. This is only intended for modules
* that have assumed responsibility for the full voting cycle: the
* votingapi_set_vote() function recalculates automatically.
*
* @param $content_type
* A string identifying the type of content being rated. Node, comment,
* aggregator item, etc.
* @param $content_id
* The key ID of the content being rated.
* @return
* An array of the resulting votingapi_cache records, structured thusly:
* $value = $results[$ag][$value_type][$function]
*/
function votingapi_recalculate_results($content_type, $content_id, $force_calculation = FALSE) {
// if we're operating in cron mode, and the 'force recalculation' flag is NOT set,
// bail out. The cron run will pick up the results.
if (variable_get('votingapi_calculation_schedule', 'immediate') != 'cron' || $force_calculation == TRUE) {
// blow away the existing cache records.
$criteria = array(
'content_type' => $content_type,
'content_id' => $content_id,
);
$old_cache = votingapi_select_results($criteria);
votingapi_delete_results($old_cache);
$votes = votingapi_select_votes($criteria);
$cache = array();
// Loop through, calculate per-type and per-tag totals, etc.
foreach ($votes as $vote) {
switch ($vote->value_type) {
case 'percent':
$cache[$vote->tag][$vote->value_type]['count'] += 1;
$cache[$vote->tag][$vote->value_type]['sum'] += $vote->value;
break;
case 'points':
$cache[$vote->tag][$vote->value_type]['count'] += 1;
$cache[$vote->tag][$vote->value_type]['sum'] += $vote->value;
break;
case 'option':
$cache[$vote->tag][$vote->value_type][$vote->value] += 1;
break;
}
}
// Do a quick loop through to calculate averages.
// This is also a good example of how external modules can do their own processing.
foreach ($cache as $tag => $types) {
foreach ($types as $type => $functions) {
if ($type == 'percent' || $type == 'points') {
$cache[$tag][$type]['average'] = $functions['sum'] / $functions['count'];
}
if ($type == 'percent') {
// we don't actually need the sum for this. discard it to avoid cluttering the db.
unset($cache[$tag][$type]['sum']);
}
}
}
// Give other modules a chance to alter the collection of votes.
drupal_alter('votingapi_results', $cache, $votes, $content_type, $content_id);
// Now, do the caching. Woo.
foreach ($cache as $tag => $types) {
foreach ($types as $type => $functions) {
foreach ($functions as $function => $value) {
$cached[] = (object) array(
'content_type' => $content_type,
'content_id' => $content_id,
'value_type' => $type,
'value' => $value,
'tag' => $tag,
'function' => $function,
);
}
}
}
votingapi_add_results($cached);
// Give other modules a chance to act on the results of the vote totaling.
module_invoke_all('votingapi_results', $cached, $votes, $content_type, $content_id);
return $cached;
}
}
/**
* Simple wrapper function for votingapi_select_votes. Returns the
* value of the first vote matching the criteria passed in.
*/
function votingapi_select_single_vote_value($criteria = array()) {
if ($votes = votingapi_select_votes($criteria) && !empty($votes)) {
return $votes[0]->value;
}
}
/**
* Simple wrapper function for votingapi_select_results. Returns the
* value of the first result matching the criteria passed in.
*/
function votingapi_select_single_result_value($criteria = array()) {
return db_result(_votingapi_query('cache', $criteria, 1));
}
/**
* Populate the value of any unset vote properties.
*
* @param $vobj
* A single vote object.
* @return
* A vote object with all required properties filled in with
* their default values.
*/
function _votingapi_prep_vote($vobj) {
global $user;
if (empty($vobj->prepped)) {
$varray = (array) $vobj;
$vote = array();
$vote['content_type'] = 'node';
$vote['value_type'] = 'percent';
$vote['tag'] = 'vote';
$vote['uid'] = $user->uid;
$vote['timestamp'] = time();
$vote['vote_source'] = ip_address();
$vote['prepped'] = TRUE;
$varray += $vote;
return (object) $varray;
}
return $vobj;
}
function _votingapi_query($table = 'vote', $criteria = array(), $limit = 0) {
$criteria += array(
'vote_id' => NULL,
'vote_cache_id' => NULL,
'content_id' => NULL,
'content_type' => NULL,
'value_type' => NULL,
'value' => NULL,
'tag' => NULL,
'uid' => NULL,
'timestamp' => NULL,
'vote_source' => NULL,
'function' => NULL,
);
$query = "SELECT * FROM {votingapi_" . $table . "} v WHERE 1 = 1";
$args = array();
if (!empty($criteria['vote_id'])) {
_votingapi_query_builder('vote_id', $criteria['vote_id'], $query, $args);
}
elseif (!empty($criteria['vote_cache_id'])) {
_votingapi_query_builder('vote_cache_id', $criteria['vote_cache_id'], $query, $args);
}
else {
_votingapi_query_builder('content_type', $criteria['content_type'], $query, $args, TRUE);
_votingapi_query_builder('content_id', $criteria['content_id'], $query, $args);
_votingapi_query_builder('value_type', $criteria['value_type'], $query, $args, TRUE);
_votingapi_query_builder('tag', $criteria['tag'], $query, $args, TRUE);
_votingapi_query_builder('function', $criteria['function'], $query, $args);
_votingapi_query_builder('uid', $criteria['uid'], $query, $args);
_votingapi_query_builder('vote_source', $criteria['vote_source'], $query, $args, TRUE);
_votingapi_query_builder('timestamp', $criteria['timestamp'], $query, $args);
}
return $limit ? db_query_range($query, $args, 0, $limit) : db_query($query, $args);
}
function _votingapi_query_builder($name, $value, &$query, &$args, $col_is_string = FALSE) {
if (!isset($value)) {
// Do nothing
}
elseif ($name === 'timestamp') {
$query .= " AND v.timestamp >= %d";
$args[] = $value;
}
else {
if (is_array($value)) {
if ($col_is_string) {
$query .= " AND v." . $name . " IN ('" . array_fill(1, count($value), "'%s'") . "')";
$args += $value;
}
else {
$query .= " AND v." . $name . " IN (" . array_fill(1, count($value), "%d") . ")";
$args += $value;
}
}
else {
if ($col_is_string) {
$query .= " AND v." . $name . " = '%s'";
$args[] = $value;
}
else {
$query .= " AND v." . $name . " = %d";
$args[] = $value;
}
}
}
}
Functions
Name | Description |
---|---|
votingapi_add_results | Save a single vote result to the database. |
votingapi_add_votes | Save a single vote to the database. |
votingapi_cron | Implementation of hook_cron. Allows db-intensive recalculations to put off until cron-time. |
votingapi_current_user_identifier | Generate a proper identifier for the current user: if they have an account, return their UID. Otherwise, return their IP address. |
votingapi_delete_results | Delete cached vote results from the database. |
votingapi_delete_votes | Delete votes from the database. |
votingapi_init | Implementation of hook_init. Loads the Views integration code. |
votingapi_menu | Implementation of hook_menu. Adds the url path for the votingapi settings page. |
votingapi_perm | Implementation of hook_perm |
votingapi_recalculate_results | Loads all votes for a given piece of content, then calculates and caches the aggregate vote results. This is only intended for modules that have assumed responsibility for the full voting cycle: the votingapi_set_vote() function recalculates… |
votingapi_select_results | Select cached vote results from the database. |
votingapi_select_single_result_value | Simple wrapper function for votingapi_select_results. Returns the value of the first result matching the criteria passed in. |
votingapi_select_single_vote_value | Simple wrapper function for votingapi_select_votes. Returns the value of the first vote matching the criteria passed in. |
votingapi_select_votes | Select individual votes from the database. |
votingapi_settings_form | |
votingapi_set_vote | Cast a vote on a particular piece of content. If a vote already exists, its value is changed. In most cases, this is the function that should be used by external modules. |
_votingapi_prep_vote | Populate the value of any unset vote properties. |
_votingapi_query | |
_votingapi_query_builder |