I managed to solve this over the weekend, thought I'd share my solution if someone has the same problem.
TL;DR.
In my setup I use the contrib module Content Access. Basic configuration; In the System user preferences I deny access content
to anonymous users, and in the Access Control settings for each content-type (admin/content/node-type/[type]/access
) I disable View access for authenticated user. This way Content Access module will control exactly which role or which user can view/update/delete any content, even down on an individual node level.
I then wrote my own module that implemented the ACL api. It was a bit tricky to learn the node access system, and even more so with the complete lack of ACL module documentation/tutorials, but when I figured it out the module works really well.
The module utilizes Translation Access (i18n_access module) for deciding the user language access permissions. Translation Access provides access control for Edit/Delete, but not for Viewing content. If that would have been the case, this module would not have been necessary...
So first I added a custom submit handler to the default permissions rebuild form
/**
* Rebuild all permission when using the standard form
*/
function mymodule_form_node_configure_rebuild_confirm_alter(&$form, &$form_state) {
array_unshift($form['#submit'], 'mymodule_rebuild_permissions_submit_handler');
}
/**
* Route submit function through custom handler to get rid of unwanted $form arguments
*/
function mymodule_rebuild_permissions_submit_handler() {
mymodule_rebuild_permissions();
drupal_set_message(t('Language permissions have been rebuilt.'));
}
Now this is where the fun begins, I created a "rebuild permissions" function that iterates all nodes restricted by language (in my case, all nodes). If a uid is provided, permissions will be rebuilt for that user only.
/**
* Rebuilds permissions for all nodes and users, or only for a specified user
*/
function mymodule_rebuild_permissions($uid = null) {
//Select the nodes to apply language restriction on
$nodes = db_query("SELECT nid, language FROM {node}");
while($node = db_fetch_object($nodes)) {
$acl_id = mymodule_get_node_acl($node);
if(!$uid) {
mymodule_update_user_access_all($acl_id, $node);
} else {
mymodule_update_user_access($acl_id, $node, $uid);
}
}
}
As seen above we need a function to retrieve/create the ACL id of the node
/**
* Gets the node ACL id
*/
function mymodule_get_node_acl($node) {
$module = 'mymodule';
$acl_name = $module.'_'.$node->nid;
//Get the ACL for this node
$acl_id = acl_get_id_by_name($module, $acl_name);
//If it doesn't exist, create new ACL
if(!$acl_id){
$acl_id = acl_create_new_acl($module, $acl_name);
// View = 1, Update = 0, Delete = 0, Priority = 0
acl_node_add_acl($node->nid, $acl_id, 1, 0, 0, 0);
}
return $acl_id;
}
As well as for updating user permissions
/**
* Update language permissions for all users
*/
function mymodule_update_user_access_all($acl_id, $node) {
$users = db_query("SELECT uid FROM {users} WHERE uid > 1");
while($user = db_fetch_object($users)) {
mymodule_update_user_access($acl_id, $node, $user->uid);
}
}
/**
* Updates language permissions for a specific user
*/
function mymodule_update_user_access($acl_id, $node, $uid) {
//Remove from list
acl_remove_user($acl_id, $uid);
//Add again if user has permissions
if(mymodule_node_access($node, $uid)) {
acl_add_user($acl_id, $uid);
}
}
Check if the user has role and language access
function mymodule_node_access($node, $uid) {
//Get the user roles
$user_roles = user_load($uid)->roles;
//Get the content access roles
$node_roles = content_access_get_per_node_settings($node);
//Check if user has permission to view node
$node_access = false;
if(isset($node_roles['view'])) {
foreach($node_roles['view'] as $role) {
if(isset($user_roles[$role])) {
$node_access = true;
}
}
}
//Check i18n selected languages
$perm = db_result(db_query("SELECT perm FROM {i18n_access} WHERE uid = %d", $uid));
$user_languages = $perm ? explode(', ', $perm) : array();
$lang_access = in_array($node->language, $user_languages) || $node->language == '';
//Return true if we have both node and language access
return $node_access && $lang_access;
}
Obviously we have to update access control when inserting/updating nodes and users
/**
* Implementation of hook_nodeapi()
*/
function mymodule_nodeapi(&$node, $op) {
if($op == 'insert' || $op == 'update') {
$acl_id = mymodule_get_node_acl($node);
mymodule_update_user_access_all($acl_id, $node);
if($op == 'insert') {
node_access_rebuild();
}
}
}
/**
* Implementation of hook_user()
*/
function mymodule_user($op, &$edit, &$account) {
if($op == 'after_update') {
mymodule_rebuild_permissions($account->uid);
}
}
Finally these functions had to be included to make the ACL api work
/**
* Implementation of ACL hook hook_enabled()
*/
function mymodule_enabled() {
return !mymodule_disabling();
}
/**
* Implementation of hook_disable().
*/
function mymodule_disable() {
mymodule_disabling(TRUE);
}
/**
* Remembers if we have disabled access.
*/
function mymodule_disabling($set = NULL) {
static $disabling = FALSE;
if (isset($set)) {
$disabling = $set;
}
return $disabling;
}
...and that's it!
Don't forget to rebuild permissions before anything (/admin/content/node-settings
)