Tag Archives: WordPress

Enabling Popularity Contest for WordPress networkwide use

Alex King’s Popularity Contest is a pretty cool way to collect data about which posts on a WordPress site the most popular. The data collected is more sophisticated and customizable than simple analytics, because it distinguishes between page views and things like trackbacks, comments, and other kinds of hits. The plugin supports WordPress Multisite in the sense that it’s possible to activate network-wide; when you do so, the plugin keeps site-specific popularity stats. But what if you want popularity rankings across your entire network?

I recently modified Popularity Contest to do just that. The idea is simple: in order to keep network-wide stats, we need a network-wide table (instead of the default site-specific tables). How do you keep information about all networkwide posts on a single blog? I used Donncha O Caoimh’s Sitewide Tags as a bridge. All posts across the network are copied to the tags blog, and popularity data is indexed on the tags blog.

To make this work, several things are needed. I can’t just give you the files because I’ve altered them in other, irrelevant ways, but I will walk you through the process of setting it up. Also, keep in mind that we’ll be modifying the plugin code for both Popularity Contest and Sitewide Tags, modifications you’ll have to make each time you upgrade the plugins. Make sure you back up your work.

  1. Install Sitewide Tags which can be downloaded from its website. You have to activate a few things in order to turn it on – read the readme carefully. Be sure to take note of the tags blog id number, as we’ll need that in a later step.
  2. In sitewide-tags.php, look for the function sitewide_tags_post(). Near the end of the function is a line that says restore_current_blog();. Immediately after that line, enter the following code:
    [code language=”php”]update_post_meta( $post->ID, ‘tags_post_id’, $p );[/code]
    That line makes sure that every time a post is aggregated on the tags blog, the original post gets a piece of metadata noting the post id of the corresponding tags blog post. We’ll use that information in a later step.
  3. Download Popularity Contest from its website. I don’t recommend that you activate it yet, especially not networkwide, because it will create a lot of tables that you don’t really need.
  4. The next few steps will require mading some modifications in the main Popularity Contest plugin file, popularity-contest.php. The first modification is to change all references to $wpdb->posts (which, when activated networkwide, will refer to the posts table for the individual blogs) and change them to point to the tags blog post table. A search and replace that replaces $wpdb->posts with wp_posts (or wp_x_posts, if your tags blog is not site number 1 but is instead site x.
  5. The next modification involves the function akpc_init(), near the end of the plugin file. That’s where the table names for the Popularity Contest custom tables are found. We need to make sure that they point to the tags blog. Replace the existing function with this:
    [code language=”php”]
    function akpc_init() {
    global $wpdb, $akpc;

    $wpdb->ak_popularity = ‘wp_ak_popularity’;
    $wpdb->ak_popularity_options = ‘wp_ak_popularity_options’;

    $akpc = new ak_popularity_contest;
    $akpc->get_settings();
    }
    [/code]
    If your tags blog is something other than blog 1, you could change these table names to match (e.g. wp_15_ak_popularity) but it isn’t really necessary.

  6. Now we have to make Popularity Contest aware of the identity relationships between the tags posts and the original posts. Two snippets of code should do it in most places. First, find the function record_feedback, which starts around line 700. Right before the switch($type) line, insert the following:
    [code language=”php”]
    if ( $tags_post_id = get_post_meta( $comment_post_ID, ‘tags_post_id’, true ) )
    $comment_post_ID = $tags_post_id;
    [/code]
    Next, find the function akpc_api_record_view(), which starts around 2550. Right after array_unique($ids); (around line 2555), insert the following:
    [code language=”php”]
    $tags_ids = array();
    foreach ( $ids as $id ) {
    $tags_ids[] = get_post_meta( $id, ‘tags_post_id’, true );
    }
    $ids = $tags_ids;
    [/code]
    These two modifications make sure that Popularity Contest knows which post on the tags blog corresponds to the post being visited/commented on on the child blogs.
  7. At this point, you can activate the popularity plugin networkwide. Here’s what happens, very roughly:
    • The plugin creates the necessarily popularity tables – just one set for the whole installation.
    • When you publish a new post on any site, it gets copied to the tags blog. Our modification from step 2 makes sure that the copied post ID (let’s say 36) is saved to the original post.
    • When someone visits the original blog post, Popularity Contest fires (because it’s been activated network wide). Our modifications in steps 4 and 5 make sure that the plugin knows to record the activity to the tags blog index, and step 6 make sure that the plugin know which post the activity belongs to.
  8. You’ll need another modification to get the data out, since you’ll want to display it on your site somewhere. The default function for this is called show_top_ranked(). We need to modify so that it gets the requested data from the right place. Replace the stock function with this one:[code language=”php”]
    function show_top_ranked($limit, $before, $after) {
    switch_to_blog( 1 );
    if ($posts=$this->get_top_ranked_posts($limit)) {
    foreach ($posts as $post) {
    $ud = get_userdata( $post->post_author );
    print(
    $before. get_thumbnail( $post->post_author, 36 ) .’ID).'”>’
    .$post->post_title.’

    ‘. $ud->display_name . ‘‘ . $after
    );
    }
    }
    else {
    print($before.'(none)’.$after);
    }
    restore_current_blog();
    }
    [/code]
    Make sure you change the number in the switch_to_blog() call to the id of your tags blog.

I think I’ve remembered everything. Good luck!

Bonus!

For my project, I was moving from a single WordPress site to a multisite situation. The popularity plugin had been running on both setups for a while, so the data was totally messed up and needed to be combined (which meant finding the corresponding post data and adding it together – yeesh!). Here’s the script I used – be careful with it, and keep in mind that it was designed for a *very* specific use. Do not use this code if you don’t understand exactly what every line does!

[code language=”php”]
global $wpdb;

$query = “SELECT * FROM {$wpdb->blogs} WHERE site_id = ‘{$wpdb->siteid}’ “;
$blog_list = $wpdb->get_results( $query, ARRAY_A );

foreach( $blog_list as $blog ) {
//print_r($blog); continue;
if ( $blog[‘blog_id’] == 1 ) continue;
//if ( $blog[‘blog_id’] != 83 ) continue;

$tn = ‘wp_’ . $blog[‘blog_id’] . ‘_posts’;
$tnmeta = ‘wp_’ . $blog[‘blog_id’] . ‘_postmeta’;

$query = “SELECT ID FROM {$tn} WHERE post_type = ‘post’ AND post_status = ‘publish’ “;
$posts = $wpdb->get_results( $query, ARRAY_A );

foreach( $posts as $post ) {
$id = $post[‘ID’];

$query = “SELECT meta_value FROM {$tnmeta} WHERE post_id = ‘{$id}’ AND meta_key = ‘tags_post_id’ “;
$tags_post_id = $wpdb->get_results( $query, ARRAY_A );
$tpid = $tags_post_id[0][‘meta_value’];

$query = “SELECT * FROM wp_ak_popularity WHERE post_id = ‘{$id}'”;
$old_data = $wpdb->get_results( $query, ARRAY_A );
$old_data = $old_data[0];

$query = “SELECT * FROM wp_ak_popularity WHERE post_id = ‘{$tpid}'”;
$new_data = $wpdb->get_results( $query, ARRAY_A );
$new_data = $new_data[0];

if ( $old_data && $new_data ) {
$combined_data = array();

$combined_data[‘post_id’] = $new_data[‘post_id’];
$combined_data[‘last_modified’] = $new_data[‘last_modified’];

foreach( $old_data as $key => $d ) {
if ( $key == ‘post_id’ || $key == ‘last_modified’ )
continue;

$combined_data[$key] = (int)$d + (int)$new_data[$key];
}
}

$query = ‘UPDATE wp_ak_popularity SET ‘;
foreach( $combined_data as $key => $cd ) {
if ( $key == ‘post_id’ )
continue;
$query .= “{$key} = ‘{$cd}’, “;
}

$query = substr_replace( $query, ”, -2 );
$query .= ‘ ‘;

$query .= “WHERE post_id = ‘{$combined_data[‘post_id’]}'”;
$wpdb->query( $query );
print_r( $old_data ); echo “
“; print_r($new_data); echo “
“; print_r($combined_data); echo “
“; echo $query; echo “

“;

}

echo “

";
//		print_r($posts);
echo "

“;
}
[/code]

Anthologize 0.4-alpha is released

The Anthologize team has been hard at work over the last week, fixing bugs behind some of the most commonly reported problems, and adding features to make Anthologizing easier and more fun. We’ve just tagged version 0.4-alpha in the WordPress repository. Visit your WordPress Dashboard’s Plugins page to upgrade.

Read more about the changes in 0.4-alpha.

Questions or thoughts about Anthologize? Visit the Anthologize home page or the Anthologize users group.

Hiding WordPress custom post type menu items without disabling edit access

WordPress 3.0’s custom post types are really cool, opening up a whole new world of use cases for WordPress. We used custom post types extensively when developing Anthologize. But there are still some rough spots.

For instance, the ‘show_ui’ parameter of register_post_type() is a little bit too coarse-grained for our purposes. For Anthologize, we wanted to allow the user to edit custom post types with the standard Edit page, but we didn’t want users to be able to access most of these post types through the menu items automatically created by register_post_types (all links to the edit pages would appear on our custom Dashboard panel, in order to reduce redundancy and confusion). With ‘show_ui’ set to true, users could access the edit screens, but they could also access the unwanted menu items; with ‘show_ui’ set to false, the menu items were hidden, but navigating to the Edit pages (directly, via URL) threw a “You don’t have permission to access this page” error.

Here’s how we resolved the dilemma. Note that it’s a bit hackish at the moment. In the future, I hope the WordPress team will split ‘show_ui’ gets into multiple, separate arguments.

  1. In your register_post_type() call, set ‘show_ui’ to true. Here’s an example from Anthologize:
    [code language=”php”]
    register_post_type( ‘library_items’, array(
    ‘label’ => __(‘Library Items’, ‘anthologize’ ),
    ‘public’ => true,
    ‘_builtin’ => false,
    ‘show_ui’ => true,
    ‘capability_type’ => ‘page’,
    ‘hierarchical’ => true,
    ‘supports’ => array(‘title’, ‘editor’, ‘revisions’),
    ‘rewrite’ => array(“slug” => “library_item”)
    ));
    [/code]
  2. To remove the unwanted menu items, we’ll take advantage of the fact that WordPress has built-in support for custom menu order. First, we have to tell WordPress to expect a custom menu order. (The following two functions are modified from Anthologize, where they’re methods on a loader class.)
    [code language=”php”]
    function toggle_custom_menu_order(){
    return true;
    }
    add_filter( ‘custom_menu_order’, ‘toggle_custom_menu_order’ );
    [/code]
  3. Once custom_menu_order has been set to true (step 2), WordPress makes a new filter hook available, menu_order. As the name says, it’s really meant to reorder menu items, but we’ll use it to erase menu items altogether.
    [code language=”php”]
    function remove_those_menu_items( $menu_order ){
    global $menu;

    foreach ( $menu as $mkey => $m ) {
    $key = array_search( ‘edit.php?post_type=library_items’, $m );

    if ( $key )
    unset( $menu[$mkey] );
    }

    return $menu_order;
    }
    add_filter( ‘menu_order’, ‘remove_those_menu_items’ ) );
    [/code]

    Here’s what’s happening. The filter hook is meant to modify $menu_order. That’s why remove_those_menu_item() takes $menu_order as an argument, and returns it back to WordPress untouched on the last line of the function. On the first line of the function, we’re taking advantage of the fact that the $menu variable – where menu items are stored for construction into markup later on – is in the global scope. Once we’ve declared that we’ll be using $menu on the first line, we loop through each of the menu items, and when we find one that matches our custom post type (ie, when we find one that contains the string ‘edit.php?post_type=library_items’ – you’ll have to replace the post_type with your own, obviously), it gets removed from the $menu global.

You can iterate this for as many different custom post types as you’d like – just add more potential keys to the foreach loop in remove_those_menu_items(), eg
[code language=”php”]
$key = array_search( ‘edit.php?post_type=library_items’, $m );
$keyb = array_search( ‘edit.php?post_type=some_other_post_type’, $m );

if ( $key || $keyb )
unset( $menu[$mkey] );
[/code]

Introducing Anthologize, a new WordPress plugin

The moment has arrived!

Anthologize

Anthologize

The product of One Week | One Tool, a one week digital humanities tool barn raising hosted by CHNM and sponsored by the NEH Office of Digital Humanities, is Anthologize. Anthologize is a WordPress plugin that lets you collect and curate content, organize and edit it into a form that works for you, and publish it in one of a number of ebook formats.

As I said in my last post, I was the lead developer for Anthologize. This stemmed from the fact that, for reasons of market penetration and ease of use, we’d chosen WordPress as a platform, and I was “the WordPress guy”. As such, I was the natural person to oversee the various parts of the development process, and to make sure that they fit together in a neat WordPress plugin package. It was an incredible and humbling experience to work with a group of developers who were, to a person, more talented and experienced than I am.

Anthologize PDF output

Anthologize PDF output

Today, the plugin is shipping with four different formats for exporting: PDF, ePub, RTF, and a modified version of TEI that leaves most content in HTML form. None of these export processes are perfect. Some require that certain libraries be installed on your server; some do not offer the kind of layout flexibility that we like; some are not great at text encoding; etc. This release is truly an alpha, a proof-of-concept. The goal is to show not only what a group of devoted individuals can conceive and develop in six short days, but also to provide the framework for further development in the world of independent authorship, publishing, and distribution.

As such, the plugin is designed, and will continue to be developed, with an eye toward maximum flexibility and modularity. Content can be created in WordPress or pulled in by RSS feeds, providing for greater choice of authoring platform. Export formats are generated by translators that work not with native WordPress data, but with an intermediary layer structured with TEI metadata markup. That means that you don’t have to know anything about WordPress to build a new export translator for yourself – you only have to know some PHP and XSLT. And we’re working on expanding Anthologize’s action and filter hooks to allow for true pluggability in the manner of WordPress itself.

I’m hoping that Anthologize will be a useful tool that draws development interest from folks who might not otherwise be interested in WordPress or web development, especially those who are working in the academic, cultural heritage, and digital humanities worlds. Get involved by checking out our Github repository at http://github.com/chnm/anthologize, our development list at http://groups.google.com/group/anthologize-dev, or stop in and chat with the dev team at #oneweek or #anthologize-dev on freenode.

Import From Ning now imports Ning content into BuddyPress

[IMPORTANT: I am no longer supporting this plugin. You may contact me for a list of consultants who may be interested in providing Ning import support.]

Back when Ning announced that it’d be cutting off previously free accounts, I took a weekend and developed Import From Ning, a plugin that helped users pull their Ning user and profile data into a WordPress or BuddyPress installation. It was my own little BuddyPress-fanboyish way of helping all those Ningsters.

Several weeks ago, Ning released its Ning Network Archiver, which (finally) allowed network admins an easy way to take their content with them. On the heels of this release by Ning, today I am releasing version 2.0 of Import From Ning, which imports the content from a Ning Network Archive.

Read more about the updated plugin here. And to those Ningsters who are coming over to WordPress and BuddyPress: good on ya!

Making Userthemes work on WordPress 3.0

Some friends of mine (Joe Ugoretz and Jim Groom) were chatting on Twitter yesterday about how Userthemes, the WPMU/MS plugin they rely on to allow user customizations of copied system themes, had broken with WordPress 3.0. I decided to take a look at it. After digging a little, I found the immediate cause, as well as a workaround.

Please note that this workaround is very much a hack. It shouldn’t cause any security issues (see explanation below), but it will break the next time you upgrade WP.

Joe’s problem was that the plugin was only working for Super Admins. Administrators of single Sites could not copy new Userthemes, and they were redirected to the dreaded wp-admin/?c=1 when they tried to access the Edit Userthemes panel on the Dashboard. I figured it was a problem with permissions, and it was: all of those functions are triggered only for those users with the capability edit_themes, but for some reason only Super Admins, and not Administrators, were showing up as having that ability. (The weird thing – when I did a var_dump of WP Roles, I saw that Administrator *did* have edit_themes.) Maybe there’s some setting in WPMS that allows users to edit themes, but I couldn’t see it.

So the solution is to change the edit_themes check to something else. switch_themes seemed like an obvious choice to me, since anyone with the ability to switch themes on a given blog would also have had the ability to edit themes on that same blog. So there shouldn’t be a security problem – only blog admins should have the ability to make userthemes.

You’ll need to modify the plugin, as well as a few lines in the WordPress core.

  1. Back up. I’m not responsible for anything that goes wrong!
  2. Open the userthemes.php file. (I’d link to it, but I can’t find it anywhere on the web. When I’m at a better internet connection, maybe I’ll upload a version for you to edit. Maybe someone out there has a copy to share.) Search for all instances of ‘edit_themes’ and replace with ‘switch_themes’.
  3. From your WP root directory, open wp-admin/theme-editor.php. On line 12, change ‘edit_themes’ to ‘switch_themes’.
  4. From your WP root directory, open wp-admin/menu.php. On line 173, change ‘edit_themes’ to ‘switch_themes’.
  • This should restore the basic functionality of Userthemes (though Joe says that there’s still some bugginess – if you can’t access the Edit Userthemes from the main Dashboard page, try going to the Userthemes panel first). I must repeat that this is an ugly hack, and I’m hoping that someone smarter than me will step in and tell me why this is happening in the first place.
  • New BuddyPress plugin: BP External Activity

    On the CUNY Academic Commons we have a MediaWiki installation running parallel to our WordPress/BuddyPress installation. In the past I had hacked together an inelegant and constantly breaking solution for importing wiki edit notifications into the BP activity stream. I’ve just written a small plugin called BP External Activity which solves the problem by using the BP activity API and RSS.

    The plugin can be used to pull items from any RSS feed and add them to your BP activity stream, with customizable text. It’s feature-light right now (and requires some hand-coding to work) but it’s still pretty much the coolest thing ever. I will update it to be better when I get around to it.

    Get BP External Activity here.

    BuddyPress plugins running on the CUNY Academic Commons

    Cross-posted on the CUNY Academic Commons dev blog

    A few people have asked recently for a list of the plugins installed on the CUNY Academic Commons. In the spirit of Joe’s post, here I thought I’d make it public. I’m going to limit myself to the BuddyPress plugins here, for the sake of simplicity. (I’d like to write a series of posts on the anatomy of the CUNY Academic Commons; maybe this will be the first in that series.) Here they are, in no particular order other than the order in which they appear on my plugin list.

    • BP TinyMCE. This plugin is messed up, and I have part of it switched off, but I still use the filters that allow additional tags through, in case people want to write some raw HTML in their forum posts, etc.
    • BP Groupblog. Allows blogs to be associated with groups, displaying posts on that group’s activity feed and automatically credentialing group members on the blog. I did some custom modifications to the way the plugin works so that clicking on the Blog tab in a group leads you to subdomain address rather than the Groupblog custom address (thereby also ensuring that visitors see the intended blog theme rather than the BP-ish theme).
    • BP MPO Activity Filter. This plugin works along with More Privacy Options to ensure that the new privacy settings are understood by Buddypress and that blog-related activity items are displayed to the appropriate people.
    • BuddyPress Group Documents. This one is crucial to our members, who often use the plugin to share collaborative docs.
    • BP Include Non-Member Comments makes sure that blog comments from non-members are included on the sitewide activity feed.
    • BP External Activity – an as-yet unreleased plugin I wrote that brings in items from an external RSS feed and adds them to the sitewide activity feed. We’re using it for MediaWiki edits.
    • BP Group Management lets admins add people to groups. Very handy for putting together a group quickly, without having to wait for invites.
    • BP System Report. We’re using this one to keep track of some data in our system and report it back to members and administrators.
    • BuddyPress Group Email Subscription allows users to subscribe to immediate or digest email notification of group activity. Right now we’re running it on a trial basis with a handful of members, in order to test it. (Here’s how to run it with a whitelist of users, if you want)
    • BuddyPress Terms of Service Agreement, another as-yet-unreleased plugin (this one by CAC Dev Team member Chris Stein) that requires new members to check TOS acceptance box before being allowed to register.
    • Custom Profile Filters for BuddyPress allows users to customize the way that their profile interests become links
    • Enhanced BuddyPress Widgets. Lets the admin decide the default state of BP widgets on the front page.
    • Forum Attachments for BuddyPress. Another of our most important BP plugins, this one allows users to share files via the group forums.
    • Group Forum Subscription for BuddyPress. This is our legacy email notification system, which is going to be in place until I get back from my honeymoon and can replace it 🙂
    • Invite Anyone lets our users invite new members to the community and makes it easier to populate groups.

    Questions about any of these plugins or how they work with BuddyPress? Ask in the comments.

    A distributed, multi-client courseware

    At yesterday’s THATCamp I attended a session, facilitated by Steve Ramsay, entitled “All Courseware Sucks”. You can read the blog post that served as the inspiration for the session at the THATCamp blog. Steve started the session by framing the issue in a way that ended up being quite helpful: he had us list the features of courseware that we’d used, and then to talk about whether they were all crucial. The problem is that almost all of them were crucial to at least someone in the room. Moreover, some of the items that certain individuals found the most useful (say, a gradebook where students could track their progress) seemed the most expendable to others. Listening to that discussion, I thought to myself: This must be what happens in Blackboard focus groups. Gödel’s Second Theorem of LMSes: Any learning management system with a vocabulary rich enough to do interesting work can be shown to be bloatware.

    If it’s not the functionality of courseware that we dislike, then, what is it? Well, the basic complaint seems to be that the interface is terrible. And not just terrible in an aesthetic way, though certainly most courseware is absolutely devoid of whimsy. The deeper problem is that software design decisions about how courseware conceives of a course – its hierarchy, its ontology, the vocabulary used to describe its objects, its workflow, its presentation – constrain the instructional design decisions that the professor can make about how the course will be taught.

    How do you design a courseware interface that jibes with the aesthetic and instructional predilictions of instructors from math, biology, French Lit, and everywhere between? Answer: You don’t. There are dozens and dozens of well-developed, general purpose content management systems out there. Each has an interface that its users are comfortable with. Why not take advantage of this fact? If students sometimes prefer Blackboard because at least with Bb they know what they’re getting into, why not just let them use whatever they’re already comfortable with?

    The idea in a nutshell: Abstract the content from the platform, so that individual students and professors can use whatever platform they’d like as an interface. Existing CMSes like Blackboard, Moodle, WordPress, Drupal, Joomla, and so forth become clients that sync with a central data repository hosted by the school or by a third party.

    There are about a trillion details that need to be filled in to make this viable. But here’s a very rough-n-ready explanation of how it might work in a particular case. Let’s imagine that Jack is the professor, using Drupal as client software, and Sally is the student, using WordPress as her preferred client. Jack is going to assign a blog post, and Sally’s going to complete it.

    1. Jack logs into his Drupal installation. This could be on his own server or on a centralized installation hosted by his school. On this installation there will be a module that creates a number of new content types in addition to the default Story and Page. Let’s imagine the content type is called Assignment, which contains fields for content, subject, due date, and whatever other metadata you might like.
    2. Jack writes the assignment and saves it. It saves to the Drupal database as a native Drupal item.
    3. After the save, a hook is triggered by a Bridge module. Bridges are client-specific translators that send and fetch information to and from the central repository. The Bridge module translates the Drupal content type into an XML or JSON document that is formatted according to the agreed-upon standard data format for Assignments.
    4. The Bridge module sends the document to the central Server. The Server can be connected to the student information and registrar systems in much the way that Blackboard is. The Server recognizes Jack’s signature on the document, and furthermore knows that the file is associated with Jack’s particular course. The document is translated into a Server database object and saved, keyed by Jack’s user id, the course id, etc. The Server could be set up to send out email notifications to students at this time, alerting them of a new assignment.
    5. The next time Sally logs into her WordPress installation, her WP Bridge plugin (another piece of translating software, this one WP-specific) pings the Server. The Server knows that Sally is in Jack’s class, so it looks through the database to see if any new assignments have been posted in that class since Sally last logged in. It finds the blog assignment that Jack posted, translates it to a JSON or XML doc, and sends it to Sally’s WordPress installation. The Bridge then parses the document and saves it as a WordPress-native item in the database of Sally’s WordPress installation, perhaps as an instance of a custom post type or something like that.
    6. Sally visits the course page in her WP installation. This could be an admin panel on the Dashboard, or a front-end page. It shows the new assignment, to write a new blog post.
    7. Sally writes a new blog post using the native blog functionality of WP (different content types that aren’t so WP-friendly would need a bit of interface or theme work. Discussion forums, for instance, could easily be stored as custom post types and skinned to look like a traditional forum thread). She might indicate, using a certain tag or category or postmeta checkbox, that this blog post belongs to a particular class, or is a response to a particular assignment. The post is saved to the WP database, and, as above, the Bridge plugin then kicks in, translates the post, and sends it back to the Server, where the process will repeat the next time that Jack logs into his installation.

    As I’m writing this, I realize that it seems really simple and obvious, and I’m sure that there are a hundred reasons why something like this would be hard to actually implement. But it’s worth exploration, as this model enjoys the huge advantage of letting the user choose whichever of a number of interfaces suits their fancy.

    A few complications to mull over, off the top of my head:

    • Certain content is likely not to translate very well between platforms, especially content whose visual presentation is central to its effect. In those instances, the content could be rendered using the client software of the author and linked to in addition to/instead of being transcoded.
    • File storage. Where do you store images, zip files, etc? On the author’s client installation, on the Server, or both?
    • Security. How do you ensure that only Jack is able to download assignments for his course? What prot

    New BuddyPress plugin: BuddyPress Group Email Subscription

    Email Options on settings page

    Email Options on settings page

    I’m quite happy to announce the release of a more-or-less stable (we hope!) version of BuddyPress Group Email Subscription, a BuddyPress plugin that allows for fine-grained, user-controllable email subscription to group content in BuddyPress.

    This plugin is different from some of my others in that it was truly a group endeavor. The base of the plugin was written by David Cartwright, with a little bit of code from me. A nearly complete rewrite of the front-end and most of the guts of the plugin was undertaken by Deryk Wenaus. I wrote the daily and weekly digest functionality, along with some of the settings pages and various bugfixes throughout. The current codebase of the plugin is probably 60% Deryk, 30% me, and 10% David.

    It was my first time working on a truly collaborative software development project like this, and it was a real pleasure working with both of these gentlemen. Thanks, guys.

    Get BuddyPress Group Email Subscription here.