| Commit | Line | Data |
|---|---|---|
| 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 |
13 | function 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 | */ | |
| 119 | function 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 | */ | |
| 197 | function 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 | 268 | function 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 | */ | |
| 336 | function 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 | */ | |
| 345 | function 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 | */ |
| 357 | function 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 | */ | |
| 377 | function 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 | */ | |
| 388 | function 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 | } |