Drupal

Achievements 7.x-1.5 released

The Drupal Achievements module offers the ability to create achievements and badges similar to systems seen on Xbox 360, Playstation 3, Foursquare, Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a certain number of times, starting a forum topic, visiting the site every day of the week, or anything else that can be tracked and coded. The recently released 7.x-1.5 update adds two optional modules, a new type of achievement, and a bunch of bug fixes and tweaks.

Critical API change for "hidden" achievements

If you're using "hidden" achievements (that is, achievements which coquettishly say "I'm here!", but don't reveal their points, title, or description until unlocked), they've been renamed to "secret" in 7.x-1.5. If you don't retype any of your hidden achievements in your code, they'll become visible to your users. From a gamer's perspective, a "secret achievement" is a more frequently used phrase than a "hidden achievement", and "secret" and "invisible" (see below) work better together than "hidden" and "invisible" (which are synonymous).

Invisible achievements are now available

"Secret" achievements are those that are displayed to the end-user but don't reveal their details until unlocked. These achievements are best used to indicate there are still things to earn, but without spoiling the fun of discovering how to do so. Sometimes, though, you want entirely "invisible" achievements - those that don't show up at all in the user's achievements list until they've been earned. These are now possible in Achievements 7.x-1.5, and are great for tenure-based milestones to thank long-time users or beta testers, event-specific achievements for those who were there at the right place and the right time, or simply to provide a different type of experience when you have thousands of achievements or want to "build up" the user's achievement list vs. starting them off with everything visible and locked.

Opting out and no competition

Two new optional modules have been made available in this release. Some folks just plain ol' hate gamification, and the Opt-out module adds a new checkbox field to the user's edit screen that allows them to refrain from (and reenable, if they so desire) earning achievements. Opting out will hide the user's Achievements tab, delete all their currently earned achievements and storage data, and can also be used as a quickie "start over" mechanism (though if you've written any legacy unlocking code for them, they won't re-earn for free what you may have granted them in the past).

The second module, Pointless, removes all the explicit competitive elements of achievements. Milestones will no longer display a user's unlock rank or the number of points, and access to the leaderboards is disabled. This creates an environment where the user unlocks achievements for their own gratification, not to simply one-up another user or to get noticed on a nebulous high-score table.

Et cetera

Further information about smaller tweaks and bug fixes is available in the release notes.

Using Achievements on your site? I'd love to see it and start unlocking 'em myself. Lemme know!

Achievements development is supported by Examiner.com, which uses the module for its Examiner-specific Trophy Case. If you're a passionate or knowledgeable writer, consider applying to become an Examiner and see the implementation yourself, as well as earning money for your popular articles.

Disobey on Drupal 7 and... Bartik?!

During DrupalCon Denver 2012, I decided to randomly upgrade Disobey.com from Drupal 6 to Drupal 7... live and without any testing or preparation. Part of my reticence for upgrading in the past was my custom theme: I simply had no interest in upgrading it to the new stuff in 7. My work of late keeps me far away from theming, and I've come to prefer "default interfaces" for things (thus why I've never tweaked my wiki's look and feel). And, generally speaking, I wasn't a fan of any of the popular Drupal contributed themes - they either required just as much work to get somewhere (Zen, ninesixty, etc.) or had crazy dependency trees that made me ROFL (Omega).

But, by this point, I didn't really care! I was frustrated with my stop motion, so I decided to use Corolla from Adaptive Themes. After fiddling with that for a few days, I hated how it looked on the iPad (and its responsive design was its primary selling point to me), and grew frustrated by some bug which caused background coloring or images on the site header to stop stretching across the full viewing width.

Screw it, I said again! I switched to core's Bartik. It's a default theme and will likely last another core version or two, right? Still, I couldn't leave well enough alone and I've just finished my 275 line CSS reskin. Disobey.com now looks nearly exactly like it did under Drupal 6, only with Bartik under Drupal 7. I no longer have to maintain a full theme, and I can just worry about CSS from now on. Works for me.

Tags:

Achievements 7.x-1.4: Using render arrays

The Drupal Achievements module offers the ability to create achievements and badges similar to systems seen on Xbox 360, Playstation 3, Foursquare, Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a certain number of times, starting a forum topic, visiting the site every day of the week, or anything else that can be tracked and coded. The recently released 7.x-1.4 update focuses on changes that make theming easier.

In my first post about #theme_wrappers, we fixed up bad HTML in #prefix and #suffix to give themers an easier time. This post, about render arrays, is no different. In fact, most of the tweaks in Achievements 7.x-1.4 are not very obvious, or even useful, to module developers. It's only when you sit down with a themer who wants to change the entire look and feel that you realize your carefully-coded "looks good in Garland!" output is not as useful as you think.

Why render arrays are better

You've probably read about render arrays in the Drupal Developer's Handbook already, but the documentation doesn't really give a developer a decent reason why they should use them. It also doesn't talk about $content or splitting your variables into "data" and "presentation". I'll cover both below.

First, let's look at the wrong approach:

    $build['image'] = theme('image', array('path' => array('misc/druplicon.png')));

If this was passed to a theme file, the themer would simply print $image; and be done with it. The themer could even link the image if they wanted, but they wouldn't be able to add any classes or attributes to the image, since calling theme() directly always returns rendered HTML. Here's what the above looks like as a render array:

    $build['image'] = array(
      '#theme' => 'image',
      '#path' => 'misc/druplicon.png',
    );

To get this to display in the theme, we'd use print render($image); instead. In this case, the theme is telling Drupal to render the HTML, not your module. Why is this important? Ignoring the nebulous "I CAN'T ADD CSS CLASSES, SNIFF!" themer lament, consider the following small tweak to our original bad example:

    $image = theme('image', array('path' => array('misc/druplicon.png')));
    $build['image'] = l($image, 'node', array('html' => TRUE));

Here, we've simply added a link to our dummy image and the themer gets the fully rendered HTML to print out. Everything is toasty... unless the client or theme doesn't want or need the image to be linked. The themer now has no choice: they either have to use a regular expression to strip out the unwanted link or they have to recreate the $image variable in his own, well, image. Not only is that fragile (it's essentially copying module code to the theme and that code might change in a future version), but it also mixes too much logic with too little presentation. If the image's path isn't available to the theme as its own variable (it isn't, in the above example), the themer will still have to parse your rendered HTML to find the image's path first. Yuck.

Let's convert the above to a render array:

    $build['image'] = array(
      '#theme' => 'image_formatter',
      '#item' => array(
        'uri' => 'misc/druplicon.png',
      ),
      '#path' => array(
        'path' => 'node',
        'options' => array('html' => TRUE),
      ),
    );

Now, if the themer wants to remove the link, they can listen in a theme override, a preprocess, or even the dreaded hook_page_alter() and simply unset($variables['image']['#path'])'. No more link, without duplicating any upstream code or recreating things themselves. This is a real-life example, by the way - I just finished an optional submodule of Achievements which removes all links to the default leaderboards. I couldn't have easily done that if I used either of the link approaches below:

    $build['achievement_title'] = l($title, 'path/to/achievement');
    $build['achievement_title']['#markup'] = l($title, 'path/to/achievement');

But with the following render array:

    $build['achievement_title'] = array(
      '#type' => 'link',
      '#title' => $title,
      '#href' => 'path/to/achievemnt',
    );

I can make those links linkless:

  unset($variables['achievement_title']['#type']);
  $variables['achievement_title']['#markup'] = $variables['achievement_title']['#title'];

Data, presentation, and $content['a'] not $a

This doesn't mean that every single variable you pass to the theme should be a render array: there's clearly a difference between variables that represent data (an image path, the $node or $user object, a Unix timestamp, etc.) and variables that are meant for presentation (the linked image, the node's filtered teaser, a human-readable date, etc.).

There's a move afoot to more clearly indicate these two types by using a $content variable as a container for all the supplied render arrays. Drupal core does this in some places (node.tpl.php, for one), but not all, and it's slowly becoming a preferred practice in the theming world. Another benefit is the ability to use print render($content);, which says "render everything I haven't already rendered", allowing themes to display things that they, or the parent module, might not know about (new data added by a third party module in a preprocess, etc.). I've yet to implement this in Achievements, but I'll likely get there for the next release.

Achievements 7.x-1.4: Using #theme_wrappers

The Drupal Achievements module offers the ability to create achievements and badges similar to systems seen on Xbox 360, Playstation 3, Foursquare, Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a certain number of times, starting a forum topic, visiting the site every day of the week, or anything else that can be tracked and coded. The recently released 7.x-1.4 update focuses on changes that make theming easier.

One of those changes was implementing #theme_wrappers. Like many other modules, Achievements has various HTML displays that are essentially containers of things: a group of unlocked achievements, a group of categorized achievements, etc. For stronger theming, one usually wraps those in an extra <div> so that CSS folks can control the container's display. In the past, I've usually accomplished this with:

    $build['achievements'] = array(
      '#prefix' => '<div class="achievement-groups">',
      '#suffix' => '</div>',
    );

Simliar code to the above exists in Drupal core (book.module, field.module, etc.) so I never gave it a second thought. However, for a themer, the above is problematic because there's no way to tweak that HTML without dipping into a hook_page_alter() and rewriting or munging the #prefix entirely. Since I've been away from Drupal theming for so long, I never knew there was a stronger alternative by using #theme_wrappers. Not surprisingly, Drupal core uses this pattern alongside the "bad" #prefix version, so a good contender for a "Novice" patch would likely be "Replace all uses of #prefix divs with #theme_wrappers".

Like other theme functions and files, #theme_wrappers requires a hook_theme() definition:

    function achievements_theme() {
      return array(
        'achievement_groups_wrapper' => array(
          'render element'  => 'element',
        ),
      );
    }

In this case, we'll use a regular ol' theme function for the callback:

    /**
     * Default theme for the wrapper around a user's achievements page.
     *  
     * @param $variables
     *   An associative array containing:
     *   - element: A render containing the user's achievements page.
     */
    function theme_achievement_groups_wrapper($variables) {
      return '<div id="achievement-groups">' . $variables['element']['#children'] . '</div>';
    }

And finally, we can tweak the above $build render array to use it:

    $build['achievements'] = array(
      '#theme_wrappers' => array('achievement_groups_wrapper'),
    );

In this case, the resultant HTML will be exactly the same, but now a themer can more easily override it just by defining THEMENAME_achievement_groups_wrapper(). This is a far cheaper method (mentally and performant-wise) than having to futz with hook_page_alter().

Achievements 7.x-1.3 released

The Achievements module offers the ability to create achievements and badges similar to systems seen on Xbox 360, Playstation 3, Foursquare, Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a certain number of times, starting a forum topic, visiting the site every day of the week, or anything else that can be tracked and coded.

Here's what's new in version 7.x-1.3, released today.

Relative leaderboards and latest achievements

The biggest changes in 7.x-1.3 happened to the leaderboards. You can now provide an (optional) "relative leaderboard" which will show a logged-in user their current rank as well as (optionally) a number of ranks before and after their position. For folks who like achievements or mapping their rise to greatness, this will give them a little something more to strive for, a continual heartbeat of their movement through the site-wide rankings. It can also encourage rivalries as users flip-flop back and forth in their point gains and positions. Some users won't give a crap at all, certainly, but for those that do, relative leaderboards can increase a site's stickiness.

Another addition to the leaderboard is "latest achievement" which is exactly as it sounds: it'll show the latest unlock a user has earned. This is important because it increases discoverability of your site's achievements. In previous versions of the module, the only mention of individual achievements was located on a tab in the user's profile -- not exactly the easiest place to find. By including recent achievements on the leaderboard, we're increasing awareness of what you're offering, as well as providing subtle and potential todos for your users and curious visitors.

Hooks for developers

Besides the required hook_achievements_info(), I didn't add any other achievement hooks in the earlier releases because I didn't trust I had a firm grasp of how Achievements module would "end up" based upon my initial roadmap. With said roadmap nearly complete, the latest release of Achievements now includes some additional hooks for more integration. As is best practice, I'm starting off "small and obvious", but will add more as needs arise. hook_achievements_info_alter(), hook_achievements_unlocked(), and hook_achievements_locked() all do what they sound like and are further documented in achievements.api.php.

Small footprint

At its heart, Achievements is a monitoring and statistical module: it listens for events, logs statistics, and does analysis on incoming data. When you have hundreds of achievements, all this intervention could slow your site down. Thankfully, the module has been designed under the oppressive gaze of better performance experts than I and has Examiner.com, one of the largest Drupal 7 sites, as an in-progress client. The new release of Achievements has yet another performance improvement that ensures heavy traffic sustainability, and the new features above cost just one additional database query (per page, but only if you're using the block with relative leaderboards enabled). Future releases of Achievements will continue this dedication to a svelte codebase.

Take a look, bub

You can read about the full feature-set at the Achievements module project page which also includes screenshots of the default interface. Don't hesitate to contact me or create an issue with your pie-in-the-sky feature requests and questions.

Achievements 7.x-1.2 released

Achievements 7.x-1.2 has been released:

  • Achievement unlock notifications are now JS fadeins and offline-able.
    • We no longer use drupal_set_message() to inform of an unlock.
    • A new achievement-notification.tpl.php controls the appearance.
    • Unlock notifications now fade in and out at the window's bottom right.
    • We now track whether a user has seen an achievement unlock notification.
    • If they haven't, they will the next time they login or access the site.
  • Leaderboard top rank counts can now be tweaked in the relevant configs.
    • This is in preparation for "relative leaderboards" planned for 7.x-1.3.
  • Unlocked achievements on user/#/achievements can now be unsorted.
    • That is, instead of "move to top" they can remain "as defined in code".
    • Defaults to "move to top". Config at admin/config/people/achievements.
  • Block 'achievements-leaderboard' renamed with underscores not dashes.
    • If you're using this block, you'll need to place it again upon upgrade.
  • Fixed a few PHP warnings when a user has yet to unlock any achievements.
  • Achievement unlocks are now logged in watchdog.
  • Variables are now properly deleted on uninstall.

Achievements 7.x-1.1 released

Achievements 7.x-1.1 has been released (with screenshots for installer trepidation):

  • Achievements can now be categorized into groups.
    • Grouped achievements will be displayed within jQuery UI tabs.
    • If groups exist, ungrouped achievements "upgrade" automatically.
    • See achievements.api.php for more on how to define achievement groups.
  • Achievements can now have images.
    • Three possible display states: locked, unlocked, and hidden.
    • Admins may set the default images at admin/config/people/achievements.
    • An 'images' array has been added to hook_achievements_info() definitions.
    • Per-achievement images can override the default on a per-state basis.
    • CSS tweaks were made for a more flexible achievement display.
    • Default images have been provided for each of the three states.
    • Administrators can now manually give and take achievements from users.
      • It's problematic on progression-based achievements and internal statistics.
      • hook_achievements_info() gets 'storage' to define where statistics are kept.
      • If 'storage' is not specified, assume it exists under the achievement ID.
      • See admin/config/people/achievements for the disclaimer text on usage.
      • See achievements.api.php for more on 'storage' and a revised HOWTO.
    • A new permission, "Earn achievements", has been added.
      • It is REQUIRED and NECESSARY for all roles that can unlock achievements.
      • Core functions check for it so you shouldn't need it in your own code.
      • If you think you need to check, use achievements_user_is_achiever().
      • Removing this permission from a role does NOT delete data or ranks.
      • It does stop, however, the collection of new data, points, or unlocks.
    • achievements/leaderboard/NONEXISTENT now returns a 404.
    • Locked achievements are now displayed on a user's achievements tab.
    • No more warnings when viewing a user with no achievement unlocks.
    • 'id' is no longer duplicated in hook_achievements_info() definitions.
    • achievements.tpl.php has had its PHP moved to template_preprocess_hook().
      • New variables have been added to streamline the display code.

    Achievements 7.x-1.0 released

    The first "official" version of my Achievements module is now available for Drupal 7.x. Achievements originated as a Drupal 6 module from 2008 which I just never got around to properly supporting with a Drupal project - it has lived the past three years as a tarball attached to an issue. Due to some recent interest from others, however, I've since finalized a new 1.0 with lots of incremental improvements, with a particular eye toward supporting very large installations. These same interested parties will be satisfying a number of new features and improvements over the coming weeks, including:

    • Per-achievement images and the ability to categorize your milestones.
    • Manual/administrative granting/removing of achievements.
    • A JavaScript popup, ala Xbox 360 or World of Warcraft, instead of drupal_set_message().
    • Progress meters for grindish achievements.

    ...and a healthy bit more.

    Bot 7.x-1.3 released

    My Drupal IRC bot.module received a new release today, bringing it to 7.x-1.3:

    • #273116: bot_auth.module added (thanks snufkin, RobLoach).
    • scripts/bot_start.php: the new location of bot_start.php.
    • scripts/bot_check.sh added: restarts the bot if it's not running.
    • #524218: Drush integration added (thanks jonhattan, q0rban, sirkitree).
    • bot_log.channel is now a text column (required for large installations).
    • All incoming messages are forced to UTF-8 (thanks bellHead, nick_vh).
    • #918966: New Net_SmartIRC wrapper; hook_irc_access added (thanks Bevan).
    • All forgotten strlens()s have been changed to drupal_strlen().
    • All forgotten substr()s have been changed to drupal_substr().
    • First release for Drupal 7.x; based on 6.x-1.2 release.

    Tags:

    Bot 6.x-1.2 released

    My Drupal IRC bot.module received a new release today, bringing it to 6.x-1.2:

    • #937836 and #937820: PostgreSQL and E_STRICT fixes (thanks Shiny).
    • #477596: Fixed regexp error in bot_seen.module (thanks Gurpartap).
    • bot_tell.module can now remind you of things: "BOTNAME: help Reminders?"
    • bot_potpourri.module added (thanks ae1): "BOTNAME: help Timezones?"
    • bot_aggregator.module added (thanks cwgordon7/snufkin). Additions:
      • Configuration has been moved to the feed config form, not our own.
      • Different feeds can now be sent to different channels, based on config.
    • If we receive an error containing "flood", we slow down our sends.
    • If the server or client library receives an error, we'll watchdog it.
    • If a channel bans us, it is now permanently removed from the join list.
    • #648606: Chinese "ni hao" is mis-spelled in greetings (thanks JohnAlbin).
    • Advanced/debugging option added: whether to use real sockets or not.
    • bot_project: fixed broken Trac metadata from ticket/revision exports.
    • bot_project: function lookups now allow dashes in the branch name.
    • irc_bot_cron_faster (one minute) and _fastest (15 seconds) are available.
    • #564524: bot_project now reports comment counts on d.o URLs (thanks killes).
    • bot_tell now accepts :;, after a told nick (thanks webchick).
    • Channels are now joined every 15 seconds instead of at initial connection.
    • Logging checks for channels are now case-insensitive (thanks mozillamonks).
    • #362661: Attempt to GHOST and IDENTIFY on nick clashes (thanks seutje).
    • #380330: Added perms for factoids, karma, and project (thanks lut4rp).
    • #391916: bot_log needed another index for speedier table SELECTs.

    Tags:

    Pages

    Subscribe to RSS - Drupal