| Commit | Line | Data |
|---|---|---|
| 6cdda368 MI |
1 | <?php |
| 2 | ||
| 3 | /** | |
| 4 | * @file | |
| 5 | * Hooks provided by the Achievements module and how to implement them. | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Define an achievement. | |
| 10 | * | |
| 11 | * This hook describes your achievements to the base module so that it can | |
| 12 | * create the pages and caches necessary for site-wide display. The base | |
| 09abe3cf MI |
13 | * module doesn't know how to unlock any of your achievements... instead, you |
| 14 | * use Drupal's existing hooks, the achievement storage tables, and a few | |
| 15 | * helper functions to complete the workflow. See the remaining documentation | |
| 16 | * in this file for further code samples. | |
| 17 | * | |
| 18 | * There are many different kinds of achievements, and it's accurate enough to | |
| 19 | * say that if you can measure or respond to an action, it can be made into a | |
| 20 | * matching achievement. Be creative. Look at what others are doing. Have fun. | |
| 21 | * Your gamification efforts will fail or be un-fun if you don't have a gamer | |
| 22 | * helping you, if you make everything a mindless grind, or if you simply | |
| 23 | * copy achievements from another site or install. | |
| 6cdda368 MI |
24 | * |
| 25 | * @return | |
| 0d348f03 MI |
26 | * An array whose keys are internal achievement IDs (32 chars max) and whose |
| 27 | * values identify properties of the achievement. These properties are: | |
| 6cdda368 MI |
28 | * - title: (required) The title of the achievement. |
| 29 | * - description: (required) A description of the achievement. | |
| 30 | * - points: (required) How many points the user will earn when unlocked. | |
| 0d348f03 | 31 | * - images: (optional) An array of (optional) keys 'locked', 'unlocked', |
| 6f307545 | 32 | * and 'secret' whose values are image file paths. Achievements exist in |
| 0d348f03 | 33 | * three separate display states: unlocked (the user has it), locked (the |
| 6f307545 | 34 | * user doesn't have it), and secret (the user doesn't have it, and the |
| 0d348f03 MI |
35 | * achievement is a secret). Each state can have its own default image |
| 36 | * associated with it (which administrators can configure), or achievements | |
| 37 | * can specify their own images for one, some, or all states. | |
| 28acbf3e MI |
38 | * - storage: (optional) If you store statistics for your achievement, the |
| 39 | * core module assumes you've used the achievement ID for the storage | |
| 40 | * location. If you haven't, specify the storage location here. This lets | |
| 41 | * the core module know what to delete when an administrator manually | |
| 09abe3cf MI |
42 | * removes an achievement unlock from a user. If your achievement |
| 43 | * tracks statistics that are NOT set with achievements_storage_get() | |
| 44 | * or _set, you don't have to define the 'storage' key. | |
| 87ead47a | 45 | * - secret: (optional) The achievement displays on a user's Achievements |
| 23043310 | 46 | * tab but does not reveal its title, description, or points until the |
| 87ead47a MI |
47 | * user has unlocked it. Compatible with 'invisible'. |
| 48 | * - invisible: (optional) The achievement does NOT display on a user's | |
| 49 | * Achievements tab, but does show up on the leaderboards when necessary. | |
| 50 | * Compatible with 'secret' (i.e., if another user has unlocked an | |
| 51 | * invisible achievement, a user who has not unlocked it will see the | |
| 52 | * placeholder secret text instead of the actual achievement itself). | |
| 4fd35678 MI |
53 | * |
| 54 | * Achievements can also be categorized into groups. Groups are simply | |
| 55 | * arrays whose keys are internal group IDs and whose values identify | |
| 56 | * the 'title' of the group as well as the array of 'achievements' that | |
| 57 | * correspond to that group. If some achievements are within a group and | |
| 58 | * some achievements are without a group, the groupless achievements will | |
| 59 | * automatically be placed into a "Miscellany" category. | |
| 6cdda368 MI |
60 | */ |
| 61 | function hook_achievements_info() { | |
| 512d7719 | 62 | $achievements = array( |
| 6cdda368 | 63 | 'comment-count-50' => array( |
| 6cdda368 MI |
64 | 'title' => t('Posted 50 comments!'), |
| 65 | 'description' => t("We no longer think you're a spam bot. Maybe."), | |
| 09abe3cf | 66 | 'storage' => 'comment-count', |
| 6cdda368 MI |
67 | 'points' => 50, |
| 68 | ), | |
| 69 | 'comment-count-100' => array( | |
| 6cdda368 MI |
70 | 'title' => t('Posted 100 comments!'), |
| 71 | 'description' => t('But what about the children?!'), | |
| 09abe3cf | 72 | 'storage' => 'comment-count', |
| 6cdda368 | 73 | 'points' => 100, |
| 0d348f03 | 74 | 'images' => array( |
| 961a0e99 | 75 | 'unlocked' => 'sites/default/files/example1.png', |
| 6f307545 | 76 | // 'secret' and 'locked' will use the defaults. |
| 0d348f03 | 77 | ), |
| 6cdda368 | 78 | ), |
| 4fd35678 | 79 | |
| 43551281 | 80 | // An example of achievement groups: 'article-creation' is the group ID, |
| 4fd35678 MI |
81 | // "Article creation" is the group title, and all relevant achievements are |
| 82 | // placed in an 'achievements' array. The ungrouped comment achievements | |
| 83 | // above will be automatically pushed into a "Miscellany" group. | |
| 84 | 'article-creation' => array( | |
| 85 | 'title' => t('Article creation'), | |
| 86 | 'achievements' => array( | |
| 87 | 'node-mondays' => array( | |
| 88 | 'title' => t('Published some content on a Monday'), | |
| 89 | 'description' => t("Go back to bed: it's still the weekend!"), | |
| 90 | 'points' => 5, | |
| 91 | 'images' => array( | |
| 961a0e99 MI |
92 | 'unlocked' => 'sites/default/files/example1.png', |
| 93 | 'locked' => 'sites/default/files/example2.png', | |
| 94 | 'secret' => 'sites/default/files/example3.png', | |
| 4fd35678 MI |
95 | // all default images have been replaced. |
| 96 | ), | |
| 97 | ), | |
| 0d348f03 | 98 | ), |
| 28acbf3e | 99 | ), |
| 6cdda368 | 100 | ); |
| 512d7719 MI |
101 | |
| 102 | return $achievements; | |
| 6cdda368 MI |
103 | } |
| 104 | ||
| 105 | /** | |
| 106 | * Implements hook_comment_insert(). | |
| 107 | */ | |
| efac995d | 108 | function example_comment_insert($comment) { |
| 09abe3cf MI |
109 | // Most achievements measure some kind of statistical data that must be |
| 110 | // aggregated over time. To ease the storage of this data, the achievement | |
| 111 | // module ships with achievement_storage_get() and _set(), which allow you | |
| 112 | // to store custom data on a per-user basis. In most cases, the storage | |
| 113 | // location is the same as your achievement ID but in situations where you | |
| 114 | // have progressive achievements (1, 2, 50 comments etc.), it's better to | |
| 115 | // share a single place like we do below. If you don't use the achievement | |
| 116 | // ID for the storage location, you must specify the new location in the | |
| 117 | // 'storage' key of hook_achievements_info(). | |
| 118 | // | |
| 119 | // Here we're grabbing the number of comments that the current commenter has | |
| 120 | // left in the past (which might be 0), adding 1 (for the current insert), | |
| 121 | // and then saving the count back to the database. The saved data is | |
| 122 | // serialized so can be as simple or as complex as you need it to be. | |
| 123 | $current_count = achievements_storage_get('comment-count', $comment->uid) + 1; | |
| 124 | achievements_storage_set('comment-count', $current_count, $comment->uid); | |
| 6cdda368 | 125 | |
| 09abe3cf MI |
126 | // Note that we're not checking if the user has previously earned any of the |
| 127 | // commenting achievements yet. There are two reasons: first, we might want | |
| 128 | // to add another commenting achievement for, say, 250 comments, and if we | |
| 129 | // had stopped the storage counter above at 100, someone who currently has | |
| 130 | // 300 comments wouldn't unlock the achievement until they added another 150 | |
| 131 | // nuggets of wisdom to the site. Generally speaking, if you need to store | |
| 132 | // incremental data for an achievement, you should continue to store it even | |
| 133 | // after the achievement has been unlocked - you never know if you'll want | |
| 134 | // to add a future milestone that will unlock on higher increments. | |
| 135 | // | |
| 136 | // Secondly, the achievements_unlocked() function below automatically checks | |
| 137 | // if the user has unlocked the achievement already, and will not reward it | |
| 28acbf3e MI |
138 | // again if they have. This saves you a small bit of repetitive coding but |
| 139 | // you're welcome to use achievements_unlocked_already() as needed. | |
| 09abe3cf MI |
140 | // |
| 141 | // Knowing that we currently have 50 and 100 comment achievements, we simply | |
| 142 | // loop through each milestone and check if the current count value matches. | |
| 6cdda368 MI |
143 | foreach (array(50, 100) as $count) { |
| 144 | if ($current_count == $count) { | |
| 28acbf3e | 145 | achievements_unlocked('comment-count-' . $count, $comment->uid); |
| 6cdda368 MI |
146 | } |
| 147 | } | |
| 148 | } | |
| efac995d MI |
149 | |
| 150 | /** | |
| 151 | * Implements hook_node_insert(). | |
| 152 | */ | |
| 153 | function example_node_insert($node) { | |
| 09abe3cf | 154 | // Sometimes, we don't need any storage at all. |
| efac995d | 155 | if (format_date(REQUEST_TIME, 'custom', 'D') == 'Mon') { |
| 09abe3cf | 156 | achievements_unlocked('node-mondays', $node->uid); |
| efac995d | 157 | } |
| b830eedf | 158 | } |
| f2e989ff MI |
159 | |
| 160 | /** | |
| f2e989ff MI |
161 | * Implements hook_achievements_info_alter(). |
| 162 | * | |
| 163 | * Modify achievements that have been defined in hook_achievements_info(). | |
| 164 | * Note that achievement info is cached so if you add or modify this hook, | |
| 165 | * also clear said achievement cache in admin/config/people/achievements. | |
| 166 | * | |
| 167 | * @param &$achievements | |
| 168 | * An array of defined achievements returned by hook_achievements_info(). | |
| 169 | */ | |
| 170 | function example_achievements_info_alter(&$achievements) { | |
| 171 | $achievements['comment-count-100']['points'] = 200; | |
| 172 | } | |
| 173 | ||
| 174 | /** | |
| 175 | * Implements hook_achievements_unlocked(). | |
| 176 | * | |
| 177 | * This hook is invoked after an achievement has been unlocked and all | |
| 178 | * the relevant information has been stored or updated in the database. | |
| 179 | * | |
| 180 | * @param $achievement | |
| 181 | * An array of information about the achievement. | |
| 182 | * @param $uid | |
| 183 | * The user ID who has unlocked the achievement. | |
| 184 | */ | |
| 185 | function example_achievements_unlocked($achievement, $uid) { | |
| 186 | // post to twitter or facebook, unlock an additional reward, etc., etc. | |
| 187 | } | |
| 188 | ||
| 189 | /** | |
| 190 | * Implements hook_achievements_locked(). | |
| 191 | * | |
| 192 | * This hook is invoked after an achievement has been removed from a user and | |
| 193 | * all relevant information has been stored or updated in the database. This | |
| 194 | * is currently only possible from the UI at admin/config/people/achievements. | |
| 195 | * | |
| 196 | * @param $achievement | |
| 197 | * An array of information about the achievement. | |
| 198 | * @param $uid | |
| 199 | * The user ID who is having the achievement taken away. | |
| 200 | */ | |
| 201 | function example_achievements_locked($achievement, $uid) { | |
| 202 | // react to achievement removal. bad user, BaAaDdd UUserrRR! | |
| 203 | } | |
| 4491b057 MI |
204 | |
| 205 | /** | |
| 254e2c5e | 206 | * Implements hook_achievements_leaderboard_alter(). |
| 4491b057 MI |
207 | * |
| 208 | * Allows you to tweak or even recreate the leaderboard as required. The | |
| 209 | * default implementation creates leaderboards as HTML tables and this hook | |
| 210 | * allows you to modify that table (new columns, tweaked values, etc.) or | |
| 211 | * replace it entirely with a new render element. | |
| 212 | * | |
| 213 | * @param &$leaderboard | |
| 214 | * An array of information about the leaderboard. Available keys are: | |
| 215 | * - achievers: The database results from the leaderboard queries. | |
| 216 | * Results are keyed by leaderboard type (top, relative, first, and | |
| 217 | * recent) and then by user ID, sorted in proper ranking order. | |
| 218 | * - block: A boolean indicating whether this is a block-based leaderboard. | |
| 219 | * - type: The type of leaderboard being displayed. One of: top (the overall | |
| 220 | * leaderboard displayed on achievements/leaderboard), relative (the | |
| 221 | * current-user-centric version with nearby ranks), first (the first users | |
| 222 | * who unlocked a particular achievement), and recent (the most recent | |
| 223 | * users who unlocked a particular achievement). | |
| 224 | * - render: A render array for use with drupal_render(). Default rendering | |
| 225 | * is with #theme => table, and you'll receive all the keys necessary | |
| 226 | * for that implementation. You're welcome to insert your own unique | |
| 227 | * render, bypassing the default entirely. | |
| 228 | */ | |
| 254e2c5e | 229 | function example_achievements_leaderboard_alter(&$leaderboard) { |
| 4491b057 MI |
230 | if ($leaderboard['type'] == 'first') { |
| 231 | $leaderboard['render']['#caption'] = t('Congratulations to our first 10!'); | |
| 232 | } | |
| 67552320 MI |
233 | } |
| 234 | ||
| 235 | /** | |
| 236 | * Implements hook_query_alter(). | |
| 237 | * | |
| 238 | * The following database tags have been created for hook_query_alter() and | |
| 239 | * the matching hook_query_TAG_alter(). If you need more than this, don't | |
| 240 | * hesitate to create an issue asking for them. | |
| 241 | * | |
| 242 | * achievement_totals: | |
| 243 | * Find the totals of all users in ranking order. | |
| 244 | * | |
| 245 | * achievement_totals_user: | |
| 246 | * Find the totals of the passed user. | |
| 247 | * | |
| 248 | * achievement_totals_user_nearby: | |
| 249 | * Find users nearby the ranking of the passed user. | |
| 250 | */ | |
| 251 | function example_query_alter(QueryAlterableInterface $query) { | |
| 252 | // futz with morbus' logic. insert explosions and singularities. | |
| 253 | } | |
| 254 | ||
| 255 | /** | |
| 256 | * Implements hook_achievements_access_earn(). | |
| 257 | * | |
| 258 | * Allows you to programmatically determine if a user has access to earn | |
| 259 | * achievements. We do already have an "earn achievements" permission, but | |
| 260 | * this allows more complex methods of determining that privilege. For an | |
| 261 | * example, see the achievements_optout.module, which allows a user to opt-out | |
| 262 | * of earning achievements, even if you've already granted them permission to. | |
| 263 | * | |
| 264 | * @param $uid | |
| 265 | * The user ID whose access is being questioned. | |
| 266 | * | |
| 267 | * @return | |
| 34da989d MI |
268 | * TRUE if the $uid can earn achievements, FALSE if they can't, |
| 269 | * or NULL if there's no change to the user's default access. | |
| 67552320 MI |
270 | */ |
| 271 | function example_achievements_access_earn($uid) { | |
| 272 | $account = user_load($uid); | |
| 273 | if ($account->name == 'Morbus Iff') { | |
| 274 | // always, mastah, alllwayyYAYsss. | |
| 275 | return TRUE; | |
| 276 | } | |
| 277 | } |