3

I'm building a large multilingual Drupal intranet, but have some trouble configuring the language settings.

There are about 6 different languages and a lot of sensitive content for each language. There are about 20 different roles (for each country), where each role can have access to 1 or more languages. E.g. All regular users in Germany should only be able to access German content, however a German manager should be able to switch between the German and English content.

Right now I'm struggling just to get a regular German user to only see German content, and a regular English user to only see English content. How would that be done?

If I select "Language negotiation: none" all users see the English content and no language-dependent stuff works. If I select "Language negotiation: by prefix only" all users are able to switch between all different languages by simply typing the prefix in the url.

Is it possible to accomplish what I'm looking for without writing my own access module?

3

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)

0

First I'll try Rules, maybe you can find integration with i18n.

If not writing custom module is the only option. Check hook_boot, you can use it to check user language and display content with language that you want.

  • OK, I will investigate the Rules module... Also thanks for telling about hook_boot, didn't know it – hampusohlsson Jul 18 '12 at 10:03
  • hook_boot() is invoked too early in the bootstrap process; when it is invoked, Drupal has still to initialize the language variables. – kiamlaluno Jul 18 '12 at 11:27
  • @kiamlaluno do you have any suggestions for my problem? – hampusohlsson Jul 18 '12 at 11:29
0

If you select "Language negotiation: by prefix only" you might be able to use hook_init() to do something along the lines of:

if "arg(#) equals user's language" {
  return // all is good
} else {
  if "user's role allows access to that language" {
    return // all is good
  } else {
    drupal_not_found();
  }
}

You will obviously need to flesh out the "arg(#)..." and "user's role allows" logic to your site specifics.

  • Definitely worth trying, thanks.. (Wanted to upvote but didn't have enough rep) – hampusohlsson Jul 19 '12 at 8:25
  • And you should remember that this hook doesn't invoke on cached pages. – Codium Jul 19 '12 at 15:28
  • true, but if the users have roles, this suggests that they are authenticated and thus the page cache isn't that much of an issue, right? – Jimajamma Jul 19 '12 at 21:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.