Achievement type 'hidden' has been renamed to 'secret'.
[project/achievements.git] / achievements.pages.inc
CommitLineData
76ff53c2 1<?php
76ff53c2
MI
2
3/**
4 * @file
5 * Page callbacks for the Achievements module.
6 */
7
8/**
da880d4e 9 * Menu callback; show the site-wide leaderboard.
76ff53c2 10 *
da880d4e 11 * @see achievement_leaderboard_block()
76ff53c2 12 */
da880d4e
MI
13function achievements_leaderboard_totals() {
14 // force no sorting on # so it doesn't get the 'active' CSS, which screws up relative non-header display.
1841b3a5 15 $header = array(array('data' => t('#'), 'sort' => NULL), t('Who'), t('Points'), t('Unlocks'), t('Latest achievement'));
da880d4e 16 $achievers = $rows = array('top' => array(), 'relative' => array());
f1c0c41b 17
da880d4e
MI
18 // load up all our achievers for the page.
19 $query = db_select('achievement_totals', 'at')->extend('PagerDefault');
9fa1ebdd 20 $query->addTag('achievement_totals')->join('users', 'u', 'u.uid = at.uid');
234b55d8
MI
21 $query->fields('at', array('uid', 'points', 'unlocks', 'timestamp', 'achievement_id'))->fields('u', array('name'));
22 $query->orderBy('at.points', 'DESC')->orderBy('at.timestamp'); // @bug DESC/ASC doesn't index. ever. bastids.
23 $query->limit(variable_get('achievements_leaderboard_count_per_page', 10));
da880d4e
MI
24 $achievers['top'] = $query->execute()->fetchAllAssoc('uid');
25
26 if (user_is_logged_in() && variable_get('achievements_leaderboard_relative', 'nearby_ranks') != 'disabled') {
27 $achievers['relative'] = (variable_get('achievements_leaderboard_relative', 'nearby_ranks') == 'nearby_ranks')
28 ? achievements_totals_user(variable_get('achievements_leaderboard_relative_nearby_ranks', 2))
29 : achievements_totals_user(); // you and me, togetheRrrr FoReEEvvvVeeEeeRRrRR.
30 }
9fa1ebdd
MI
31
32 // display our achievers in "how awesome they are" order.
33 // Global rank isn't stored in the database (to prevent us from having
34 // complicated rejiggering every time an achievement is unlocked), but
35 // the order the query returns is the order of the leaderboard. thus,
36 // we start at 1 and increase the rank for every row we're showing.
37 // also, be careful to start with the right number on pager queries.
78b85bb7 38 $rank = 0 + ($GLOBALS['pager_page_array'][0] * variable_get('achievements_leaderboard_count_per_page', 10));
da880d4e
MI
39 foreach (array('top', 'relative') as $type) {
40 foreach ($achievers[$type] as $achiever) {
c00d62a9
MI
41 // determine the current rank and set some speshul classes.
42 $rank = $achiever->rank = isset($achiever->rank) ? $achiever->rank : $rank + 1;
43 $classes = ($achiever->uid == $GLOBALS['user']->uid) ? array('achievement-leaderboard-current-user') : array();
44 if ($rank <= 3) { // give special classes if its the current user or if it's a cherished leader position.
45 $classes[] = 'achievement-leaderboard-top-rank';
46 $classes[] = 'achievement-leaderboard-top-rank-' . $rank;
47 }
48
49 $rows[$type][$achiever->uid] = array(
50 'class' => $classes,
f29347c6 51 'data' => array(
c00d62a9
MI
52 array(
53 'data' => $rank,
f29347c6
MI
54 'class' => array('achievement-leaderboard-rank')
55 ),
56 array(
57 'data' => theme('username', array('account' => $achiever)),
58 'class' => array('achievement-leaderboard-username')
59 ),
60 array(
61 'data' => l($achiever->points, 'user/' . $achiever->uid . '/achievements'),
62 'class' => array('achievement-leaderboard-points')
63 ),
64 array(
65 'data' => l($achiever->unlocks, 'user/' . $achiever->uid . '/achievements'),
66 'class' => array('achievement-leaderboard-unlocks')
67 ),
da880d4e 68 ),
b830eedf 69 );
1b065a4e
MI
70
71 // since we're showing the "latest achievement" for a user, we have to
6f307545 72 // determine what image to use and respect 'secret' status. we already
1b065a4e 73 // do this in achievements_template_shared_variables(), so reuse it. we
78b85bb7 74 // fake an #unlock so the 'unlocked' image is returned; the POV for this
1b065a4e
MI
75 // achievement display is the ranked user/row, NOT the current user.
76 $achievers[$type][$achiever->uid]->latest = array(
f4be3342 77 '#theme' => 'achievement_latest_unlock',
1b065a4e
MI
78 '#achievement' => achievements_load($achiever->achievement_id),
79 '#unlock' => array('timestamp' => $achiever->timestamp),
80 );
81 $rows[$type][$achiever->uid]['data'][] = array(
82 'data' => render($achievers[$type][$achiever->uid]->latest),
83 'class' => array('achievement-leaderboard-latest')
84 );
76ff53c2 85 }
76ff53c2 86 }
76ff53c2 87
da880d4e 88 foreach (array('top', 'relative') as $type) {
4491b057
MI
89 $leaderboard = array(
90 'achievers' => $achievers,
91 'block' => FALSE,
92 'type' => $type,
93 'render' => array(
94 '#attributes' => array('class' => array('achievement-leaderboard', 'achievement-leaderboard-' . $type)),
95 '#header' => $type == 'top' ? $header : NULL, // relative piggybacks off of top.
96 '#empty' => $type == 'top' ? t('No one has been ranked yet.') : NULL,
97 '#rows' => $rows[$type],
98 '#theme' => 'table',
99 ),
da880d4e 100 );
a16b1ff1
MI
101 if ($type == 'top' || count($rows['relative'])) {
102 drupal_alter('achievements_leaderboard', $leaderboard);
103 $build['achievements']['leaderboard'][$type] = $leaderboard['render'];
104 }
da880d4e 105 }
4491b057 106
da880d4e
MI
107 $build['achievements']['leaderboard_pager'] = array(
108 '#theme' => 'pager',
f1c0c41b 109 );
76ff53c2 110
da880d4e
MI
111 return $build;
112}
113
114/**
115 * Block callback; show the global leaderboard.
116 *
117 * @see achievement_leaderboard_totals()
118 */
119function achievements_leaderboard_block() {
120 $header = array(array('data' => t('#'), 'sort' => NULL), t('Who'), t('Pts'));
121 $achievers = $rows = array('top' => array(), 'relative' => array());
122
123 $query = db_select('achievement_totals', 'at');
124 $query->addTag('achievement_totals')->join('users', 'u', 'u.uid = at.uid');
234b55d8 125 $query->fields('at', array('uid', 'points', 'unlocks'))->fields('u', array('name'));
da880d4e
MI
126 $query->orderBy('at.points', 'DESC')->orderBy('at.timestamp'); // @bug DESC/ASC doesn't index.
127 $query->range(0, variable_get('achievements_leaderboard_block_count_top', 5));
128 $achievers['top'] = $query->execute()->fetchAllAssoc('uid');
129
130 if (user_is_logged_in() && !isset($achievers['top'][$GLOBALS['user']->uid]) && variable_get('achievements_leaderboard_block_relative', 'nearby_ranks') != 'disabled') {
131 $achievers['relative'] = (variable_get('achievements_leaderboard_block_relative', 'nearby_ranks') == 'nearby_ranks')
132 ? achievements_totals_user(variable_get('achievements_leaderboard_block_relative_nearby_ranks', 1))
c00d62a9 133 : achievements_totals_user(); // you and me, togetheRrrr almost nEveVvaeeeeEEERrrrrR111!
da880d4e
MI
134 }
135
c00d62a9 136 $rank = 0; // innocent widdle variable!
da880d4e
MI
137 foreach (array('top', 'relative') as $type) {
138 foreach ($achievers[$type] as $achiever) {
c00d62a9
MI
139 $rank = $achiever->rank = isset($achiever->rank) ? $achiever->rank : $rank + 1;
140 $classes = ($achiever->uid == $GLOBALS['user']->uid) ? array('achievement-leaderboard-current-user') : array();
141 if ($rank <= 3) { // give special classes if its the current user or if it's a cherished leader position.
142 $classes[] = 'achievement-leaderboard-top-rank';
143 $classes[] = 'achievement-leaderboard-top-rank-' . $rank;
144 }
145
146 $rows[$type][$achiever->uid] = array(
147 'class' => $classes,
f29347c6 148 'data' => array(
c00d62a9
MI
149 array(
150 'data' => $rank,
f29347c6
MI
151 'class' => array('achievement-leaderboard-rank')
152 ),
153 array(
154 'data' => theme('username', array('account' => $achiever)),
155 'class' => array('achievement-leaderboard-username')
156 ),
157 array(
158 'data' => l($achiever->points, 'user/' . $achiever->uid . '/achievements'),
159 'class' => array('achievement-leaderboard-points')
160 ),
da880d4e
MI
161 ),
162 );
163 }
76ff53c2 164 }
da880d4e
MI
165
166 foreach (array('top', 'relative') as $type) {
4491b057
MI
167 $leaderboard = array(
168 'achievers' => $achievers,
169 'block' => TRUE,
170 'type' => $type,
171 'render' => array(
172 '#attributes' => array('class' => array('achievement-leaderboard', 'achievement-leaderboard-' . $type)),
173 '#header' => $type == 'top' ? $header : NULL, // relative piggybacks off of top.
174 '#empty' => $type == 'top' ? t('No one has been ranked yet.') : NULL,
175 '#rows' => $rows[$type],
176 '#theme' => 'table',
177 ),
9fa1ebdd 178 );
a16b1ff1
MI
179 if ($type == 'top' || count($rows['relative'])) {
180 drupal_alter('achievements_leaderboard', $leaderboard);
181 $build['achievements']['leaderboard'][$type] = $leaderboard['render'];
182 }
9fa1ebdd 183 }
4491b057 184
da880d4e 185 $build['achievements']['see-more'] = array(
4491b057
MI
186 '#prefix' => '<div class="achievement-see-more">',
187 '#markup' => l(t('View full leaderboard '), 'achievements/leaderboard'),
188 '#suffix' => '</div>',
da880d4e 189 );
76ff53c2 190
b830eedf 191 return $build;
76ff53c2
MI
192}
193
194/**
195 * Menu callback; display a single achievement page with leaderboards.
196 */
197function achievements_leaderboard_for($achievement) {
73624c6e 198 drupal_set_title(t('Achievement: @title', array('@title' => $achievement['title'])), PASS_THROUGH);
76ff53c2 199
b830eedf 200 $build['achievements']['achievement'] = array(
f1c0c41b
MI
201 '#achievement' => $achievement,
202 '#unlock' => achievements_unlocked_already($achievement['id']),
653a3a63 203 '#theme' => 'achievement__leaderboard_for',
f1c0c41b 204 );
76ff53c2 205
f1c0c41b
MI
206 // get stats for first and most recent unlocks.
207 $query = db_select('achievement_unlocks', 'au');
8e8c57f9
MI
208 $query->join('achievement_totals', 'at', 'at.uid = au.uid');
209 $query->join('users', 'u', 'u.uid = au.uid'); // same basic start for both queries.
1841b3a5 210 $query->condition('au.achievement_id', $achievement['id']); // ... with a slight tweak.
8e8c57f9
MI
211 $query->fields('au', array('uid', 'rank', 'timestamp'))->fields('at', array('points', 'unlocks'))->fields('u', array('name'));
212 $query2 = clone $query; // allows us to save a few lines of duplicate query building. never used clone before. awesome.
4491b057
MI
213 $achievers['first'] = $query->orderBy('rank')->range(0, 10)->execute()->fetchAllAssoc('uid'); // FI... sigh.
214 $achievers['recent'] = $query2->orderBy('timestamp', 'DESC')->range(0, 10)->execute()->fetchAllAssoc('uid');
f1c0c41b 215
f1c0c41b
MI
216 foreach (array('first', 'recent') as $type) {
217 $rows = array(); // clear previous run.
4491b057
MI
218 foreach ($achievers[$type] as $achiever) {
219 $rows[$achiever->uid] = array(
c95c2ece
MI
220 'class' => array(),
221 'data' => array(
222 array(
223 'data' => $achiever->rank,
224 'class' => array('achievement-leaderboard-rank')
225 ),
226 array(
227 'data' => theme('username', array('account' => $achiever)),
228 'class' => array('achievement-leaderboard-username')
229 ),
230 array(
231 'data' => l($achiever->points, 'user/' . $achiever->uid . '/achievements'),
232 'class' => array('achievement-leaderboard-points')
233 ),
234 array(
235 'data' => format_date($achiever->timestamp, 'short'),
236 'class' => array('achievement-leaderboard-when')
237 ),
b830eedf 238 ),
76ff53c2
MI
239 );
240 }
241
4491b057
MI
242 $leaderboard = array(
243 'achievers' => $achievers,
244 'block' => FALSE,
245 'type' => $type,
246 'render' => array(
247 '#attributes' => array('class' => array('achievement-leaderboard-' . $type)),
248 '#caption' => t('@type achievement unlocks', array('@type' => drupal_ucfirst($type))),
249 '#header' => array(t('#'), t('Who'), t('Points'), t('When')),
250 '#empty' => t('No one has unlocked this yet. Keep trying!'),
251 '#rows' => $rows,
252 '#theme' => 'table',
253 ),
f1c0c41b 254 );
4491b057
MI
255 drupal_alter('achievements_leaderboard', $leaderboard);
256 $build['achievements']['leaderboard'][$type] = $leaderboard['render'];
f1c0c41b 257 }
76ff53c2 258
b830eedf 259 return $build;
76ff53c2
MI
260}
261
262/**
263 * Menu callback; display all achievements for the passed user.
264 *
f1c0c41b 265 * @param $account
76ff53c2
MI
266 * The user object this request applies against.
267 */
f1c0c41b 268function achievements_user_page($account) {
f1c0c41b 269 drupal_set_title(t('Achievements for @name', array('@name' => $account->name)));
735fa69a 270 $unlocks = achievements_unlocked_already(NULL, $account->uid);
da880d4e 271 $achiever = array_pop(achievements_totals_user(0, $account->uid));
f1c0c41b 272
b830eedf 273 $build['achievements']['stats'] = array(
6749b115
MI
274 '#theme' => 'achievement_user_stats',
275 '#stats' => array(
276 'name' => $account->name,
277 'rank' => isset($achiever->rank) ? $achiever->rank : 0,
278 'points' => isset($achiever->points) ? $achiever->points : 0,
279 'unlocks_count' => count($unlocks),
280 'total_count' => count(achievements_load()),
281 ),
76ff53c2 282 );
bf2a4a64 283
7aafb408
MI
284 $achievements_grouped = achievements_load(NULL, TRUE);
285
1a4da324
MI
286 // theme wrappers let us wrap our children with a rethemable/overridable DIV.
287 $build['achievements']['tabs']['#theme_wrappers'] = array('achievement_groups_wrapper');
4fd35678
MI
288
289 // use jQuery tabs if more than one group.
290 if (count($achievements_grouped) > 1) {
291 foreach ($achievements_grouped as $group_id => $group) {
292 $links['achievement-group-' . $group_id] = array(
293 'title' => $group['title'],
294 'fragment' => 'achievement-group-' . $group_id,
295 'href' => '', // just the fragment, please.
296 'external' => TRUE, // no base_path dammit. GZUS.
297 );
298 }
299 $build['achievements']['tabs']['navigation'] = array('#theme' => 'links', '#links' => $links);
300 $build['achievements']['tabs']['navigation']['#attached']['library'][] = array('system', 'ui.tabs');
301 $build['achievements']['tabs']['navigation']['#attached']['js']= array( // I AM NOT A JS EXPERT. OOooOH NO.
302 'jQuery(document).ready(function(){jQuery("#achievement-groups").tabs();});' => array('type' => 'inline')
303 );
304 }
305
ce6c3c4b 306 foreach ($achievements_grouped as $group_id => $group) {
73908d19 307 $locked_weight = 0; // we can't use definition order.
c00d62a9 308 $build['achievements']['tabs']['groups'][$group_id]['#group_id'] = $group_id; // add a DIV in the wrapper.
1a4da324 309 $build['achievements']['tabs']['groups'][$group_id]['#theme_wrappers'] = array('achievement_group_wrapper');
ce6c3c4b 310
512d7719 311 foreach ($group['achievements'] as $achievement_id => $achievement) {
4fd35678 312 $build['achievements']['tabs']['groups'][$group_id][$achievement_id]['#achievement'] = $achievement;
653a3a63 313 $build['achievements']['tabs']['groups'][$group_id][$achievement_id]['#theme'] = 'achievement__user_page';
bf2a4a64 314
512d7719 315 if (isset($unlocks[$achievement_id])) {
a5776b3c 316 $build['achievements']['tabs']['groups'][$group_id][$achievement_id]['#unlock'] = $unlocks[$achievement_id];
7aafb408
MI
317
318 if (variable_get('achievements_unlocked_move_to_top', TRUE)) {
da880d4e 319 $build['achievements']['tabs']['groups'][$group_id][$achievement_id]['#weight'] = -$unlocks[$achievement_id]['timestamp'];
7aafb408
MI
320 // by setting the negative weight to the timestamp, the latest unlocks are always shown at the top.
321 }
73908d19 322 }
7aafb408
MI
323 elseif (!isset($unlocks[$achievement_id]) && variable_get('achievements_unlocked_move_to_top', TRUE)) {
324 // if we're forcing unlocks to the top, locked achievements have to be forced to the bottom too.
4fd35678 325 $build['achievements']['tabs']['groups'][$group_id][$achievement_id]['#weight'] = $locked_weight++;
512d7719
MI
326 }
327 }
76ff53c2
MI
328 }
329
b830eedf 330 return $build;
76ff53c2
MI
331}
332
d9c89d89 333/**
28acbf3e
MI
334 * Menu callback; Retrieve autocomplete suggestions for achievement names.
335 */
336function achievements_autocomplete($string = '') {
337 $achievements = achievements_load(); // MmmMMmMm, gooOoaaAaalLLlsss.
338 array_walk($achievements, 'achievements_autocomplete_search', $string);
339 drupal_json_output(array_slice(array_filter($achievements), 0, 10));
340}
341
342/**
343 * array_walk helper function for achievements_autocomplete().
344 */
345function achievements_autocomplete_search(&$value, $key, $string) {
346 $value = (stripos($value['title'], $string) === FALSE) ? FALSE : $value['title'];
347}
6749b115
MI
348
349/**
350 * Default theme implementation for a user's achievement stats.
351 *
1a4da324
MI
352 * @param variables
353 * An associative array containing:
354 * - stats: An array which contains the following keys:
355 * name, rank, points, unlocks_count, and total_count.
6749b115
MI
356 */
357function theme_achievement_user_stats($variables) {
358 $output = '<div class="achievement-user-stats">'; // VERY CONFUSING CODE COMMENT ABOUT PSYNAPTIC. YOU THOUGHT I WAS KIDDING?
359 $output .= t('@name is ranked #@rank with @points points. @unlocks_count of @total_count achievements have been unlocked.', array(
360 '@name' => $variables['stats']['name'],
361 '@rank' => $variables['stats']['rank'],
362 '@points' => $variables['stats']['points'],
363 '@unlocks_count' => $variables['stats']['unlocks_count'],
364 '@total_count' => $variables['stats']['total_count'],
365 ));
366 $output .= '</div>';
367 return $output;
368}
369
1a4da324
MI
370/**
371 * Default theme for the wrapper around a user's achievements page.
372 *
373 * @param $variables
374 * An associative array containing:
375 * - element: A render containing the user's achievements page.
376 */
377function theme_achievement_groups_wrapper($variables) {
378 return '<div id="achievement-groups">' . $variables['element']['#children'] . '</div>';
379}
380
381/**
382 * Default theme for the wrapper around an achievement group.
383 *
384 * @param $variables
385 * An associative array containing:
386 * - element: A render containing the achievement group.
387 */
388function theme_achievement_group_wrapper($variables) {
389 $group_id = 'achievement-group-' . $variables['element']['#group_id'];
390 return '<div id="' . $group_id . '" class="achievement-group">' . $variables['element']['#children'] . '</div>';
391}