The Organic Groups module in Drupal provides groups of users with a way of discussing common interests in a separate space of their own. It does a great job at controlling who has access to what, depending on how open you want your groups to be. Yet, we've found in a couple of projects that it didn't quite fulfill our needs. Naturally, you wouldn't be reading this if we hadn't found a solution.
The Organic groups module has been a long time and has served Drupal developers with a nice alternative to the classic Forum feature, where the concept of forums are replaced by a completely flexible, community managed, collection of user groups. It's sometimes even used as a more generic toolkit for controlling access to different parts of a web site.
The access control in OG basically has two parts: policies for group membership and access to group content. The first part means you can have groups that anyone can become a member of, groups where membership is strictly controlled by an admin, or groups that do something in between. The second part is about the actual content: you can decide on a group basis if just members or everyone should be able to see it, and you can make the same choice for each post within a group.
The problem
Although you can choose whether or not some group or some post should be private (members only) or public – sometimes what you want is something in between. Imagine an organization that wants an intranet kind of solution based on OG. Obviously, it's important that all internal content is hidden from the open public. This could be achieved by only having private groups with private content, but that means that members of the organization can only see groups that they belong to. This makes it impossible to take advantage of the organic nature of OG, simply because users can't tell what groups are available, and they can't see any of their content, until they've been assigned to the groups by an administrator.
As been pointed out in a comment to this post, a way of dealing with this is to block the "access content" permission for anonymous users, but that doesn't work if you want to have some other content that is readable by everyone on the same site. What we wanted was a solution where OGs public groups and posts were public only to members of the organization – not to the general public – while keeping other content open. This particular use case is not possible to achieve out of the box.
Making things public – to some
Two things make it possible for us to work around this problem without actually changing any code in OG. One is the relative flexibility of Drupal's node access system, which makes it possible for us to create a module which defines access grants side by side with OG's. The other is the undocumented hook_og_access_grants_alter() (invoked at the bottom of og_access_node_access_records()), which makes it possible to hook into OG's access grants and alter them.
Since we finished our own implementation of this, a module has been released which uses this approach: OG Access Roles. What it does is that it adds a new setting for individual group posts which allows you to not just make them private or public, but also to specify specific user groups that should be allowed to view the post.
Our approach is a little simpler: it doesn't add any extra settings, but rather makes it so that "public" in fact means "public to authenticated users", which actually suits us better since it saves our client from having to do any extra clicks. It also provides a simple example of how to use the node access system, so let's have a look at how it's done.
The solution
What's required for this implementation is a module containing four functions, each implementing a separate hook. They are all listed here, including some explanation of each. The code assumes you're creating a module called mymodule.
First, the hook_node_access_explain() implementation simply defines and explains our grant realm, which is what will grant access to public nodes for authenticated users.
/**
* Implementation of hook_node_access_explain().
*/
function mymodule_node_access_explain($row) {
if ($row->realm == 'mymodule_authenticated') {
return t('All authenticated users may view this node.');
}
}Then, we implement hook_node_access_records(), which takes a node that the node access system sends to it, and decides if this grant should be used for the particular node. If the node is a public OG post, the grant is added. If not, this function doesn't do anything.
/**
* Implementation of hook_node_access_records.
*/
function mymodule_node_access_records($node) {
// The 'authenticated' grant that will be set on public OG content.
$grant = array (
'realm' => 'mymodule_authenticated',
'gid' => 0,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
'priority' => 0,
);
// Non-private groups.
if (og_is_group_type($node->type) && !$node->og_private) {
return array($grant);
}
// Public group posts.
elseif (!empty($node->og_groups) && $node->og_public) {
return array($grant);
}
return NULL;
}Then, we implement hook_node_grants() to define if a specific user account is granted view access.
/**
* Implementation of hook_node_grants().
*/
function mymodule_node_grants($account, $op) {
$grants = array();
// Allow authenticated users to view public group nodes.
if ($op == 'view' && in_array('authenticated user', $account->roles)) {
$grants['mymodule_authenticated'][] = 0;
}
return $grants;
}There is still one missing piece. We've now added our own custom node access grant for public OG nodes. But OG is still doing the same thing on its end, which means there is a potential problem that it will override what we're trying to do. There is a weighting principle in the node access system which would allow us to override OGs public grants, but we've found that doing this may also override OG's private grants, effectively making private nodes public.
In order to avoid any confusion, we use hook_og_access_grants_alter() to strip out OG's public grants before they are returned to the node access system. The nodes that would've received this grant will get our custom grant instead, in mymodule_node_access_records().
/**
* Implementation of hook_og_access_grants_alter().
*/
function mymodule_og_access_grants_alter(&$grants, $node) {
// og_access is about to insert grants for this node. Remove any public grants
// so that this node isn't visible to everyone. It will be replaced by our
// own mymodule_authenticated grant.
foreach ($grants as $key => $grant) {
if ($grant['realm'] == 'og_public') {
unset($grants[$key]);
}
}
}Conclusion
Node access is a rather complicated area, so it's probably wise to avoid doing custom development with it as much as possible. But as we know, sometimes standard solutions just don't cover our needs. In this post we've shown that creating a simple node access module doesn't have to be that difficult, and that whenever there's a problem, there is (almost) always a hook to help fix it.


Kommentarer
You could have just removed
You could have just removed the "access content" permission from anonymous users...
It will be easier in Group module
In Group module (The Drupal 7 version of OG) it will be easier to mark nodes as private, while still in Public groups. See the intro movie.
Access content permission
You have a point about blocking "access content". What I forgot to mention is that we needed other content on the site to be public. I'll update the original post, thanks!
@Amitaibu: Looking forward to learning more about that!
Rules to mark content public
I've found myself using Rules more frequently to handle situations like this. It's become a very powerful module and has helped reduce the number of modules I need for my sites.
In this particular case, if you wanted to take content that was private and in a private group, and make it publicly visible then I would add a flag (flag module) or additional checkbox (using CCK) to the node. When the author selects this flag, it would trigger a rule to flip the "public" checkbox on or off.
I'm not sure that made sense or not... but basically it's using rules (and possibly flag) to let the author note if content is public or private within the group. It doesn't let the use select what roles can see it though.
Great tutorial! I can't wait
Great tutorial! I can't wait to try it out. I was honestly surprised that OG didn't include this functionality to begin with. I have a question though before I start since I'm a bit of a PHP wimp ;)
In your third chunk of code, Implementation of hook_node_grants, could I make 2 roles active, like 'employee' and 'manager' roles for example but not necessarily every 'authenticated user'?
I thought maybe:
if ($op == 'view' && (in_array('employee', $account->roles) || in_array('manager', $account->roles))Or perhaps:
if ($op == 'view' && (in_array('employee' || 'manager' , $account->roles)Any thoughts? thanks again for this great post.
Definitely. You can check on
Definitely. You can check on any roles you want, not just 'authenticated user'. Your second exemple won't work, but the first one should. Another way woud probably be:
if (array_intersect(array('employee', 'manager'), $account->roles)) {why not as a module?
Why not package and release your code as a contributed addon-module for organic groups?
That would make it accessible to more users.
As you hinted in the article, this is a feature that should have been in OG to begin with, so this is badly needed.
OG Access Roles
Sascha, I think that module probably exists already. OG Access Roles does basically the same thing as our solution, but in a more configurable way. The downside would be that it may expose too many options to the user, depending on what you want to do.
Very timely!
I was actually just trying to figure out how to do something like this for my Drupal distro Eduglu. I wanted to have three types of groups (or spaces) by default, Public, Private, and Protected. For protected, I wanted the groups to have open membership but that only authenticated members could access the content. With your code and a bit of tweaking, it's working! Thanks!
Thanks!
Thank you! This example is great and it works.
Hi! I'm using this w/ your
Hi! I'm using this w/ your tip 15 jun 2010. Like Kyle, I'm trying to setup a hybrid, but in my case, I'm looking to list the avail. groups in the directory (according to the role's permissions eg: staff=list all v clients=list none) while honouring the standard OG permissions for edit/delete/etc when _within_ groups.
So... can I bother you for a few pointers on 1) listing groups in the directory, but keeping their content private & 2) toggling (1) according to the user's role?
Using Open Atrium 1.0. Drupal experience: enough to be dangerous. PHP experience : I can break stuff.
TIA
Sorry, I can't think of any
Sorry, I can't think of any good pointers off the top of my head. Try the Organic Groups group! Also, this is an old post, so there may be other/better solutions available, especially if you're using Drupal 7.
Lägg till ny kommentar