Newer
Older
* Unlock achievements and earn points based on milestones.
*/
/**
* Implements hook_permission().
function achievements_permission() {
return array(
'access achievements' => array(
'title' => t('Access achievements'),
),
'earn achievements' => array(
'title' => t('Earn achievements'),
),
'administer achievements' => array(
'title' => t('Administer achievements'),
),
);
}
*/
function achievements_menu() {
$items['achievements/leaderboard'] = array(
'access arguments' => array('access achievements'),
'description' => 'View the site-wide achievements leaderboard.',
'file' => 'achievements.pages.inc',
'page callback' => 'achievements_leaderboard_totals',
'title' => 'Leaderboard',
);
$items['achievements/leaderboard/%achievements'] = array(
'access arguments' => array('access achievements'),
'description' => "View a specific achievement's leaderboard.",
'file' => 'achievements.pages.inc',
'page callback' => 'achievements_leaderboard_for',
'page arguments' => array(2),
'title' => 'Per-achievement leaderboard',
'type' => MENU_CALLBACK,
);
$items['user/%user/achievements'] = array(
'access arguments' => array('access achievements'),
'description' => "View a specific user's leaderboard.",
'file' => 'achievements.pages.inc',
'page callback' => 'achievements_user_page',
'page arguments' => array(1),
'title' => 'Achievements',
'type' => MENU_LOCAL_TASK,
);
$items['admin/config/people/achievements'] = array(
'description' => 'Configure the achievements system.',
'file' => 'achievements.pages.inc',
'page callback' => 'drupal_get_form',
'page arguments' => array('achievements_settings'),
'title' => 'Achievements',
);
$items['achievements/autocomplete'] = array(
'access arguments' => array('access achievements'),
'file' => 'achievements.pages.inc',
'page callback' => 'achievements_autocomplete',
'title' => 'Achievement title autocomplete',
'type' => MENU_CALLBACK,
);
function achievements_theme() {
return array(
'achievement' => array(
'variables' => array('achievement' => NULL, 'unlock' => NULL),
'template' => 'achievement',
),
);
'achievements-leaderboard' => array(
'info' => t('Achievements leaderboard'),
'cache' => DRUPAL_CACHE_GLOBAL,
function achievements_block_view($delta = '') {
if ($delta == 'achievements-leaderboard') {
include_once(drupal_get_path('module', 'achievements') . '/achievements.pages.inc');
return array( // stupid file that I have to include. WHERE"S MY FUNCTION REGSITERYR.
'subject' => t('Leaderboard'),
'content' => achievements_leaderboard_totals(TRUE),
);
}
}
/**
* Load information about our achievements.
*
* @param $achievement_id
* Optional; the achievement this request applies against.
* @return $achievements
* An array of all achievements, or just the one passed.
*/
function achievements_load($achievement_id = NULL, $reset = FALSE) {
$achievements = &drupal_static(__FUNCTION__);
if (!isset($achievements) || $reset) {
if (!$reset && $cache = cache_get('achievement_info')) {
$achievements = $cache->data;
}
else {
$achievements = module_invoke_all('achievements_info');
cache_set('achievement_info', $achievements, 'cache', CACHE_TEMPORARY);
// no magically-useful way to say "on file change". le sigh.
return $achievement_id
? (isset($achievements[$achievement_id]) ? $achievements[$achievement_id] : FALSE)
: $achievements; // return FALSE to stop bum URLs (via the menu %loader callback).
* Returns all, or per user, achievement totals.
* @param $count
* Defaults to 50; the number of top-ranking users to return.
* @param $current_user
* Defaults to TRUE; whether to include the current user's stats in the list,
* even if they're not in the top $count. This is just a friendly "how you
* compare" feature ("I'm rank 52; nearly in the top 50. woot!").
* @return $totals
* An array of totals information for the top $count users.
function achievements_totals($count = 50, $current_user = TRUE) {
$query = db_select('achievement_totals', 'at');
$query->join('users', 'u', 'u.uid = at.uid');
$query->fields('at', array('uid', 'points', 'unlocks', 'timestamp'))->fields('u', array('name'));
$query->orderBy('at.points', 'DESC')->orderBy('at.timestamp'); // @todo DESC/ASC doesn't index.
$achievers = $query->range(0, $count)->execute()->fetchAllAssoc('uid');
$rank = 1; // add the ranking.
foreach ($achievers as $achiever) {
$achiever->rank = $rank++;
}
// if the current user isn't in our top $count, find 'em.
if ($current_user && user_is_logged_in() && !isset($achievers[$GLOBALS['user']->uid])) {
$achievers[$GLOBALS['user']->uid] = achievements_totals_user('all');
return $achievers;
* Returns a specific user's achievement totals.
* @param $type
* Defaults to 'points'; one of 'points', 'unlocks', 'rank', or 'all'.
* @param $uid
* Defaults to current user; the user to return achievement info for.
*
* @return $integer or $object
* The value of the passed $type. If $type is 'all', the full object.
function achievements_totals_user($type = 'points', $uid = NULL) {
list($uid, $access) = achievements_user_is_achiever($uid);
// we don't check for access as this is info grubbing only.
$achievers = &drupal_static(__FUNCTION__);
$query = db_select('achievement_totals', 'at');
$query->join('users', 'u', 'u.uid = at.uid');
$query->fields('at', array('uid', 'points', 'unlocks', 'timestamp'))->fields('u', array('name'));
$achievers[$uid] = $query->condition('at.uid', $uid)->execute()->fetchObject();
// to find the user's rank, count all the users with greater points, add
// all the users with equal points but earlier timestamps, and then add 1.
$better_points = db_select('achievement_totals')->condition('points', $achievers[$uid]->points, '>')->countQuery()->execute()->fetchField();
$earlier_times = db_select('achievement_totals')->condition('points', $achievers[$uid]->points)->condition('timestamp', $achievers[$uid]->timestamp, '<')->countQuery()->execute()->fetchField();
$achievers[$uid]->rank = $better_points + $earlier_times + 1;
return $type == 'all' ? $achievers[$uid] : ($achievers[$uid]->$type ? $achievers[$uid]->$type : 0);
}
/**
* Logs a user as having unlocked an achievement.
*
* @param $achievement_id
* The achievement this request applies against.
* @param $uid
* Defaults to current user; the user to unlock an achievement for.
*/
function achievements_unlocked($achievement_id, $uid = NULL) {
list($uid, $access) = achievements_user_is_achiever($uid);
if (!$access) { return; } // i know you want it, but...
// grab information about the achievement.
$achievement = achievements_load($achievement_id);
if (!isset($achievement)) { // hrm... try a cache refresh?
$achievement = achievements_load($achievement_id, TRUE);
}
if (isset($achievement) && !achievements_unlocked_already($achievement_id, $uid)) {
$last_rank = db_select('achievement_unlocks', 'au')->fields('au', array('rank')) // not. exciting. at. all.
->condition('achievement_id', $achievement_id)->orderBy('rank', 'DESC')->range(0, 1)->execute()->fetchField();
db_insert('achievement_unlocks')
->fields(array(
'achievement_id' => $achievement_id,
'uid' => $uid,
'rank' => $last_rank ? $last_rank + 1 : 1,
'timestamp' => REQUEST_TIME,
))
->execute();
db_merge('achievement_totals')
->key(array('uid' => $uid))
->fields(array(
'points' => $achievement['points'],
'unlocks' => 1, // OMG CONGRATS
'timestamp' => REQUEST_TIME,
))
->expression('points', 'points + :points', array(':points' => $achievement['points']))
->expression('unlocks', 'unlocks + :increment', array(':increment' => 1))
->execute();
if ($uid == $GLOBALS['user']->uid) { // only show the unlock if $uid is our viewer.
drupal_set_message(t('<strong>Achievement unlocked:</strong> @achievement (+@number). !view.',
array('@achievement' => $achievement['title'], '@number' => $achievement['points'],
'!view' => l(t('View your achievements'), 'user/' . $uid . '/achievements'))));
}
}
}
/**
* Determine if a user has already unlocked an achievement.
*
* @param $achievement_id
* The achievement this request applies against.
* @param $uid
* Defaults to current user; the user this request applies against.
* @return NULL or $unlocked
* $unlocked is an array containing rank and timestamp.
*/
function achievements_unlocked_already($achievement_id, $uid = NULL) {
list($uid, $access) = achievements_user_is_achiever($uid);
if (!$access) { return; } // i can't let you in, y'know?
$unlock = db_select('achievement_unlocks', 'au')->fields('au', array('rank', 'timestamp'))
->condition('achievement_id', $achievement_id)->condition('uid', $uid)->execute()->fetchAssoc();
}
/**
* Retrieve data needed by an achievement.
*
* @param $achievement_id
* An identifier for the achievement whose data is being collected.
* @param $uid
* Defaults to current user; the user this stored data applies to.
* The data stored for this achievement and user (unserialized).
*/
function achievements_storage_get($achievement_id = NULL, $uid = NULL) {
list($uid, $access) = achievements_user_is_achiever($uid);
if (!$access) { return; } // it's not that I don't want to...
return unserialize(db_select('achievement_storage')->fields('achievement_storage', array('data'))
->condition('achievement_id', $achievement_id)->condition('uid', $uid)->execute()->fetchField());
}
/**
* Save data needed by an achievement.
*
* @param $achievement_id
* An identifier for the achievement whose data is being collected.
* @param $uid
* Defaults to current user; the user this stored data applies to.
* @param $data
* The data being saved (of any type; serialization occurs).
*/
function achievements_storage_set($achievement_id = NULL, $data = NULL, $uid = NULL) {
list($uid, $access) = achievements_user_is_achiever($uid);
if (!$access) { return; } // I... I'M IN LOVE WITH MORBUS OK?!!?
db_merge('achievement_storage')
->key(array('uid' => $uid, 'achievement_id' => $achievement_id))
->fields(array('data' => serialize($data))) // it's hot in here.
->execute(); // i hate all DBTNG syntax. NEVAH STANDARNDIZEE?Ee1
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
/**
* Determine if a user is able to earn achievements.
*
* This is a general helper around the core achievements functions and allows
* us to default to the global user if a $uid is not passed, but also check
* permissions against a user who is not the global user. This allows us to
* a) define roles of users that CAN NOT earn achievements and b) manually
* unlock achievements for a non-current user.
*
* @param $uid
* Defaults to current user; the user to check for "earn achievements".
*
* @return $results
* An array with values of:
* - $uid is the determined user (default: the global user).
* - $access is a TRUE or FALSE as returned by user_access().
*/
function achievements_user_is_achiever($uid = NULL) {
if (!isset($uid) || $uid == $GLOBALS['user']->uid) {
return array($GLOBALS['user']->uid, user_access('earn achievements'));
}
else {
return array($uid, user_access('earn achievements', user_load($uid)));
}
}
function achievements_user_cancel($edit, $account, $method) {
achievements_user_delete($account); // no stats for non-players.
function achievements_user_delete($account) {
db_delete('achievement_totals')->condition('uid', $account->uid)->execute();
db_delete('achievement_unlocks')->condition('uid', $account->uid)->execute();
db_delete('achievement_storage')->condition('uid', $account->uid)->execute();